# 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-6` in tables, `p-4` in
  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-mono` and 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/google`
  as `--font-plus-jakarta-sans`, mapped to Tailwind's `--font-sans` in
  `@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`

## 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 `@theme` as `--radius-lg` (`=
  --radius`) and `--radius-xl` (`= --radius + 0.25rem`), so `rounded-lg` /
  `rounded-xl` utilities resolve consistently off one token.
- `--shadow-card: 0 4px 20px rgba(0,0,0,0.01)` → `shadow-card` utility.
  Default resting elevation for cards, table containers, kanban cards.
- `--shadow-card-hover: 0 8px 30px rgba(0,0,0,0.02)` → `shadow-card-hover`
  utility. 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`, subtitle `text-xs text-muted-foreground mt-1`, primary
  action button `bg-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 use
  `whitespace-nowrap transition-opacity duration-200` and fade to
  `opacity-0` immediately 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-200` on rows,
  buttons, nav links.
- **Kanban drag**: dragged card renders in a `DragOverlay` with
  `rotate-1 shadow-xl`; drop target column highlights via `border-primary
  bg-primary/5`.

## UX Rules

1. Numeric/tabular data is always monospace and right-aligned in tables.
2. Every interactive control has a visible focus state (`focus:ring-1
   focus:ring-ring focus:border-primary` pattern) for keyboard accessibility.
3. Status is always communicated redundantly — color + text label — never
   color alone.
4. Destructive actions use `destructive` tokens, never ad-hoc red.
5. Empty states are muted, centered, and brief ("Nessun lead trovato").
6. 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`. |
| `LeadDetail` | `src/components/admin/leads/LeadDetail.tsx` | Lead detail page (`/admin/pipeline/[id]`): bespoke header (name + inline `StatusBadge` + action bar), asymmetric `lg:grid-cols-3` layout (1/3 profile card w/ icon-chip rows + tags, 2/3 Note/Follow-up + unified timeline). |
| `ClientProfitability` | `src/components/admin/dashboard/ClientProfitability.tsx` | Dashboard widget: valore orario reale per cliente (`getClientProfitability` → contrattualizzato ÷ ore tracciate) con badge margine (Ottimo `bg-primary/10 text-primary` / In Linea `bg-muted`). |
| `YearSelector` | `src/components/admin/YearSelector.tsx` | Selettore anno restilizzato a pill (`rounded-full border-border bg-card shadow-card`), frecce ←/→, valore `font-mono`. Guida i dati year-scoped della dashboard via `?year=`. |
| `ConversationsView` | `src/components/admin/conversazioni/ConversationsView.tsx` | Inbox messaggi clienti (`/admin/conversazioni`) a due pannelli: lista conversazioni filtrabile (search su nome/brand/ultimo messaggio, pallino unread) + thread attivo con bolle messaggio (admin a destra `bg-primary`, cliente a sinistra), badge entità per messaggio (Generale/Fase/Task/Deliverable) e form di risposta. Auto-mark-read all'apertura. |
| `MessagesWidget` | `src/components/admin/dashboard/MessagesWidget.tsx` | Widget dashboard "Messaggi Clienti": chat clienti in attesa di risposta (`getConversations()` filtrato su `unread`), pill emerald "N Nuovi" + pallino pulsante, righe con anteprima e link `Rispondi →` deep-link a `/admin/conversazioni?c=<clientId>`. Empty state "Nessun messaggio in attesa ✓". |
| `CopyLinkButton` | `src/components/admin/CopyLinkButton.tsx` | Icon button che copia un URL assoluto (`window.location.origin + path`) negli appunti; swap icona → check emerald per 1.5s con tooltip "Copiato!". Usato nella lista Clienti per il link profilo pubblico. |
| `ClientRow` | `src/components/admin/ClientRow.tsx` | Riga tabella Clienti restilizzata Quiet Luxury: nome link → dettaglio + sottotitolo brand/progetti, colonne numeriche `font-mono` right-aligned (LTV, incassato come pill emerald, da saldare), cella "Link profilo" con pill mono troncata (apre portale in new tab) + `CopyLinkButton`. |

**Unified timeline pattern**: activities + transcripts merged into one
reverse-chronological "Storico delle Interazioni". Vertical connector = absolute
`w-px bg-border` at `left-[19px]` behind `w-10 h-10 rounded-full` nodes.
Transcript node is highlighted (`bg-primary text-primary-foreground`) and
expandable (`line-clamp-2` → "Mostra tutto/meno"); activity nodes are muted
(`bg-muted border-border`). Dates are `font-mono` — activities relative
(`formatDistanceToNow`), transcripts absolute (`d MMMM yyyy`).

### Admin Dashboard restyle notes

The `/admin` dashboard was migrated to "Quiet Luxury" (applied 2026-07-11),
replicating `design-reference/pagina-dashboard`. It is a condensed single-screen
layout: no in-`<main>` `PageHeader` (sidebar + shell header carry the chrome).

- **KPI strip**: 4 `bg-card` metric cards (Clienti Attivi · Contrattualizzato ·
  Incassato Reale · Ore Tracciate). Value `text-2xl font-bold font-mono`, label
  `text-[10px] uppercase text-muted-foreground`. Positive delta uses
  `text-emerald-600 dark:text-emerald-400`.
- **Griglia `lg:grid-cols-3`**: 2/3 col = `ForecastChart` (istogramma flussi 12
  mesi, mese prossimo evidenziato `bg-primary`) + `ClientProfitability`; 1/3 col =
  `FollowUpWidget` (count pill amber) + `MessagesWidget` ("Messaggi Clienti", pill
  emerald "N Nuovi" + pallino pulsante, chat clienti in attesa di risposta) +
  `OffersSoldChart` ("Offerte Più Richieste").
- **Year selector** interattivo (`YearSelector`) in una toolbar top-right; governa
  i KPI year-scoped + redditività. Il forecast è rolling-12-mesi (non year-scoped).
- Widget rimossi dal legacy: chart incassi mensili, card "Da incassare"/"Clienti
  acquisiti", barre "Ore per cliente" (sostituite dalla redditività oraria).

### Conversazioni (inbox) notes

`/admin/conversazioni` è l'inbox unificata dei messaggi clienti (replica
`design-reference/pagina-conversazioni`). Ogni conversazione è **per cliente**:
raccoglie tutti i suoi commenti — `general` + quelli su fasi/task/deliverable —
in un unico thread ordinato, con badge entità per messaggio.

- **Fonte unread**: `clients.admin_last_read_at` confrontato con l'ultimo
  messaggio autore `client`. Una conversazione è non letta se esiste un messaggio
  cliente dopo `admin_last_read_at` (o se è `NULL`). Query in
  `src/lib/conversations-queries.ts` (`getConversations`, `getConversationThread`,
  `getUnreadConversationsCount`).
- **Badge sidebar**: voce "Conversazioni" (`AdminSidebar`) con pill emerald del
  numero di clienti con non letti (pallino verde se collapsed), alimentato da
  `getUnreadConversationsCount()` in `src/app/admin/layout.tsx`. Stessa fonte di
  verità del `MessagesWidget` in dashboard.
- **Reply**: salvata come commento `general` sul cliente e aggiorna
  `admin_last_read_at` (mark-read). L'apertura di una conversazione fa
  auto-mark-read. Deep-link a un thread specifico via `?c=<clientId>`.

### Clienti (lista) notes

`/admin/clients` migrata a "Quiet Luxury" (replica `design-reference/pagina-clienti`).
Tabella `bg-card rounded-xl border-border shadow-card` con header uppercase muted e
righe `py-4 px-6` / `divide-y divide-border` / `hover:bg-muted/40`.

- **Colonne**: Cliente (nome link → `/admin/clients/[id]` + sottotitolo
  brand/progetti) a sinistra; LTV, Importo incassato, Importo da saldare, Link
  profilo tutte right-aligned. Valori monetari `font-mono`; "incassato" reso come
  pill emerald (`bg-emerald-50 text-emerald-700` + variante dark), zero → `—` muted.
- **Link profilo**: pill mono troncata (`/client/<slug|token>…`) che apre il portale
  in new tab + `CopyLinkButton` (copia URL assoluto).
- **Header actions**: link testuale "Mostra/Nascondi archiviati" (`?archived=1`) +
  Button primario "Nuovo cliente". Footer tabella "Mostrando N clienti attivi"
  (singolare/plurale gestiti).
- Le righe archiviate restano `opacity-60` con pill "Archiviato".

### Offerte (lista) notes

`/admin/offers` migrata a "Quiet Luxury" (replica `design-reference/pagina-offerte`).
Griglia responsive di card `bg-card rounded-xl border-border p-6 shadow-card`
(`hover:shadow-card-hover`), `md:grid-cols-2 lg:grid-cols-3`.

- **Header**: `PageHeader` "Offerte" + sottotitolo, azione "+ Nuova Offerta"
  (`Button` primario) che espande un form inline (`Input` nome + "Crea").
- **Filtri**: pill categoria rettangolari (`rounded-lg px-4 py-2`) — attiva
  `bg-primary text-primary-foreground`, inattiva `border-border bg-card
  text-muted-foreground hover:bg-muted`. "Tutti" + `categoryOptions` dinamiche.
  A destra checkbox "Mostra offerte archiviate".
- **Card**: nome interno + descrizione muted + badge categoria colorato per tipo
  (Retainer→blue, Entry→purple, Signature→amber, tutti con variante dark) via
  helper `categoryBadgeClass`. Le archiviate sono `opacity-60` con pill "Archiviata".
- **Tabella tier**: `border-border rounded-lg`, header uppercase muted
  (Tier · Servizi · Pubblico), righe `font-mono` `divide-y divide-border`; solo i
  tier con `servicesTotal > 0` o `public_price` valorizzato; "Pubblico" assente → `—`.

### Catalogo (griglia servizi) notes

`/admin/catalog` migrata a "Quiet Luxury" (replica `design-reference/pagina-catalogo`).
È una griglia editabile in stile foglio di calcolo: `ServiceTable` in un contenitore
`bg-card rounded-xl border-border shadow-card overflow-hidden`, header uppercase muted
(`text-[11px] font-semibold uppercase tracking-wider text-muted-foreground`, `bg-muted/50`),
righe `hover:bg-muted/40` / `divide` via `border-border`.

- **Celle inline**: nome/descrizione/prezzo/stato usano `EditableCell`; fase usa
  `OptionSelect`; offerta/pacchetto usano `OptionMultiSelect`. I badge fase/offerta/
  pacchetto sono colorati deterministicamente via `getOptionColor` (hash → palette a 7
  colori) — nessun colore semantico fisso, coerente col sistema tag del resto dell'app.
- **Colonne**: riordinabili via drag (`GripVertical` on hover) e ordinabili sulle colonne
  `sortable` (`ArrowUp/Down` in `text-primary`); ordine persistito in `localStorage`
  (`catalog_col_order`).
- **Quick-add**: ultima riga `bg-muted/40` con input inline (`+ Aggiungi servizio`, prezzo,
  fase datalist) — Invio ↵ crea il servizio. Servizi disattivati raggruppati sotto un header
  `bg-muted/40` e resi `opacity-50`.
- **Search**: `SearchInput` primitivo (non più input+icona inline) su nome/categoria/fase/
  tag/pacchetto. Footer tabella "Visualizzazione di N servizi" (singolare/plurale).
- **Nota primitivi**: in questa pass `OptionSelect` ed `EditableCell` (`accent-primary`)
  sono stati tokenizzati — erano rimasti con hex hardcoded mentre `OptionMultiSelect` era
  già token-based. Il fix vale per tutte le pagine che li usano (Offerte, Lead).

### Preventivi (lista) notes

`/admin/preventivi` migrata a "Quiet Luxury" (replica `design-reference/pagina-preventivi`).
Tabella in contenitore `bg-card rounded-xl border-border shadow-card overflow-hidden`, header
uppercase muted (`bg-muted/50`, `text-[11px] font-semibold uppercase tracking-wider`), righe
`hover:bg-muted/40` con `divide-y divide-border`.

- **Header**: `PageHeader` "Preventivi" + sottotitolo, azione `Button` "Genera preventivo"
  (`+`, link a `/admin/preventivi/genera`).
- **Colonne**: Titolo (link → dettaglio, `font-semibold hover:text-primary`), Lead/Cliente,
  Offerta (muted), Stato (badge centrato), Data (`font-mono text-xs`), Azione.
- **Badge stato**: pill rounded-full `text-[10px] uppercase border` con dual light/dark —
  Bozza→amber, Pubblicato→sky, Accettato→emerald, Rifiutato→red (stesso linguaggio di
  `StatusBadge`; label ITA mappate localmente perché gli stati proposal ≠ lead stages).
- **Azione**: solo per `published`/`accepted` link "Apri →" (`ArrowRight` con group-hover
  translate) al preventivo pubblico `/preventivo/<slug>` in new tab; altrimenti `—` muted.
- **Empty state**: `border-dashed border-border rounded-xl`, `FileText` muted + CTA
  "Genera il primo preventivo →". Footer "Mostrando N di N preventivi generati".

### Progetti (lista) notes

`/admin/projects` migrata a "Quiet Luxury" (replica `design-reference/pagina-progetti`).
Stesso contenitore tabella di Preventivi (`bg-card rounded-xl border-border shadow-card`,
header `bg-muted/50` uppercase muted, righe `hover:bg-muted/40` + `divide-y divide-border`).

- **Header**: `PageHeader` "Progetti" + sottotitolo (redditività oraria), azione `Button`
  "Nuovo Progetto" (`+`, link a `/admin/projects/new`).
- **Colonne**: Progetto (link → dettaglio + cliente muted, badge "Archiviato" se archiviato),
  Valore Totale (`text-right font-mono`), Acconto (badge centrato), Saldo (badge centrato),
  Timer (`TimerCell` centrato), Redditività €/H (`text-right`).
- **Badge pagamento** (`ProjectRow`): pill rounded-full `text-[10px] uppercase border` dual
  light/dark — saldato→emerald, da_saldare→rose, inviata→amber; prefissati "Acconto:"/"Saldo:".
  Pagamento assente → `—` muted.
- **TimerCell**: ora una pill `rounded-full border font-mono` — idle neutro (`bg-muted`,
  play triangle su `bg-foreground/10`), running emerald (`bg-emerald-50/60` dark
  `emerald-950/30`, pausa su `bg-emerald-500`), contatore live. Stessa pill riusata nel
  `TimerTab` del dettaglio progetto.
- **Redditività**: `null` (0h tracciate) → `—` muted; `>0` → emerald bold con dot
  `bg-emerald-500`; `=0` → mono muted. Footer "Mostrando N di N progetti attivi".

### Impostazioni notes

`/admin/impostazioni` migrata a "Quiet Luxury" (replica `design-reference/pagina-impostazioni`).
Pagina a sezioni impilate (`space-y-8`), ogni sezione è una card
`rounded-xl border-border-light bg-card shadow-card p-6`.

- **Header**: `PageHeader` "Impostazioni" + sottotitolo ("Configura le tariffe target e gestisci
  le tassonomie globali del sistema").
- **Titoli sezione**: `text-xs font-bold uppercase tracking-wider text-foreground`, sottotitolo
  `text-xs text-tertiary`. Le sezioni tassonomia hanno un'icona (`Tag`/`BookOpen`) in un box
  `h-9 w-9 rounded-lg border-border-light bg-muted text-muted-foreground`.
- **Analytics Profittabilità**: form server-action con input `type=number` in un gruppo bordato
  `focus-within:border-primary focus-within:ring-1 ring-primary`, prefisso `€`/suffisso `/h`
  muted assoluti, valore `font-mono`; bottone "Salva" `bg-primary text-primary-foreground`.
- **Griglie tassonomia** (`TaxonomyManager`): `border-t border-border-light pt-4` + grid
  `gap-6` — Offerte `md:grid-cols-2`, Catalogo `md:grid-cols-2 lg:grid-cols-3`.
- **PoolManager** (box campo): `rounded-xl border-border-light bg-muted/40 p-5 flex flex-col
  justify-between`. Testata = label uppercase muted + badge conteggio mono
  (`bg-card border-border-light text-tertiary`, sempre visibile anche a 0). Valori = chip
  `bg-card border-border/60 text-foreground shadow-sm` con `X` (hover destructive → cascade
  delete confermata via `window.confirm`). Vuoto → "Nessun valore inserito" italic muted.
  Aggiunta = input full-width `bg-card border-border` con Enter (no bottone `+`); duplicati
  bloccati con errore inline `text-destructive`.
- **Footer**: nota `text-xs text-tertiary` sulla sincronizzazione bidirezionale dei valori.

### 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-card` sticky 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 a `dark:` 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 follow `rounded-xl border-border-light
  bg-card shadow-card`.
- `ClientDashboard` takes an `embedded` prop: 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.
