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.
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-topand--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-expandedand--sidebar-width-collapsed - Mobile header height:
h-14 - Mobile utility dock: fixed bottom-right dock on
<640pxwith 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
- Mobile:
- 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 tomax-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 frommdupward - Core top rail: the live tape is mounted directly below the global PSI
RegimeBaron 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 attop-[3px]withz-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 attop-[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 littext-frost-blue, inactive icons aretext-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-beamsweep 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-shelltreatment and the older product-callout tile rhythm, but the content is randomized from Core, Track, Analyze, and Monitor pages. A slimChart your routeheader 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-dotSpotlightaccent chip; each compact route carries its nav-group accent as a category chip (text mixed toward--foregroundfor legibility). Hover/focus tints the tile toward its--discovery-accent, lifts the icon tile with a soft accent ring, and reveals a trailingArrowUpRight. The five tiles fade-and-rise on mount viapharos-stagger-entrance(spotlight first, routes cascading), disabled underprefers-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 Summarylabel, newspaper-styleNewsreadertitle 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 Addressesblock shows a one-row, six-item preview on mobile with aShow alltoggle;sm+continues to show the full icon grid LongformScrollspyNavrenders as a sticky horizontal pill banner belowlg; onlg+, the same section model moves into a sticky right-side rail withvariant="rail"- A single
Explore Nexthub 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 viadigestDisplay.classNamewithtext-[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 proseEDITORIAL_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-monotabular-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-fullmicro-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-kickereyebrow + 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-kickerpharos-focus-ringpharos-card-shellpharos-interactive-cardpharos-page-titlepharos-leadpharos-section-titlepharos-metapharos-control-pill/pharos-control-pill-activepharos-toggle-pillpharos-chart-stagepharos-chart-legend-chippharos-table-shellpharos-table-toolbarpharos-table-sticky-primary/pharos-table-sticky-metricpharos-panel-headerpharos-subtle-bandpharos-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 methodologyandVersion historyactions 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-shellis 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, orpb-2 - Titles: mostly
leading-none font-semibold - Shared route and chart surfaces increasingly use
pharos-panel-headerfor 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 (
accentClassinhero-card-metrics) - internal admin status sections (
StatusSectionstill accepts an optionalaccentClassName)
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:
| 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
updatedAtMsfrom the originating cache snapshot, notDate.now()at render - match
staleAfterMsto 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-shellover a plain rounded border wrapper - Toolbars should prefer
pharos-table-toolbarwith 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-bwith 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 outlinePrevious/Nextcontrols
Mobile Directory Table Handling
- Toolbar becomes a vertical stack on mobile instead of a cramped inline row
ColumnsandExport CSVkeep 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
Listmode 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 belowxl, 820px fromxl); the mobile/desktop column boundary isxl(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-pointerhover: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-pointerfocus-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
ResponsiveContainerrenders - 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
MonoYAxisprimitive defaults towidth={56}andtickMargin={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.5or2 - 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-fullbg-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) orh-[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 littext-frost-blue. The row also mounts a one-shotpharos-nav-beamlight sweep on activation (gated onprefers-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 beneathpharos-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-nonefocus-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-centerh-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 dataAffected: 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-nowrapline. - Snapshot KPI grid expands; other dense-data grids transition between mobile and desktop layouts
- Masthead tagline becomes visible as a single
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
- Sidebar becomes active (
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: 56pxis 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
.svgassets, 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.