- New MilestoneStepper primitive (horizontal per-phase progress) - Token-migrate portal shell, phase cards, sidebar cards, kanban to dual-theme - Soft-tint status pills (emerald/amber/muted) replacing solid badges - embedded prop on ClientDashboard fixes double-header in multi-project tabs Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
9.6 KiB
Design System: iamcavalli Admin & Client Portal (v1.0)
"Minimalist Premium / Quiet Luxury" — the aesthetic that replaces the old mixed token/hardcoded styling across ClientHub's admin area. First applied to the Lead Pipeline page (Phase: Pipeline redesign); the tokens and primitives documented here are the base for restyling every other section.
Philosophy
- Quiet, not loud. Elevation is nearly invisible (shadow opacity ≤ 0.02), borders are hairline, color is used sparingly and only to carry meaning (status, semantics) — never for decoration.
- Density with air. Generous padding (
py-4 px-6in tables,p-4in kanban columns) paired with small type (text-xs,text-[11px]) reads as premium rather than cramped. - Numbers are monospace. Prices, phone numbers, dates, counts — anything
tabular/numeric — use
font-monoand right-alignment so columns of digits line up. - Dual-theme by construction. Every surface is built from semantic
tokens (
bg-card,text-muted-foreground,border-border, …), never raw Tailwind palette classes (bg-white,text-slate-900, …) or hex literals. This is what makes dark mode "just work" without a parallel dark stylesheet. - Motion is functional. Transitions exist to explain state change (sidebar
collapse, hover, drag) —
duration-200–duration-350,ease-[cubic-bezier(0.4,0,0.2,1)]for the sidebar specifically. No decorative animation.
Typography
- Sans: Plus Jakarta Sans (weights 300–700), loaded via
next/font/googleas--font-plus-jakarta-sans, mapped to Tailwind's--font-sansin@theme. Used for all UI text. - Mono: Geist Mono,
--font-geist-mono→--font-mono. Used for numeric data cells only (prices, phone numbers, counts, dates in tables). - Scale conventions:
- Page title:
text-2xl font-semibold tracking-tight - Page subtitle:
text-xs text-muted-foreground - Table header cells:
text-[11px] font-semibold uppercase tracking-wider text-muted-foreground - Table body:
text-sm - Badge/pill label:
text-[10px]–text-xs font-semibold uppercase tracking-wide
- Page title:
Color Tokens
Raw values live in :root (light) / .dark (dark) in src/app/globals.css
and are mapped to Tailwind utilities via @theme inline. Components must
consume the mapped utility, never the raw hex or a Tailwind palette shade.
| Utility | Light | Dark | Usage |
|---|---|---|---|
bg-background / text-foreground |
#ffffff / #1a1a1a |
#0e1512 / #f2f4f3 |
Page canvas |
bg-card / text-card-foreground |
#ffffff |
#131a16 |
Cards, table, kanban cards, header bar |
bg-muted / text-muted-foreground |
#f9f9f9 / #71717a |
#171f1b / #9aa39e |
Subtle backgrounds, secondary text, kanban columns |
bg-primary / text-primary-foreground |
#1A463C |
#3FA88C |
Primary actions, sidebar, active nav state |
bg-accent |
#DEF168 |
#DEF168 |
Rare highlight accent (lime) |
border-border / border-input |
#e5e7eb |
#26302b |
All hairline borders |
ring-ring |
#1A463C |
#3FA88C |
Focus rings |
bg-destructive |
#ef4444 |
#f87171 |
Destructive actions |
Sidebar exception: the sidebar stays brand green bg-[#1A463C] in both
themes (it is not a themed surface — it's the constant brand anchor).
Status/semantic colors (lead stages, badges) use Tailwind's default
palette (blue/purple/amber/orange/emerald/red) directly, each paired with an
explicit dark: variant for legibility on dark surfaces — see StatusBadge.
Elevation & Radius
--radius: 0.5rem(base) is wired into@themeas--radius-lg(= --radius) and--radius-xl(= --radius + 0.25rem), sorounded-lg/rounded-xlutilities resolve consistently off one token.--shadow-card: 0 4px 20px rgba(0,0,0,0.01)→shadow-cardutility. Default resting elevation for cards, table containers, kanban cards.--shadow-card-hover: 0 8px 30px rgba(0,0,0,0.02)→shadow-card-hoverutility. Applied on hover for interactive cards.
Layout Conventions
- Table container:
bg-card rounded-xl border border-border shadow-card overflow-hidden. - Table header row:
bg-muted/50 border-b border-border text-[11px] uppercase tracking-wider text-muted-foreground. - Table body rows:
py-4 px-6,divide-y divide-border,hover:bg-muted/40 transition-colors. - Kanban column:
bg-muted/60 rounded-xl border border-border p-4. - Kanban card:
bg-card rounded-lg border border-border shadow-sm hover:border-primary/30 hover:shadow-card-hover transition-all. - Page header: title
text-2xl font-semibold tracking-tight text-foreground, subtitletext-xs text-muted-foreground mt-1, primary action buttonbg-primary text-primary-foreground hover:bg-primary/90.
Color/Class Migration Map (dual-theme principle)
When restyling any surface, replace hardcoded classes with token utilities:
| Old (hardcoded) | New (token) |
|---|---|
bg-white |
bg-card |
bg-slate-50 / bg-gray-50 |
bg-muted |
border-slate-100 / border-slate-200 |
border-border |
text-slate-900 / text-[#1a1a1a] |
text-foreground |
text-slate-500 / text-slate-400 / text-[#71717a] |
text-muted-foreground |
bg-brand-dark / text-[#1A463C] (as action color) |
bg-primary / text-primary |
Motion
- Sidebar collapse:
transition-[width] duration-350 ease-[cubic-bezier(0.4,0,0.2,1)]on the<aside>; label/logo text usewhitespace-nowrap transition-opacity duration-200and fade toopacity-0immediately on collapse, but only fade back in ~150ms after the width transition starts on expand (avoids text reflow/wrap during the animation). - Hover states:
transition-colors duration-150–duration-200on rows, buttons, nav links. - Kanban drag: dragged card renders in a
DragOverlaywithrotate-1 shadow-xl; drop target column highlights viaborder-primary bg-primary/5.
UX Rules
- Numeric/tabular data is always monospace and right-aligned in tables.
- Every interactive control has a visible focus state (
focus:ring-1 focus:ring-ring focus:border-primarypattern) for keyboard accessibility. - Status is always communicated redundantly — color + text label — never color alone.
- Destructive actions use
destructivetokens, never ad-hoc red. - Empty states are muted, centered, and brief ("Nessun lead trovato").
- Sidebar collapse state persists across page loads via
localStorage(iamcavalli:sidebar) and is restored before paint where possible to avoid layout jump.
Component Inventory
Primitives extracted from the Lead Pipeline redesign, intended for reuse across all future admin pages.
| Component | Path | Purpose |
|---|---|---|
AdminShell |
src/components/admin/AdminShell.tsx |
Client shell holding sidebar-collapsed state (persisted to localStorage), renders sidebar + top header + <main>. Wraps all /admin/* pages. |
AdminSidebar |
src/components/admin/AdminSidebar.tsx |
Collapsible brand sidebar (w-64 ↔ w-20), controlled by AdminShell. Nav items, theme toggle, sign-out. |
StatusBadge |
src/components/ui/StatusBadge.tsx |
Rounded-full status pill with a color map per lead stage, each with a dark: variant. Replaces the old scattered STAGE_COLOR maps. |
SearchInput |
src/components/ui/SearchInput.tsx |
Search-icon input, rounded-lg border-border focus:ring-primary. |
SegmentedToggle |
src/components/ui/SegmentedToggle.tsx |
Generic Lista/Kanban-style segmented control (bg-muted p-1 rounded-lg, active option bg-card shadow-sm). |
PageHeader |
src/components/admin/PageHeader.tsx |
Page title (text-2xl tracking-tight) + subtitle + action slot, token-based. |
LeadTable |
src/components/admin/leads/LeadTable.tsx |
Luxury table restyle: bg-card rounded-xl shadow-card, uppercase muted headers, hover:bg-muted/40 rows, uses StatusBadge. |
LeadsKanbanBoard |
src/components/admin/leads/LeadsKanbanBoard.tsx |
Kanban restyle: bg-muted/60 columns with count pills, bg-card cards with hover:border-primary/30. Uses @dnd-kit, keeps all 6 lead stages. |
LeadsViewToggle |
src/components/admin/leads/LeadsViewToggle.tsx |
Wires SearchInput + SegmentedToggle together with the table/kanban views. |
MilestoneStepper |
src/components/client/MilestoneStepper.tsx |
Client-portal horizontal milestone stepper: one node per real phase, connecting line filled by global_progress_pct. Done = filled bg-primary ✓, active = border-primary outline, upcoming = bg-muted. |
Client Portal restyle notes
The /client/[token] portal was migrated to the same "Quiet Luxury" tokens
(applied 2026-07-10). Key conventions specific to the client-facing surface:
- Header:
bg-cardsticky bar, three zones —iamcavalli | Client Portal(left), brand name centered, "Area Riservata Protetta" emerald pill (right). - Status pills (phases, payments) use soft tints, not solid fills:
emerald-50/700 (done/saldato), amber-50/700 (in corso/da saldare),
bg-muted/text-muted-foreground(upcoming) — each with adark:variant. - Phase cards (
PhaseCard):rounded-xl border-border-light bg-card shadow-card, progress bar colored per status (emerald/amber/bg-border). - Sidebar cards (
OffersSection,PaymentStatus,DocumentsSection,NotesSection,TranscriptsSection) all followrounded-xl border-border-light bg-card shadow-card. ClientDashboardtakes anembeddedprop: when rendered inside the multi-project tabs wrapper (page.tsx), it drops its own header/footer/canvas so the page-level chrome isn't duplicated.