Skip to content

Unify design system with blue/indigo palette and new UI primitives#178

Merged
AnnatarHe merged 1 commit intomasterfrom
claude/refine-design-quality-3SauO
Apr 14, 2026
Merged

Unify design system with blue/indigo palette and new UI primitives#178
AnnatarHe merged 1 commit intomasterfrom
claude/refine-design-quality-3SauO

Conversation

@AnnatarHe
Copy link
Copy Markdown
Member

@AnnatarHe AnnatarHe commented Apr 14, 2026

Summary

This PR introduces a unified design system across the application by establishing a canonical blue-400 → indigo-500 gradient palette, creating reusable UI primitives (Surface, DecorBlobs, PageHeader, PageShell), and refactoring existing pages to use these new components. This ensures visual consistency and reduces duplication across auth, error, payment, policy, and dashboard pages.

Key Changes

New UI Primitives

  • Surface — Glass-morphism card wrapper with four variants (default, muted, elevated, plain) replacing ad-hoc Card and inline glass styles
  • DecorBlobs — Reusable decorative gradient blob background with tone variants (primary, danger, success)
  • PageHeader — Unified page/section title component with optional eyebrow, icon, description, and action slots; enforces the blue-400 → indigo-500 gradient
  • PageShell — Server-side page wrapper providing consistent padding, max-width, and slide-in animation

Color Palette Unification

  • Replaced scattered color schemes (purple, cyan, teal, green, etc.) with unified blue-400 → indigo-500 gradient across:
    • Auth pages (/auth/phone, /auth/signin)
    • Error and 404 pages
    • Payment pages (success/canceled)
    • Policy/support pages
    • Dashboard pages (profile, admin, comments)
  • Updated tailwind.css with CSS variables for the primary palette

Page Refactors

  • Auth layouts — Replaced Card with Surface, added DecorBlobs, introduced navigation tabs with active state styling
  • Error/404 pages — Replaced inline gradient orbs with DecorBlobs, wrapped content in Surface
  • Payment pages — Added DecorBlobs and Surface wrappers, improved icon and CTA styling
  • Support page — Unified background and card styling using new primitives
  • Pricing page — Retuned gradient orbs to blue/indigo palette, simplified background markup
  • Dashboard pages — Updated glow effects and card styling to use blue/indigo tones

Component Updates

  • FieldInput — Modernized styling with responsive layout and updated color scheme
  • Button — Adjusted primary variant to use blue-400 → indigo-500 gradient
  • Card — Kept for backward compatibility; now visually matches Surface
  • Comments page — Added PageHeader component for consistent title styling

Style Cleanup

  • Removed unused CSS module (book-cover.module.css)
  • Removed inline background gradients in favor of anna-page-container class
  • Simplified decorative element markup (removed filter/opacity duplication)
  • Added pointer-events-none to decorative elements for better UX

Minor Fixes

  • Fixed typo in payment canceled page metadata ("Payemnt" → "Payment")
  • Improved import ordering and consistency across files
  • Enhanced accessibility with proper aria-label and aria-current attributes on navigation

Implementation Details

  • All new UI primitives use cn() utility for class composition and support dark mode
  • DecorBlobs supports both absolute and fixed positioning for flexibility
  • Surface uses polymorphic component pattern (as prop) for semantic HTML
  • Gradient orbs use blur-3xl and opacity values tuned for the new palette
  • All pages maintain the with-slide-in animation class for consistent entrance

https://claude.ai/code/session_0126FKHoW1xBRsuz1gGyi97E


Open with Devin

Consolidates the app's palette, primitives, and page shells so every
route shares the same calm, blue-400 anchored look defined in CLAUDE.md.

Foundation
- Add --color-primary, --shadow-elevated tokens and retune
  .anna-page-container / default-bg-gridient to a sky-50 -> blue-50 ->
  indigo-50 gradient (dark: slate-950 -> slate-900 -> blue-950).
- New layout primitives: ui/surface, ui/page-shell, ui/page-header,
  ui/decor-blobs - server-safe building blocks reused across pages.
- Modernize Card (glass surface, dark mode) and FieldInput (rounded-lg,
  blue-400 focus ring, responsive width); recolor loading spinner.

Global shells
- DashboardContainer drops the muddy bg-gray-400 overlay so the unified
  page gradient shows through; adds subtle decor blobs.
- Settings layout moves to Surface with split sidebar/content.
- Auth signin / phone layouts get a Surface hero with segmented tabs.

Server pages
- 404 / user-not-found / error / payment success + canceled / policy
  support move to anna-page-container + Surface + DecorBlobs; drop
  hardcoded bg-slate-900 and replace #045fab literals with blue-400.
- Home, profile activity, webhooks, comments, upload, admin, clippings
  layout, pricing content & skeleton retuned to blue-400 -> indigo-500.
- Comments page drops its conflicting min-h-screen wrapper.

Shared components
- Footer / NavigationBar / Button / Noun editor: recolor accents to
  blue-400/indigo-500; remove all remaining #045fab hex literals.
- BookCover: drop empty CSS module, use pure Tailwind with blue-toned
  shadow and hover translate.

Loading states
- DashboardLoadingPage: spinner + consistent centered layout.
- CenterPageLoading, home skeleton, pricing skeleton recolored to blue
  tinted placeholders.

https://claude.ai/code/session_0126FKHoW1xBRsuz1gGyi97E
Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request implements a comprehensive UI overhaul to unify the design system using new primitives like Surface, PageHeader, and DecorBlobs, while standardizing the color palette and layouts across the application. Feedback points out an unsafe non-null assertion in the admin panel and suggests further architectural improvements, including refactoring the legacy Card component to use the Surface primitive, consolidating duplicated authentication layouts, and making the Button component polymorphic to reduce style duplication.

<div className="h-2 w-2 rounded-full bg-blue-400" />
<span className="text-sm font-medium text-slate-600 dark:text-slate-300">
{t('Showing books')} {offset + 1} -{' '}
{offset + (data!.adminDashboard.uncheckedBooks?.length || 0)}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The non-null assertion (!) on data is unsafe. If the query returns no data or fails silently, this will cause a runtime error. It's better to use optional chaining with a fallback value to ensure the page doesn't crash.

Suggested change
{offset + (data!.adminDashboard.uncheckedBooks?.length || 0)}
{offset + (data?.adminDashboard?.uncheckedBooks?.length || 0)}

@@ -1,5 +1,7 @@
import type React from 'react'

import { cn } from '@/lib/utils'
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Import the new Surface component to use it as the base for the legacy Card wrapper, ensuring consistency with the new design system primitives.

Suggested change
import { cn } from '@/lib/utils'
import { cn } from '@/lib/utils'
import Surface from '../ui/surface/surface'

Comment on lines +21 to 32
<section
className={cn(
'm-4 rounded-2xl border border-white/40 bg-white/70 p-6 shadow-sm backdrop-blur-xl transition-shadow hover:shadow-md dark:border-slate-800/40 dark:bg-slate-900/70',
glow && 'ring-1 ring-blue-400/30',
className
)}
onClick={onClick}
style={style}
data-glow={glow}
>
{children}
</section>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Refactor the Card component to use the Surface primitive. This centralizes the glass effect styling and ensures that this legacy component stays in sync with any future updates to the design system's canonical surface styles.

    <Surface
      as="section"
      className={cn(
        'm-4 p-6 transition-shadow hover:shadow-md',
        glow && 'ring-1 ring-blue-400/30',
        className
      )}
      onClick={onClick}
      style={style}
      data-glow={glow}
    >
      {children}
    </Surface>

Comment on lines +38 to 107
<section className="anna-page-container relative flex min-h-screen items-center justify-center overflow-hidden px-4 py-10">
<DecorBlobs />
<Surface
variant="elevated"
className="with-slide-in relative z-10 w-full max-w-md p-8 md:p-10"
>
<div className="mb-6 flex flex-col items-center justify-center">
<Image
src={logoLight}
alt="clippingkk logo"
className="dark:hidden"
width={80}
height={80}
/>
<Image
src={logoDark}
alt="clippingkk logo"
className="hidden dark:block"
width={80}
height={80}
/>
<h1 className="font-lato mt-3 bg-gradient-to-r from-blue-400 via-blue-500 to-indigo-500 bg-clip-text text-2xl font-semibold tracking-tight text-transparent">
ClippingKK
</h1>
</div>

<nav
className="mb-6 grid grid-cols-2 gap-1 rounded-xl border border-slate-200/70 bg-slate-50/80 p-1 dark:border-slate-800/60 dark:bg-slate-900/60"
aria-label="Auth methods"
>
<Link
href="/auth/phone"
aria-current="page"
className="rounded-lg bg-blue-400 px-4 py-2 text-center text-sm font-medium text-white shadow-sm transition-colors hover:bg-blue-500 dark:bg-blue-400 dark:text-slate-950 dark:hover:bg-blue-300"
>
{t('app.auth.phone')}
</Link>
<Link
href="/auth/signin"
className="rounded-lg px-4 py-2 text-center text-sm font-medium text-slate-600 transition-colors hover:text-slate-900 dark:text-slate-300 dark:hover:text-white"
>
{t('app.auth.signin')}
</Link>
</nav>

<Link
href="/auth/signin"
className={
'flex px-8 py-4 text-lg transition-colors duration-200 hover:bg-indigo-400'
}
>
{t('app.auth.signin')}
</Link>
<div className="space-y-4">{children}</div>

<div className="relative my-6">
<div className="absolute inset-0 flex items-center">
<span className="w-full border-t border-slate-200/70 dark:border-slate-800/60" />
</div>
<hr className="my-2" />
{children}
<hr className="my-2" />
<div className="flex items-center justify-center">
<a
href={`https://github.com/login/oauth/authorize?client_id=${GithubClientID}&scope=user:email`}
onClick={onGithubClick}
title="github login"
>
<GithubLogo />
</a>
<div className="relative flex justify-center">
<span className="bg-white/70 px-3 text-xs tracking-wider text-slate-500 uppercase dark:bg-slate-900/70 dark:text-slate-400">
{t('app.auth.or') || 'or'}
</span>
</div>
</>
</Card>
</div>

<div className="flex items-center justify-center">
<a
href={`https://github.com/login/oauth/authorize?client_id=${GithubClientID}&scope=user:email`}
onClick={onGithubClick}
title="github login"
className="rounded-xl p-2 transition-colors hover:bg-slate-100 dark:hover:bg-slate-800"
>
<GithubLogo />
</a>
</div>
</Surface>
</section>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

There is significant code duplication between src/app/auth/phone/layout.tsx and src/app/auth/signin/layout.tsx. Both layouts share the same structure, logo, title, navigation tabs, and social login section. Consider extracting this shared shell into a reusable component (e.g., AuthLayout) to improve maintainability and reduce duplication, which is one of the key goals of this PR.

<Link
href={`/dash/${myUidInt}/unchecked`}
className="group relative overflow-hidden rounded-md bg-gradient-to-r from-blue-500 to-purple-500 px-6 py-2 font-medium text-white shadow-md transition-all duration-300 hover:translate-y-[-2px] hover:shadow-lg"
className="inline-flex items-center rounded-xl bg-blue-400 px-5 py-2 text-sm font-medium text-white shadow-sm transition-all duration-200 hover:-translate-y-0.5 hover:bg-blue-500 hover:shadow-md dark:bg-blue-400 dark:text-slate-950 dark:hover:bg-blue-300"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This primary button style is manually duplicated in many places across the application (Admin page, 404 page, Error page, Payment pages, etc.). To ensure consistency and reduce maintenance overhead, consider making the Button component polymorphic (using an as prop) so it can be used with the Next.js Link component while sharing the same visual styles and gradient logic.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: fff1aec520

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

</a>
<div className="relative flex justify-center">
<span className="bg-white/70 px-3 text-xs tracking-wider text-slate-500 uppercase dark:bg-slate-900/70 dark:text-slate-400">
{t('app.auth.or') || 'or'}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Add missing app.auth.or locale entry

The new separator label uses t('app.auth.or') || 'or', but no app.auth.or key was added in locale files, so i18next will return the literal key string (truthy) and the 'or' fallback never applies. This makes auth pages render app.auth.or to end users (same pattern appears in src/app/auth/signin/layout.tsx), so please add the key in translations or pass a defaultValue to t(...) instead of relying on ||.

Useful? React with 👍 / 👎.

@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Apr 14, 2026

PR Review: Unified Design System (blue/indigo palette + UI primitives)

Overall this is a solid, well-motivated refactor. The four new primitives (Surface, DecorBlobs, PageHeader, PageShell) are clean and composable, and the palette unification is exactly what CLAUDE.md asks for. A few issues worth addressing before merging:


Bugs / Correctness

Missing translation key app.auth.or

Both auth layouts use {t('app.auth.or') || 'or'} but app.auth.or doesn't exist in any locale file (en.json, zhCN.json, ko.json). The fallback always fires, so localization for this string is silently broken. Either add the key to every locale file or just hardcode the string since it's currently untranslated anyway.

// src/app/auth/phone/layout.tsx + src/app/auth/signin/layout.tsx
{t('app.auth.or') || 'or'}   // key missing in all locales

href={homeLink as any} in payment success

src/app/payment/success/content.tsx suppresses a type error with as any. This masks a potential runtime issue (e.g. homeLink being undefined). The underlying type mismatch should be fixed rather than cast away.


Design System Consistency

Repeated CTA class string instead of Button component

The Button component was just updated with the from-blue-400 to-indigo-500 gradient, but dozens of new inline <Link> and <button> elements across the changed pages bypass it entirely:

// Appears 10+ times across payment, not-found, error, admin pages:
className="inline-flex items-center gap-2 rounded-xl bg-blue-400 px-6 py-3 font-medium text-white shadow-sm transition-all duration-200 hover:-translate-y-0.5 hover:bg-blue-500 hover:shadow-md dark:bg-blue-400 dark:text-slate-950 dark:hover:bg-blue-300"

This works but defeats the purpose of a centralized Button primitive. Next.js <Link> can't use the Button component directly, but a shared linkButtonClass or buttonVariants helper (like shadcn/ui's approach) would keep the styles in one place and prevent drift.

Defined CSS variables go unused

tailwind.css introduces --color-primary, --color-primary-soft, and --shadow-elevated but none of the new components reference them — they all use hardcoded Tailwind color classes. Either wire these up in the components (e.g. bg-[var(--color-primary)]) or remove them from this PR to avoid dead code.


Duplication

Auth layouts are nearly identical

/auth/phone/layout.tsx and /auth/signin/layout.tsx now share ~90% of their markup (logo block, nav tabs, divider, GitHub login). The only difference is which tab carries aria-current="page". A shared AuthShell component accepting an activeTab prop would cut this in half and ensure future changes stay in sync.


Minor Issues

Surface loses prop types at runtime

// surface.tsx
const Component = (as ?? 'div') as React.ElementType

The cast to React.ElementType erases the element-specific prop types. This is a known limitation of the polymorphic component pattern in TypeScript, but consider documenting it or using a stricter cast so accidental invalid props surface at compile time rather than runtime.

DecorBlobs z-index may not work without a positioned ancestor

DecorBlobs uses -z-10 which only creates a stacking context relative to the nearest positioned ancestor. In pages where the parent doesn't have position: relative (or equivalent), the blobs may render above content. The absolute inset-0 parent should also set position: relative on the wrapping element — most usages already do this, but worth auditing.

Skeleton gradient is visually complex for a loading state

// skeleton.tsx
className="... animate-pulse rounded-2xl border border-white/40 bg-gradient-to-br from-blue-400/10 via-slate-200/60 to-indigo-400/10 backdrop-blur-xl ..."

The backdrop-blur-xl on a skeleton has no frosted-glass effect (nothing is behind it during loading). Simpler: bg-slate-200 dark:bg-slate-800 animate-pulse rounded-2xl is standard practice and avoids the browser painting a blur on an empty skeleton.


Positive Notes

  • The aria-current="page" on the active auth tab and aria-label="Auth methods" on the <nav> are good accessibility improvements.
  • Adding type="button" to the reset button in error.tsx is a correct fix (prevents accidental form submission).
  • Fixing the "Payemnt" typo in payment metadata is appreciated.
  • pointer-events-none and aria-hidden on DecorBlobs are exactly right for purely decorative elements.
  • The PageHeader API is well-designed — the eyebrow, icon, actions, align, and as slots cover the real usage patterns without over-engineering.

Summary: Address the missing app.auth.or translation key and the as any cast before merging. The duplication and unused CSS variables are worth a follow-up but aren't blockers.

Copy link
Copy Markdown

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 2 potential issues.

View 6 additional findings in Devin Review.

Open in Devin Review

}
>
<div className="dark:bg-opacity-80 bg-opacity-60 flex min-h-screen w-full flex-col bg-gray-400 backdrop-blur-xl dark:bg-gray-900">
<section className="anna-page-container relative flex min-h-screen w-full flex-col overflow-hidden">
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 overflow-hidden on DashboardContainer breaks sticky navigation bar

Adding overflow-hidden to the <section> in DashboardContainer breaks the position: sticky behavior of the <nav> element inside NavigationBar (src/components/navigation-bar/navigation-bar.tsx:63). Per CSS spec, when an ancestor has overflow set to anything other than visible, it becomes the nearest scroll container for sticky positioning. Since the <section> stretches to fit content and never actually scrolls, the nav will never "stick" — it scrolls away with the page. This affects every route that uses DashboardContainer: all /dash/[userid]/* pages, /pricing, /payment/*, and /policy/support. The fix is to replace overflow-hidden with overflow-clip (Tailwind's overflow-clip), which clips the decorative blobs without creating a scroll container.

Suggested change
<section className="anna-page-container relative flex min-h-screen w-full flex-col overflow-hidden">
<section className="anna-page-container relative flex min-h-screen w-full flex-col overflow-clip">
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment thread src/app/error.tsx
Comment on lines +31 to 33
<div className="mx-auto mb-6 inline-flex h-16 w-16 items-center justify-center rounded-2xl bg-rose-400/10 ring-1 ring-rose-400/20 dark:bg-rose-400/15">
<AlertTriangle className="h-8 w-8 text-rose-500 dark:text-rose-300" />
</div>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 inline-flex with mx-auto prevents icon centering on error page

The error icon container uses mx-auto mb-6 inline-flex h-16 w-16 ... but mx-auto has no centering effect on inline-level elements — auto margins on inline boxes compute to 0 per CSS spec. Unlike all other similar pages in this PR (e.g. src/app/not-found.tsx:24, src/app/payment/canceled/content.tsx:6) which wrap icons in a flex flex-col items-center parent, the error page's icon sits directly inside the <Surface> which is a plain <div> in normal block flow. The icon will render left-aligned instead of centered, breaking the visual symmetry with the centered heading and text above/below it.

Suggested change
<div className="mx-auto mb-6 inline-flex h-16 w-16 items-center justify-center rounded-2xl bg-rose-400/10 ring-1 ring-rose-400/20 dark:bg-rose-400/15">
<AlertTriangle className="h-8 w-8 text-rose-500 dark:text-rose-300" />
</div>
<div className="mx-auto mb-6 flex h-16 w-16 items-center justify-center rounded-2xl bg-rose-400/10 ring-1 ring-rose-400/20 dark:bg-rose-400/15">
<AlertTriangle className="h-8 w-8 text-rose-500 dark:text-rose-300" />
</div>
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@AnnatarHe AnnatarHe merged commit ed780bd into master Apr 14, 2026
13 checks passed
@AnnatarHe AnnatarHe deleted the claude/refine-design-quality-3SauO branch April 14, 2026 09:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants