---
title: "Design Language"
canonical: "https://pharos.watch/docs/design-language/"
description: "Pharos design language reference for live UI patterns, typography, spacing, responsive behavior, visual hierarchy, and route-specific conventions."
---

# Design Language Reference (Live Baseline)

This document reflects the current UI baseline in the codebase and was re-verified on **March 24, 2026**.

Use this as the visual source of truth for product-facing design decisions. For token definitions (primitive, semantic, component), see [`design-tokens.md`](https://pharos.watch/docs/design-tokens/). The bridge layer lives in `src/app/globals.css`.

---

## Visual Direction

Pharos ships as a **light-default financial dashboard**:

- Dense data presentation
- Conservative card-and-table surfaces
- Small, meaningful color accents (risk, status, category)
- Heavy use of monospace for numeric trust and scanability

The default theme on load is light, with a user toggle for dark mode.

Light mode keeps the same hierarchy as dark mode, but status/accent text is calibrated one step darker to preserve readability on pale surfaces (typical pattern: `text-*-700 dark:text-*-400`).

### Typography carve-out

Newsreader serif is reserved for the Daily Digest editorial surfaces: the `/digest/**` route and the homepage `DailyDigest` preview card. The detail-page `AiSummary` component uses Georgia serif (`font-serif`) for its AI-authored narrative paragraph — this is a second intentional carve-out. The Cemetery obituary plaques (`cemetery-tombstones.tsx`) are a third Newsreader serif carve-out (Design Council B11), allow-listed in the design-invariants test alongside `AiSummary`. The `/timeline/` route is a further carve-out: Geist Mono dominates the wire-service event stream (see `### Tape (Special)` below). Every other dashboard panel on Pharos — including the homepage Market Snapshot, Core Monitoring band, Research Surfaces band, and all stablecoin-detail cards — uses Geist Sans at all weights. Do not introduce new serif usage outside the Digest, `AiSummary`, and Cemetery obituary carve-outs, and do not extend the Tape mono treatment to general analytics surfaces; a Vitest invariant in `src/lib/__tests__/design-invariants.test.ts` currently guards component-level drift under `src/components/**`, while route-level files still require manual review.

### Masthead tagline

The `SiteHeader` tagline reads `Every tracked stablecoin: backing, freeze risk, liquidity, and peg stress.` It is exposed from `md` upward (not `lg+`-only as before) as a single `whitespace-nowrap` line — there is no line-clamp. Mobile (`<md`) keeps the compact wordmark + stat-pill card.

### Hero signals rail (stablecoin detail)

On `lg+`, the detail hero's right column surfaces a four-pill `HeroSignalsRail` (Safety / Peg / Liquidity / DEWS) that quick-jumps to `#report-card` and `#liquidity`. It replaces the duplicated `SafetyGradeHero` block that used to sit opposite the Safety Score card. Mobile (`<lg`) continues to render `SafetyGradeHero` because the Safety Score card is far down scroll on narrow screens.

### Breadcrumbs

`src/components/breadcrumb.tsx` is the shared visual breadcrumb primitive for bespoke deep-route surfaces such as stablecoin detail. Most feature and taxonomy routes use `FeaturePageShell`, which renders its own `Dashboard / current page` breadcrumb and emits `BreadcrumbJsonLd` from either the default `breadcrumbName`/`path` pair or an explicit `breadcrumbItems` override for N-level routes. For new standard feature pages, prefer `FeaturePageShell`; for bespoke deep routes, use `Breadcrumb` directly or consolidate the two renderers first.

---

## Global App Shell

### Root + Fonts

- Body classes: `geist_*` font variables + `antialiased`
- Sans font: `Geist`
- Mono font: `Geist Mono`
- Default corner radius token: `--radius: .5rem`
- Body background adds two subtle radial glow layers via `--page-glow-top` and `--page-glow-bottom`

### Layout Structure

Public pages use this shell:

```tsx
<header className="lg:hidden sticky top-[3px] z-50 border-b border-border/80 bg-background" style={{ boxShadow: "var(--elevation-rest)" }} />
<div className="flex min-h-screen">
  <aside className="hidden lg:flex flex-col fixed top-[3px] left-0 h-[calc(100vh-3px)] border-r border-border/70 bg-card shadow-[0_0_0_1px_oklch(1_0_0_/0.03),0_20px_35px_oklch(0_0_0_/0.2)] z-[55] transition-all duration-200" />
  <div className="hidden lg:block shrink-0 transition-all duration-200 w-[var(--sidebar-width-expanded)]" />

  <div className="flex-1 flex flex-col min-w-0">
    <main id="main-content" className="pharos-mobile-utility-safe flex-1 container mx-auto px-4 py-6 md:py-7 lg:px-6">
      {/* route content */}
    </main>

    <footer className="border-t border-border/70 py-8 sm:py-10" />
  </div>
</div>
```

### Chrome Patterns

- Desktop sidebar widths: `--sidebar-width-expanded` and `--sidebar-width-collapsed`
- Mobile header height: `h-14`
- Mobile utility dock: fixed bottom-right dock on `<640px` with shared feedback + scroll-to-top placement; the dock stays hidden until the first scroll so it does not cover top-fold content
- Main content and footer reserve bottom safe space via `pharos-mobile-utility-safe` + `--mobile-utility-safe-offset`
- Main container padding:
  - Mobile: `px-4`
  - Vertical rhythm: `py-6` (`md:py-7`)
  - Desktop (`lg`): `px-6`
- Footer now prioritizes a short list of core routes, keeps category browsing secondary, drops duplicate tagline text beside socials, and lets intro/legal copy breathe across wider lines.
- The footer intro block is not width-capped inside its header row; on larger screens it expands to fill the available column beside the social icons.

---

## Page Shell Variants

### Standard Analytics Pages

Most routes use:

- Wrapper: `space-y-6`
- Title block: `space-y-2.5`
- Breadcrumb: `flex items-center gap-1.5 text-xs text-muted-foreground sm:text-sm`
- Title row outer layer: `flex max-w-full flex-wrap items-start justify-between gap-x-3 gap-y-3`
- Title row inner text/action layer: `flex max-w-4xl flex-wrap items-center gap-x-3 gap-y-2`

### Longform Pages

- Privacy: `mx-auto w-full space-y-6 max-w-2xl`
- Methodology: `mx-auto w-full max-w-[76rem] space-y-8`
- Digest archive: `mx-auto max-w-4xl`
- Digest detail shell: `mx-auto max-w-4xl`, with editorial body copy constrained to `max-w-[68ch]`

### Start Here (Special)

The `/start/` orientation route keeps the shared breadcrumb/title shell, then shifts into a broader planning-board layout:

Behavioral contract: Start Page

- Wrapper: `mx-auto max-w-6xl space-y-8`
- Hero shell: large rounded plotting-board surface with editorial onboarding copy on the left and a route board on the right
- Route board: uniform goal cards in `sm:grid-cols-2 xl:grid-cols-3`
- Mobile top fold compresses the hero copy so the first route card stays visible above the fold
- Desktop hero keeps the route board on the right while the left column stacks headline copy and `HeroEscapeHatch`
- Desktop support content under the headline stays in a clean vertical stack: CTA row first, then the experienced-user note
- Follow-up sections use glossary cards, flattened feature-atlas groups, and shortcut cards instead of one long prose stream

### Home Dashboard (Special)

Home keeps a single visible page `h1` owned by `SiteHeader`; the rest of the top fold is composed of:

Behavioral contract: Homepage

- Masthead strip: `pharos-card-shell flex flex-col gap-2 px-3 py-2 sm:gap-2.5 sm:px-4 sm:py-2.5 md:flex-row md:items-center md:justify-between md:gap-6 md:px-5 md:py-3` — stacked on mobile, side-by-side from `md` upward
- Core top rail: the live tape is mounted directly below the global PSI `RegimeBar` on core pages, with a horizontal nav strip underneath for Dashboard, Safety Scores, Depeg/DDR, Yield Intelligence, Alt-Pegs, FreezeWatch, Stability Index, PharosWatchBot, Learn, Timeline, and Status. On desktop, the combined tape + nav rail is sticky at `top-[3px]` with `z-50`, below the fixed PSI strip (`z-[60]`) and below the desktop sidebar (`z-[55]`), so both elements persist while scrolling without covering the sidebar search. On mobile, only the nav strip is sticky at `top-[3px]` (`z-[55]`); the events tape remains in normal flow and scrolls away. Each item renders its Lucide icon (`aria-hidden`); the active item's icon is lit `text-frost-blue`, inactive icons are `text-muted-foreground/80`. The active item is a self-contained frost-lit pill (`.pharos-rail-tab-active`) — the same frost wash + hairline inset ring + soft halo recipe as `.pharos-nav-active`, scaled for a small inline chip — with a `.pharos-nav-beam` sweep on activation and a faint frost-tinted ground (`.pharos-rail-ground`) below the tape. No left-edge accent stripe. The tape starts at the active sidebar width on desktop; the nav strip spans the full viewport and centers its run when it fits (`[justify-content:safe_center]`), with left and right edge gradient fades for overflow and the active pill scrolled into view on route change. While a core page is active, the sidebar suppresses duplicate core shortcuts and keeps Dashboard as the only core entry there.
- Page Discovery module: production-style five-link route board under the digest preview. It keeps the analytics `pharos-card-shell` treatment and the older product-callout tile rhythm, but the content is randomized from Core, Track, Analyze, and Monitor pages. A slim `Chart your route` header strip (frost-blue mono kicker + warm subline + route count) titles the board; below it sit one larger spotlight tile with the full route description and four compact tiles with one-line short descriptions, semantic accent blocks, and Lucide route icons. The spotlight kicker is a live-dot `Spotlight` accent chip; each compact route carries its nav-group accent as a category chip (text mixed toward `--foreground` for legibility). Hover/focus tints the tile toward its `--discovery-accent`, lifts the icon tile with a soft accent ring, and reveals a trailing `ArrowUpRight`. The five tiles fade-and-rise on mount via `pharos-stagger-entrance` (spotlight first, routes cascading), disabled under `prefers-reduced-motion`. No edge accent stripe.
- Snapshot shell: PSI-dominant first card + four supporting desktop KPI panels; mobile and tablet collapse to a 2x2 compact tile grid that includes net mint/burn flow
- Snapshot PSI lead card always renders the three compact delta pills (`24h`, `7d`, `30d`) beside the score/band lockup
- Digest preview: broadsheet split with a mono masthead, hairline `Executive Summary` label, newspaper-style `Newsreader` title on the left, and the lead paragraph plus CTA rail on the right at desktop

### Stablecoin Detail (Special)

Active detail pages keep one server-rendered semantic `h1` for crawlers and assistive tech, while the visible identity lives in the client hero:

- Server `h1`: `sr-only`
- Client HeroCard mobile `h2`: `text-2xl font-black tracking-tighter`
- Client HeroCard desktop `h2`: `text-3xl font-black tracking-tighter`
- Section and block titles across the detail route use `text-lg font-semibold tracking-tight`
- Detail metadata badges that qualify a section title (for example liquidity source coverage) sit inline with the title instead of dropping onto a separate row
- The `Contract Addresses` block shows a one-row, six-item preview on mobile with a `Show all` toggle; `sm+` continues to show the full icon grid
- `LongformScrollspyNav` renders as a sticky horizontal pill banner below `lg`; on `lg+`, the same section model moves into a sticky right-side rail with `variant="rail"`
- A single `Explore Next` hub at the end of the page, replacing the older stack of repeated research/compare/related link grids with one consolidated crawlable route cluster

This is intentionally denser than standard feature pages.

### Digest Article (Special)

Digest entries use a distinctive **"intelligence briefing"** editorial aesthetic that deliberately departs from the standard Geist-based UI:

- `h1`: Newsreader display face via `digestDisplay.className` with `text-[clamp(2.2rem,5vw,3.5rem)] font-semibold leading-[0.92] tracking-[-0.04em]`
- Executive summary card ahead of body copy
- Editorial prose constrained to `max-w-[68ch]`
- Homepage digest preview switches to a split desktop layout so the title block and italic executive-summary paragraph can use the full container width; dedicated digest pages keep the `max-w-[68ch]` editorial measure.

#### Editorial Typography System

The digest feature employs a dual-font hierarchy that evokes newspaper headlines over wire-service dispatches:

| Element | Font | Rationale |
|---------|------|-----------|
| **Headlines** | `font-serif` + route-local `Newsreader` usage where needed | Editorial authority — magazine headline gravitas |
| **Body copy** | `Courier New` italic | Raw urgency — telegrams, terminals, raw intel |
| **Metadata** | `Courier New` upright | Systematic precision — timestamps, edition numbers |

This pairing creates a "broadsheet newspaper" aesthetic that signals both authority and real-time urgency. It is one of three intentional non-Geist text treatments in Pharos, alongside the stablecoin-detail `AiSummary` Georgia serif paragraph and the `/timeline/` wire-service stream documented in `### Tape (Special)` below.

**Implementation**: Import styles from `@/lib/digest`:
- `EDITORIAL_BODY_STYLE` — Courier italic for prose
- `EDITORIAL_META_STYLE` — Courier upright for labels

### Tape (Special)

The `/timeline/` event stream uses a deliberate **wire-service / terminal aesthetic** that diverges from the standard `pharos-card-shell` analytics surface. Where Digest is the broadsheet, Timeline is the syslog: Geist Mono everywhere, hairline dividers in place of card chrome, severity expressed as text color, per-class background tints (hue signals class, text-color signals severity), and row time prefixes that use `HH:MM` on larger screens and compact relative tokens on mobile.

This is a third intentional non-Geist-Sans treatment alongside the Digest dual-font system (Newsreader serif + Courier italic) and the stablecoin-detail `AiSummary` Georgia paragraph. Tape is distinct from both: it leans on Geist Mono as the primary typeface across the stream, not serif for editorial gravitas.

The absence of `pharos-card-shell` on event rows, day groups, the currently-open band, pinned linked-event block, and the filter row is **intentional, not an oversight**. The filter row is a flat wire-control surface with hairline `border-y` dividers and shared control primitives, not a card shell.

The canonical contract — rules, structured row layout, day-separator format, and the Aesthetic Lock against harmonization — lives in tape-page.md under `## Visual Identity` and `## Aesthetic Lock`. Update both docs together when the wire-service treatment changes.

### Cemetery (Special)

The Stablecoin Cemetery (`/cemetery/`) employs a **unique memorial aesthetic** that is intentionally divergent from standard Pharos UI patterns:

- **Tombstone visualizations**: Custom SVG-based tombstones with varying shapes (arch, hammer, cross), sizes (by peak market cap), and weathering effects (by age)
- **Theme-aware memorial palette**: Uses bespoke stone/zinc accents and cause colors for the memorial atmosphere, while tombstone SVGs and cards still adapt through semantic CSS variables and light/dark Tailwind classes
- **Cause-of-death color system**: Algorithmic failure (red), counterparty failure (amber), liquidity drain (orange), regulatory (blue), abandoned (zinc)
- **Interactive memorial**: "Press F to pay respects" with persistent flower accumulation

This is a **one-off artistic treatment** — the patterns are not intended for reuse on other pages. The bespoke memorial colors and components serve the specific narrative of memorializing failed stablecoins.

---

## Typography

### Heading Scale

| Role                      | Live class pattern                                                                           |
| ------------------------- | -------------------------------------------------------------------------------------------- |
| Standard page title       | `min-w-0 text-3xl sm:text-4xl font-extrabold tracking-tight leading-[1.05]`                  |
| Shared page title utility | `pharos-page-title`                                                                           |
| Digest article title      | Newsreader via `digestDisplay.className`, `text-[clamp(2.2rem,5vw,3.5rem)]`, `font-semibold`, `leading-[0.92]`, `tracking-[-0.04em]` |
| Homepage digest hero      | `Newsreader`, `font-semibold`, `text-[clamp(2.8rem,6vw,5rem)]`, `leading-[0.88]`, `tracking-[-0.045em]` |
| Home logotype label       | `text-sm font-mono font-semibold uppercase tracking-[0.14em] md:text-[1.02rem] md:tracking-[0.16em]` |
| Primary section heading   | `leading-none font-semibold`                                                                 |
| Secondary section heading | `text-lg font-semibold` or `text-lg font-semibold tracking-tight`                            |
| Table/section kicker      | `text-[12px] sm:text-[11px] font-semibold uppercase tracking-[0.12em] text-muted-foreground` |
| Subsection heading        | `text-foreground font-medium`                                                                |

### Body + Supporting Text

| Role               | Live class pattern                             |
| ------------------ | ---------------------------------------------- |
| Standard body copy | `text-sm text-muted-foreground`                |
| Shared lead copy   | `pharos-lead`                                 |
| Small metadata     | `text-xs text-muted-foreground`                |
| Shared metadata    | `pharos-meta`                                 |
| Card micro-labels  | `text-xs uppercase tracking-wide`              |
| Footer legal group | `flex flex-wrap items-center gap-x-4 gap-y-2 text-xs text-muted-foreground` |

### Numeric Language

Numbers are consistently mono/tabular where precision matters:

- `font-mono`
- `tabular-nums`

---

## Spacing and Layout Rhythm

### Common Vertical Rhythm

- Section rhythm: `space-y-6`
- Header block rhythm: `space-y-2.5`
- Longform rhythm: `space-y-8`
- Card prose rhythm: `space-y-6 text-sm text-muted-foreground leading-relaxed`

### Common Grids

- KPI grid (dense analytics): `grid grid-cols-2 gap-3 sm:gap-5 lg:grid-cols-5`
- Home feature grid: `grid grid-cols-2 lg:grid-cols-3 gap-3 sm:gap-5`
- Home snapshot desktop partition: `hidden lg:grid grid-cols-[minmax(0,1.1fr)_repeat(4,minmax(0,0.92fr))] divide-x divide-border/30`

### Chip/Pill Layout

- Chips are frequently wrapped in `flex flex-wrap gap-2`
- Category links and peg links prioritize `rounded-full` micro-surfaces.

### Onboarding / Access Surfaces

First-run compare, portfolio, and gated status states now share a structured onboarding surface:

- Large rounded shell with dark gradient backdrop
- `pharos-kicker` eyebrow + one decisive title
- 3 step explainer cards
- CTA row using rounded-full buttons
- Preview panel shell on the right at desktop, stacked on mobile
- Optional footnote/support panel at the bottom of the text column

The dedicated `/start/` route extends the same language into a full-page onboarding pattern:

- large hero shell with route-selection cards instead of a single preview panel
- compact fact blocks embedded in the copy column
- glossary cards beneath the hero
- flattened feature-atlas groups plus shortcut cards for optional progressive discovery

---

## Shared Utility Classes

Live production now leans on a broader shared utility layer for finish-level consistency:

- `pharos-kicker`
- `pharos-focus-ring`
- `pharos-card-shell`
- `pharos-interactive-card`
- `pharos-page-title`
- `pharos-lead`
- `pharos-section-title`
- `pharos-meta`
- `pharos-control-pill` / `pharos-control-pill-active`
- `pharos-toggle-pill`
- `pharos-chart-stage`
- `pharos-chart-legend-chip`
- `pharos-table-shell`
- `pharos-table-toolbar`
- `pharos-table-sticky-primary` / `pharos-table-sticky-metric`
- `pharos-panel-header`
- `pharos-subtle-band`
- `pharos-empty-note`

Current high-use areas:

- Homepage snapshot and explore cards
- Peg filter pills
- CTA links with custom focus treatment
- chart legends, chart stages, and comparison controls
- stablecoin/comparison table wrappers and toolbars

---

## Contextual Explainability

Computed metrics now use a shared contextual-methodology pattern instead of relying only on page-level intros or `/methodology` as a separate destination.

Current pattern:

- compact help trigger attached directly to the metric label
- desktop behavior: rich tooltip with short definition + methodology links
- mobile behavior: bottom sheet with the same content
- score-card footers may add `View methodology` and `Version history` actions when the surface is interpretation-heavy

Use this on:

- composite scores (`Safety Score`, `Liquidity Score`, `PYS`, `DEWS`)
- baseline-relative or Pharos-native signals (`Pressure Shift vs 30D`, `Bank Run Gauge`)
- opaque sub-dimensions where the label alone is insufficient (`Resilience`, `Dependency Risk`)

Do not use this on every metric indiscriminately. The trigger is reserved for values where local interpretation meaningfully improves user decision-making.

---

## Cards

### Base Card Primitive

Default card composition in production:

- `data-slot="card"`
- `bg-card text-card-foreground flex flex-col gap-4 rounded-xl border py-4 shadow-sm`
- card surfaces now inherit a subtle shell gradient plus top-left highlight through the global token bridge instead of relying on flat fills alone
- `pharos-card-shell` is the promoted authored-surface wrapper for major route modules, tables, and feature cards

### Card Header + Title

- Header: `@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-1.5 px-4 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-4`
- Tight variants add `pb-1`, `pb-1.5`, or `pb-2`
- Titles: mostly `leading-none font-semibold`
- Shared route and chart surfaces increasingly use `pharos-panel-header` for a restrained header band instead of ad-hoc muted strips

### Accent Border Palette (Live)

The decorative per-card colored left stripe (`border-l-[3px] border-l-*-500`) has been **retired from analytics, discovery, and methodology surfaces** (May 2026 harmonization). KPI stat cards (`MetricStatCard` with no `borderColorClass`), feature/section cards (`/about`, `/funding`, `/methodology` and its changelogs), the `/status` sections, the `/liquidity`, `/depeg`, `/freezewatch` heroes, the `/cemetery` autopsy box, the digest snapshot cards, and the stablecoin-detail Yield Intelligence + blacklist cards now render as flat homepage-style cards (`rounded-xl border bg-card shadow-sm`, no colored edge, neutral `pharos-kicker` eyebrows).

`border-l-[3px]` (with a semantic color) remains reserved for **data-driven indicators**, not card chrome:

- depeg table row severity accents (`rowAccentClass` → `border-l-red-500` / `border-l-orange-500` / `border-l-amber-500`)
- stablecoin-detail hero metric accents (`accentClass` in `hero-card-metrics`)
- internal admin status sections (`StatusSection` still accepts an optional `accentClassName`)

The **desktop sidebar** navigation active state no longer uses a left stripe (June 2026 "watch column" pass). It is now a frost lit-tab — a frost wash falling from the icon side, a hairline frost inset ring, a soft halo, and a frost-lit icon — defined by `.pharos-nav-active` in `globals.css`. See `### Navigation Active vs Inactive` below. The **mobile drawer** (`header.tsx`) still uses `border-l-2 border-l-frost-blue` on the active route group as its own treatment.

### Interactive Card Pattern

`pharos-interactive-card` is the richer hover-lift utility used on the about-page feature grid. Homepage callouts currently stay on lighter `pharos-card-shell` variants without the extra interactive-card class. Following the May 2026 harmonization these surfaces no longer carry a colored left stripe.

```tsx
className =
  "pharos-card-shell pharos-focus-ring pharos-interactive-card group flex flex-col gap-2 bg-gradient-to-b from-background/40 to-transparent p-4";
```

### Logo Containers

Tracked token logos now render inside a shared neutral container:

- `rounded-full border border-border/60 bg-background/80`
- subtle inset highlight
- image shrunk slightly inside the wrapper so transparent/low-quality upstream assets do not collapse into the page background

---

## Badges and Chips

### Version Badge

Secondary version pill:

- `bg-background/35 text-muted-foreground border-border/60`

### Micro Chips

Common chip form:

- `inline-flex items-center rounded-full border bg-background px-2.5 py-1 text-xs font-medium hover:bg-accent transition-colors`

### Control Pills

The preferred finish-level control language is now the shared pill system:

- base: `pharos-control-pill`
- selected: `pharos-control-pill pharos-control-pill-active`
- used on time-range controls, density toggles, lens pills, and lightweight route context summaries
- pills should feel dense and precise, not marketing-chip playful

`pharos-control-pill` is the **canonical small-control shell** for any dense, secondary action surface — defined in `src/app/globals.css` line 490. Following the May 2026 detail-page pass, this includes the hero tertiary chips (chains pill, proof-of-reserves tier chip, freezable pill), per-section freshness stamps, and the longform scrollspy. New surfaces should reach for this utility before constructing ad-hoc rounded-full button shells.

### Proof-of-Reserves Attestor Tier Ladder

`POR_TIER_STYLES` in `shared/lib/classification/badges.ts` defines a 5-tier categorical color ladder used for the per-coin proof-of-reserves badge on detail pages. The ladder maps directly to `AttestorTier` from `shared/types/core.ts`:

| Tier       | Color            | Token classes                                                                                  | Meaning                                                          |
| ---------- | ---------------- | ---------------------------------------------------------------------------------------------- | ---------------------------------------------------------------- |
| `big4`     | emerald          | `bg-emerald-500/10 text-emerald-700 dark:text-emerald-400 border-emerald-500/30`               | Big-4 firm independent attestation                               |
| `regional` | blue             | `bg-blue-500/10 text-blue-700 dark:text-blue-400 border-blue-500/30`                           | Licensed regional CPA / auditor                                  |
| `niche`    | muted / neutral  | `bg-muted/40 text-muted-foreground border-border/60`                                           | Single-jurisdiction or small-practice attestor                   |
| `self`     | amber            | `bg-amber-500/10 text-amber-700 dark:text-amber-400 border-amber-500/30`                       | Issuer self-attestation, no third-party signoff                  |
| `none`     | red              | `bg-red-500/10 text-red-700 dark:text-red-400 border-red-500/30`                               | No attestation surface published                                 |

This is the canonical 5-tier categorical ladder for evidence-quality badges. Reuse `POR_TIER_STYLES` rather than redefining the palette inline. The ladder degrades cleanly to a 3-tier emerald / amber / red flatten for severity-style surfaces; do not introduce a competing "audit quality" palette.

### Freshness Stamps

`FreshnessIndicator` from `src/components/status/freshness-indicator.tsx` is the canonical "Updated X ago" affordance across the dashboard. It computes age client-side from a `updatedAtMs` prop, switches into a stale tone once `staleAfterMs` is exceeded, and pauses ticking while the document is hidden. As of the May 2026 detail-page pass it also renders inside the Safety Score card header on the stablecoin detail route, paired with the per-card `pharos-control-pill` chrome.

When adding a new freshness stamp:

- always pass `updatedAtMs` from the originating cache snapshot, not `Date.now()` at render
- match `staleAfterMs` to the producer cron interval (see CLAUDE.md hook timing rule)
- prefer the small inline form inside `CardHeader`; avoid stacking a new "last updated" line of body copy on the same surface

---

## Tables

### Base Table Styling

- Table: `w-full caption-bottom text-sm`
- Major table surfaces should prefer `pharos-table-shell` over a plain rounded border wrapper
- Toolbars should prefer `pharos-table-toolbar` with a brief explanatory line rather than a bare row of buttons
- Row: `hover:bg-muted/40 data-[state=selected]:bg-muted border-b transition-colors`
- Table rows now also take a subtle left-edge accent and a small horizontal nudge on hover; risk rows preserve their own semantic border color on hover

### Header Variants

- Standard header: `[&_tr]:border-b` with a theme-aware header band and token-driven shadow
- Sticky directory header (peg pages): sticky top header with restrained blur and `--table-header-shadow`
- Stablecoin detail history tables (depeg + mint/burn) use a rounded bordered shell (`rounded-xl border overflow-hidden`) with the muted header treatment and a footer row pairing mono range copy with outline `Previous` / `Next` controls

### Mobile Directory Table Handling

- Toolbar becomes a vertical stack on mobile instead of a cramped inline row
- `Columns` and `Export CSV` keep large tap targets on mobile; density and range controls also stay pill-based instead of collapsing into tiny tabs
- Density controls now include a true `List` mode for ticker-first scanning; in that mode the stablecoin table suppresses the expanded coin name and keeps only the ticker lockup
- Table keeps a deliberate horizontal-scroll affordance via helper copy and `min-w-[420px] xl:min-w-[820px]` (compact 420px floor below `xl`, 820px from `xl`); the mobile/desktop column boundary is `xl` (1280px)
- Bottom spacing is preserved so the mobile utility dock never sits on the last visible rows

### Sortable Head Pattern

Sortable heads consistently include:

- `cursor-pointer`
- `hover:bg-muted/50 transition-colors`

Numeric columns remain right-aligned (`text-right`) and collapse progressively by breakpoint (`hidden sm:table-cell`, `hidden md:table-cell`, etc.).

### Clickable Rows

Interactive rows use:

- `group cursor-pointer`
- `focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:outline-none`

---

## Charts

### Live Chart Container Pattern

- Height: `h-[250px] sm:h-[350px]`
- Recharts container keeps `min-width: 0; min-height: 0`
- Chart-heavy home modules now reserve height through matching skeletons or client-ready mount guards before `ResponsiveContainer` renders
- Premium chart framing now uses `pharos-chart-stage`: a dedicated bordered stage inside the card, rather than letting charts float directly on the card background
- Legends should prefer compact chips (`pharos-chart-legend-chip`) when the chart needs persistent series context outside the tooltip

### Axis + Grid (Observed)

From production rendered charts:

- Tick text: `font-size: 12`, `font-family: var(--font-mono, monospace)`, `fill: var(--color-muted-foreground)`
- X axis keeps extra breathing room through `tickMargin={10}`
- The shared `MonoYAxis` primitive defaults to `width={56}` and `tickMargin={8}` for cleaner number alignment
- Grid lines: `stroke="var(--color-border)"`, `strokeDasharray="2 6"`, verticals off by default

### Area Chart Styling (Observed)

- Areas use gradient fills (e.g. `fill="url(#psiScoreGradient)"`)
- Stroke widths are typically `1.5` or `2`
- Tooltips should use the shared elevated card treatment (`PharosChartTooltip`) with uppercase label treatment and mono values

### Loading Fallbacks

Common chart skeletons:

- `rounded-lg bg-muted/30 animate-pulse relative overflow-hidden h-[250px] sm:h-[350px] w-full`
- `bg-accent animate-pulse h-[250px] sm:h-[350px] w-full rounded-xl`
- Blacklist hero chart uses `h-[220px] sm:h-[280px]`
- Yield scatter plot uses `h-[420px]` (compact) or `h-[600px] sm:h-[850px]` (full) inside a bordered chart stage

---

## Interaction and State Patterns

### Navigation Active vs Inactive

The desktop sidebar (`src/components/sidebar.tsx`) frames navigation as a lighthouse "watch column": the active route reads as **lit by the beam** rather than flagged by a left stripe.

- Active sidebar item: `pharos-nav-active` — a frost wash falling from the icon side, a hairline frost inset ring + soft halo (no border stripe), `text-foreground`, and the Lucide icon lit `text-frost-blue`. The row also mounts a one-shot `pharos-nav-beam` light sweep on activation (gated on `prefers-reduced-motion: no-preference`).
- Inactive sidebar item: `text-muted-foreground hover:bg-muted/50 hover:text-foreground` (no left border at any state).
- When a live signal supplies an `accentClass` (the `/stability-index/` PSI band tint), the band background composes *beneath* `pharos-nav-active`, so an active item shows frost light on the icon side with the regime band persisting to the right.
- The brand lockup casts a thin frost shaft (`pharos-brand-beam`) along the header divider; the Search row is a bordered inset command field, not a nav link, and brightens its border toward frost on hover.

The **core top rail** (`src/components/core-top-rail.tsx`) is the horizontal echo of the watch column. Each destination is a `.pharos-rail-tab` ghost chip; the active item gains `.pharos-rail-tab-active`, which reuses the exact frost `color-mix` recipe from `.pharos-nav-active` — frost wash + hairline frost inset ring + soft halo — scaled for a small inline pill rather than a full-width sidebar row. The active pill also mounts the `.pharos-nav-beam` one-shot frost sweep on activation (reduced-motion gated), and the nav bar sits on a very faint frost-tinted `.pharos-rail-ground` to distinguish it visually from the neutral-card live ticker tape directly above it. There is no left-edge accent stripe, consistent with the May 2026 harmonization and the June 2026 watch-column pass.

The mobile drawer (`header.tsx`) keeps its own active treatment (`border-border/70 bg-muted/60` links, `border-l-2 border-l-frost-blue` group accents) and was intentionally left unchanged in this pass.

### Focus Treatment

Two dominant focus patterns:

- `focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none`
- `focus-visible:ring-[3px] focus-visible:ring-ring/50`

### Loading States

- Skeletons are the default loading surface (`data-slot="skeleton"` + `animate-pulse`)
- Page-level loader currently appears as:
  - `flex min-h-[40vh] items-center justify-center`
  - `h-10 w-10 rounded-full bg-frost-blue/30 animate-pharos-pulse`

### Live/Event Indicator

Depeg live indicator uses animated ping:

- `animate-ping absolute inline-flex h-full w-full rounded-full bg-red-400 opacity-75`

### Data Availability Banner

When data streams are missing:

- `rounded-md border px-4 py-2.5 text-sm border-border/60 bg-muted/40 text-muted-foreground`

The current pattern is a titled trust banner with dataset-specific copy, for example:

- `Waiting for initial data`
- `Affected: report cards.`
- `Last successful update: Mar 24, 1:01 PM GMT+1.`

---

## Responsive Behavior

### Breakpoint Behavior in Production

- `sm`:
  - Compacts/expands table columns
  - Converts details/nav patterns
- `md`:
  - Masthead tagline becomes visible as a single `whitespace-nowrap` line.
  - Snapshot KPI grid expands; other dense-data grids transition between mobile and desktop layouts
- `lg`:
  - Sidebar becomes active (`lg:flex`); mobile header / drawer hides (`lg:hidden`)
  - Tablet portrait (sub-`lg`) intentionally falls back to the mobile drawer because the desktop nav has too many groups to remain legible at that width
  - Main horizontal padding increases (`lg:px-6`)
  - Larger grid splits and extra table columns
- `xl`:
  - Additional dense table columns
  - Home KPI grid keeps the wide five-panel snapshot module intact

### Mobile-Specific UX

- Category browse collapses into `details` (`sm:hidden`)
- `--table-header-top: 56px` is set on mobile for sticky header offset alignment
- Bottom utility controls are consolidated into one dock on mobile instead of separate floating widgets

---

## Accessibility Baseline

Live app-wide patterns:

- Skip link present on every page: `sr-only focus:not-sr-only ...`
- Breadcrumb navigation on content routes
- Focus-visible rings on sidebar links, buttons, table rows, and chips
- Keyboard-ready clickable rows on interactive tables
- Color is reinforced with structure and iconography for key status states

---

## Draw the Metaphor

When a page introduces a metaphor, render it — don't just name it. The Stablecoin Cemetery draws actual tombstones with arched caps, crosses, and flower scatter. The Alt-Peg Atlas draws a starfield with celestial bands and constellation cohorts. The Chains Harbor Chart draws ships with flags, cargo, wakes, and depth lines. On `/depeg/`, the Depeg Duration Resolver (DDR) draws each open event as a forecast timeline — the deviation path *so far* (peak → now spark), the verdict at a pulsing **NOW** marker, and the projected resolution band (median diamond + IQR over the 6h/24h/7d/30d landmark axis) reaching into the future — while its Reviewer (DDRR) draws a track-record timeline where every graded past call seats above the rail (correct) or below it (miss), so accuracy reads at a glance. The kill-vs-anchor tug-of-war and the DDRR calibration ledger remain beneath these as the "why" and the honesty check.

Rules that keep this from drifting into decoration:

- **Every shape encodes a data field.** No ornamental geometry. If a shape doesn't vary with a number, remove it.
- **Inline JSX SVG, semantic CSS variables, hex fallbacks.** No external `.svg` assets, no runtime SVG libraries.
- **CSS keyframes only, gated on `@media (prefers-reduced-motion: no-preference)`.** No framer-motion.
- **Mobile preserves the metaphor,** not feature removal. Visualization canvases should fit their container without page-level horizontal scrolling; use simplified lists only when the visual cannot remain legible.
- **The underlying data table remains.** The metaphor is a hero. The table is the workbench.

---

## Maintenance Rule

If a deployed class pattern changes in production, update this document immediately after release. This file is intended to describe what users currently see, not aspirational or historical styles.
