Skip to main contentSkip to data table
Pharos
PHAROSlive stablecoin signals

Design Language

Pharos design language reference for live UI patterns, typography, spacing, responsive behavior, visual hierarchy, and route-specific conventions.

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. 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.

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:

<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:

ElementFontRationale
Headlinesfont-serif + route-local Newsreader usage where neededEditorial authority — magazine headline gravitas
Body copyCourier New italicRaw urgency — telegrams, terminals, raw intel
MetadataCourier New uprightSystematic 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

RoleLive class pattern
Standard page titlemin-w-0 text-3xl sm:text-4xl font-extrabold tracking-tight leading-[1.05]
Shared page title utilitypharos-page-title
Digest article titleNewsreader via digestDisplay.className, text-[clamp(2.2rem,5vw,3.5rem)], font-semibold, leading-[0.92], tracking-[-0.04em]
Homepage digest heroNewsreader, font-semibold, text-[clamp(2.8rem,6vw,5rem)], leading-[0.88], tracking-[-0.045em]
Home logotype labeltext-sm font-mono font-semibold uppercase tracking-[0.14em] md:text-[1.02rem] md:tracking-[0.16em]
Primary section headingleading-none font-semibold
Secondary section headingtext-lg font-semibold or text-lg font-semibold tracking-tight
Table/section kickertext-[12px] sm:text-[11px] font-semibold uppercase tracking-[0.12em] text-muted-foreground
Subsection headingtext-foreground font-medium

Body + Supporting Text

RoleLive class pattern
Standard body copytext-sm text-muted-foreground
Shared lead copypharos-lead
Small metadatatext-xs text-muted-foreground
Shared metadatapharos-meta
Card micro-labelstext-xs uppercase tracking-wide
Footer legal groupflex 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 (rowAccentClassborder-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.

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:

TierColorToken classesMeaning
big4emeraldbg-emerald-500/10 text-emerald-700 dark:text-emerald-400 border-emerald-500/30Big-4 firm independent attestation
regionalbluebg-blue-500/10 text-blue-700 dark:text-blue-400 border-blue-500/30Licensed regional CPA / auditor
nichemuted / neutralbg-muted/40 text-muted-foreground border-border/60Single-jurisdiction or small-practice attestor
selfamberbg-amber-500/10 text-amber-700 dark:text-amber-400 border-amber-500/30Issuer self-attestation, no third-party signoff
noneredbg-red-500/10 text-red-700 dark:text-red-400 border-red-500/30No 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

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.