Layer 3 · tokens/app/ · dist/css/app.css

App surface

The product, on the round-12 rules: one typeface with hierarchy by weight, a 16px root on a true 4px grid, bordered 12px cards on a quiet page, a dark default and a light theme, and every data-semantic hue. Toggle the theme and every readout re-resolves. This page loads no webfont — the demo self-hosts Inter as --font-inter, which is unset here, so --font-display falls through its stack to the system sans.

01 · Themes

Dark by default, light by attribute

The dark theme is declared on :root and repeated for [data-theme="dark"], so that attribute on any element pins its subtree to dark whatever the page shows. The light theme is :root[data-theme="light"]: the same custom properties, redefined — never a second copy of the app. Only the surface, text and border scaffolding flips; the accent and every data-semantic hue read identically in both.

Follows the page
--bg-page—--surface-panel—--text-strong—

Toggle the theme (top right) and these change. The choice is kept in local storage, as the demo's ThemeContext keeps it.

Pinned · data-theme="dark"

This card is pinned to the dark theme whatever the page shows — /design's Final section pins two cards this way. Inside it --surface-panel is —.

Native controls follow

Each theme block sets color-scheme, and select, option inherit it, so dropdowns and date inputs render in the page's scheme instead of forcing dark.

02 · Brand

Wordmark, accent family, type

The one thing that is exact and never substituted: --blue-500. Everything else sits quietly around it.

Wordmark and mark · components/Wordmark.tsx · app/icon.svg

The full word, both leading S's in the accent, and an accent asterisk floating above each i (drawn over a dotless ı) — the one deliberate glyph-level brand mark (founder logo 2026-07-21). Flat accent colour, no gradient, no drop shadow. The compact mark is the eight-spoke asterisk from the favicon, for the collapsed left nav; it draws in currentColor so a parent can retint it.

Electric cobalt family
Blue 300--blue-300—
Blue 400--blue-400—= accent-bright
Blue 500 — THE accent--blue-500—Exact hex, never substituted.
Blue 600--blue-600—= accent-hover
Blue 700--blue-700—
accent--accent—
accent-hover--accent-hover—
accent-bright--accent-bright—
accent-soft--accent-soft—
accent-tint--accent-tint—rgba fill, e.g. the Active badge.
accent-tint-strong--accent-tint-strong—The leverage picker's selected band.
Typography · one typeface, three classes

One typeface — Inter — carries every word AND every number. Numerals get Inter's tabular figures via font-num, so prices and countdowns align in a column without a second webfont. A real monospace survives only for literal code (font-code) — raw wire frames and the token names on this page. font-mono still works and resolves to the same stack; it is a legacy alias and no longer means monospace.

displayInter — Settlement Calendar
numInter tabular — $108,420.00
code{ strike: 4280 }
Display--font-display—
Num (tabular figures via .font-num)--font-num—
Mono (legacy alias)--font-mono—
Code--font-code—
03 · Surfaces and tones

Hierarchy by tone, with a hairline edge

Every background below is one step lighter than the last in dark mode. Tone still does most of the separating; the difference from round 3's "border massacre" is that a card now also carries --border to define its edge. Light mode inverts the stack: the page is the tinted step and cards are pure white, so a card still reads as the raised object.

Background tone stack
Page--bg-page—Base the whole app sits on — the darkest step in dark theme, the lightest in light.
Panel--surface-panel—Cards, modals, the calendar's own Card wrapper.
Row--surface-row—Table / list zebra tone, one step up from panel.
Hover--surface-hover—Default interactive hover; the topic-row-driven day highlight.
Hover (light)--surface-hover-light—A directly hovered day cell — one step lighter than hover.
Raised--surface-raised—Popovers and modals float above everything else.
Inset--surface-inset—Recessed wells, e.g. the sample strips inside a palette card.

Round 12 removed the Swiss-grid overlay that used to sit on the page tone — a bordered card on a flat page reads as a discrete object, and the grid was competing with its edge. --grid-line is kept for components that draw their own rules. The scrollbar thumb is its own pair so the light theme can lighten it: --scrollbar-thumb —, --scrollbar-thumb-hover —.

04 · Text ramp and roles

Emphasis follows data, not position

Five weights of grey, one job each — used consistently whether the surface underneath is page or panel. Hierarchy comes from weight and colour, not from a size jump or a second family, which is why the working range of the whole UI is 11–16px.

On panel
--text-strong—Strong — settlement calendarTitles, event-day numerals, emphasis.
--text-body—Body — settlement calendarDefault running copy.
--text-muted—Muted — settlement calendarSecondary copy, taglines.
--text-faint—Faint — a section markerChrome labels, eyebrows, empty-day numerals.
--text-dim—Dim — settledPast / settled state, lowest-priority meta.
on accent
--text-onaccent—
on fill, dark
--text-on-fill-dark—
on fill, light
--text-on-fill-light—
Roles · dist/css/app.roles.css · rendered size · weight · line-height · letter-spacing
.sx-page-title—Pick a directionThe one h1 on a page; 32px, tracked tight.
.sx-title—Available strategiesA section heading, 20px.
.sx-card-title—FOMC Rate Decision16px / 600 / strong — a card or panel heading.
.sx-body—Settlement calendar14px / 400 / body — the default for prose and rows.
.sx-meta—Settlement calendar12px / 400 / muted — secondary chrome under a title.
.sx-eyebrow—Settlement calendar11px / 600 / faint / 0.06em caps — a section marker. Never mono.
.sx-figure—$3,980.00Inter's tabular figures, so columns align.
.sx-code—{ frame: 42 }System monospace, for literal code only.
Emphasis follows events · the calendar's day numerals

A day cell's numeral weight is driven entirely by whether it HAS events — not by weekday, column or row. Event days render strong; empty days recede to faint; past days dim further regardless of events; today is a solid YELLOW block (founder 2026-08-16 round 2 — accent stays reserved for interactive / selection state) with near-black text and a pulsing dot.

14
Event day
15
Empty day
9
Past day
10
Today
05 · Borders, focus, selection

Three border tones, one ring

A card's edge is its 1px --border; the quiet rule between day cells is the tertiary tone. The only outline in the system is *:focus-visible — hover is a background step, never a ring.

border--border—A card's edge.
border-strong--border-strong—An unchecked category checkbox.
border-tertiary--border-tertiary—Calendar grid line — the quiet rule between day cells.
grid-line--grid-line—For components that draw their own rules.
focus-ring--focus-ring—— solid, offset —.
Focus ring (always on, for demo) Click to select this line — ::selection is accent under white, globally.

The class-order trap: a day cell's background must resolve to exactly ONE mutually exclusive class — never two conditional utilities layered side by side. Tailwind orders rules by declaration order, not by how cn() concatenates class names, so whichever rule the stylesheet emits LAST silently wins.

06 · Semantic

Colour = data semantics, not decoration

Every non-neutral colour in the system answers a specific question. None of them are interchangeable.

Market direction — up / down
up--up—
up-dim--up-dim—Candle wick tone.
down--down—
down-dim--down-dim—Candle wick tone.
viz-settle--viz-settle—The payoff at settlement — a categorical pair, not a market one.
viz-now--viz-now—The payoff today; stepped for the white card in light.

Rule: up / down is reserved for MARKET DIRECTION only — PnL sign, 24h change, candle colour, In-Zone status. Never repurposed as a generic success / error pair.

--up aliases the brand's sage, but --down is a separate muted red rather than the brand's oxblood — chosen so the breaking-news alarm can be louder than price direction. Both values on the overview's drift list.

Interactive — accent, focus, selection
accent--accent—
accent-hover--accent-hover—
accent-bright--accent-bright—
focus-ring--focus-ring—

Rule: accent marks INTERACTIVE state and selection — the segmented control's thumb, a selected row, a selected date. Never market direction or category. The calendar's TODAY block moved off the accent to the yellow below (founder 2026-08-16 round 2, item 9), so accent now means exactly one thing.

The yellow trio — today, spot, star
Today · 10
today-highlight--today-highlight—Every calendar's TODAY block. Text: --text-on-today —.
Spot 108,420
spot-highlight--spot-highlight—The order books' inline spot bar — a signal yellow used at block scale nowhere else. Text: --text-on-spot —.
star--star—The watchlist star's fill. Deeper on the light theme: the dark yellow measures about 1.6:1 on white as a small glyph.

The demo declares the same yellow three separate times with no shared variable, even though /design calls them one "signal-yellow family"; the foundation's --signal-yellow — is that family, and today and spot alias it.

Mood — constants resolved through the active action palette

lib/mood.ts maps each portfolio mood to a representative action score (0 / 15 / 45 / 65 / 95) and resolves it through the ACTIVE action palette rather than inventing a second colour system. Those scores are number tokens, not CSS; the chips read the active palette's buckets.

Empty · 0 Chill · 15 Normal · 45 Hot · 65 Act now · 95

A palette readout, not a component: /design sets each label in the mood's own colour over a 13% wash of it, which measures about 2:1 for the yellow and orange buckets on the light theme; here the label is strong text and the dot carries the colour. The shipped MoodPanel uses the colour only for its bar.

07 · Category colours

Six categories, one source of truth

lib/categories.ts is the only place a category colour is assigned — every chip, pill and dot reads through it, never a hard-coded hex. BREAKING (founder 2026-08-16) is the urgent-news alarm tag, a hot signal red deliberately louder than the muted market --down; it sorts first and always wins as an event's primary tag. The text on a solid fill is computed per fill by WCAG luminance, not eyeballed: crypto's cobalt and breaking's red take white, the other four take near-black.

Breaking
tint
--cat-breaking— --cat-breaking-tint— text-on: light → --text-on-fill-light —
Crypto
tint
--cat-crypto— --cat-crypto-tint— text-on: light → --text-on-fill-light —
Macro
tint
--cat-macro— --cat-macro-tint— text-on: dark → --text-on-fill-dark —
Political
tint
--cat-political— --cat-political-tint— text-on: dark → --text-on-fill-dark —
Banking
tint
--cat-banking— --cat-banking-tint— text-on: dark → --text-on-fill-dark —
Equities
tint
--cat-equities— --cat-equities-tint— text-on: dark → --text-on-fill-dark —
Chip samples — solid fill + the declared contrast text
Breaking Crypto Macro Political Banking Equities

Crypto reuses --blue-400 and equities reuses --up exactly; political reuses --amber-500. Macro is a near-duplicate of the foundation's teal-400 that is not the same value (foundation › teal).

08 · Impact

Severity reuses the market and status hues

High is the market red, medium the amber status hue, low the dim text tone — no new palette. The topic row's 3px severity bar draws it; the bar is hidden in the demo today ("may return"), so the recipe makes it opt-in by data-severity.

High--impact-high—→ down
Med--impact-med—→ amber-500
Low--impact-low—→ text-dim
FOMC Rate Decisionsettles in 19d 6h
MacroBanking
Nvidia earningssettles in 3d 12h
Equities
CPI printsettled
Macro
09 · Action score

Four palettes for one 0–100 urgency number

Bucketed low 0–25 / mid 26–50 / high 51–75 / critical 76–100. Every palette renders the same sample scores so they can be compared directly; the active one is what the portfolio uses (founders' palette review, 2026-07-10: "action score carries the traffic-light semantics"). A few buckets carry a light-theme override where the dark hex washes out on white — toggle the theme to see them step.

Preview palette
Sample
POS12
POS44
POS68
POS93

The switch is CSS only: the strip reads the chosen palette's tokens through the radio group. Without :has() it shows the active palette. The score is drawn as ActionPositionCard ships it — --fs-3xl bold on the bucket colour mixed 14% into --surface-row — so the strip measures the real pair.

At the card's 28px the pair needs 3:1 as large text. The dark theme clears it except Verdant→Crimson's low and critical (2.8 and 2.9:1); the light theme does not for the mid and high buckets of Signal, Vital and Verdant→Crimson, or Vital's low (1.9–2.6:1). The demo's values, kept exactly; toggle the theme to see them.

Signal signal

Clinical grey-to-red ramp — quiet until it isn't, no colour spent on "fine."

low · 0–25--action-signal-low—Light: deepened so "low" still registers on white.
mid · 26–50--action-signal-mid—
high · 51–75--action-signal-high—
critical · 76–100--action-signal-critical—
Sample
POS12
POS44
POS68
POS93

Cobalt Heat cobalt-heat

Brand cobalt warming through violet into magenta — the house accent turned into an urgency gauge.

low--action-cobalt-heat-low—Light: the brand's own deep cobalt keeps "low" legible without changing its hue.
mid--action-cobalt-heat-mid—
high--action-cobalt-heat-high—
critical--action-cobalt-heat-critical—
Sample
POS12
POS44
POS68
POS93

Vital vital

Teal-yellow-orange-red — a vitals-monitor read, healthy to flatlining.

low--action-vital-low—
mid--action-vital-mid—Light: the yellow is too pale on white; deepened toward gold.
high--action-vital-high—
critical--action-vital-critical—
Sample
POS12
POS44
POS68
POS93

Verdant→Crimson verdant-crimson

Active

Green through amber into crimson — the classic semantic traffic light, ported from the DTE ramp per the founders' palette review (2026-07-10): the action system carries the loudest semantics because it matters more.

low--action-verdant-crimson-low—Light: the deep verdant reads muddy on white — brightened one step.
mid--action-verdant-crimson-mid—
high--action-verdant-crimson-high—
critical--action-verdant-crimson-critical—
Sample
POS12
POS44
POS68
POS93

The score itself is a deterministic pseudo-random stand-in seeded off the position id (lib/actionScore.ts's DEMO LOGIC NOTICE) — the buckets, palettes and colour resolution are meant to outlive it. The /design specimen sets the score in the code face; a score is a number, so the system sets it in the figure face (Rule 2: "numbers use font-num").

10 · DTE (days to expiry)

Three 7-step ramps, deeper the closer

Calendar-day brackets for a position's settlement — today, 0–2, 3–7, 7–14, 14–21, 21–30, >30 — each whole day in exactly one (a shared boundary day resolves to the farther bucket). Days farther out than 60 get no DTE colour at all. The far end of a 7-step ramp is tuned to barely register on the dark page; on white that same hex reads as a blotch, so the far brackets carry light-theme overrides. The cells' text is whichever of the two on-fill inks measures the higher contrast against the painted fill; /design picks by a luminance threshold instead, and the line under each week says where the two differ.

Preview palette
Sample week
34>30d
2521–30d
1714–21d
107–14d
53–7d
10–2d
0today

—

/design's contrastTextFor() inks a fill white below a luminance of 0.4 and near-black above it. The two inks cross at about 0.19, so on the saturated ambers, oranges and reds of these ramps — most of Ember in both themes, the near brackets of Verdant→Crimson — a fill between 0.19 and 0.40 takes white at 2.3–4.3:1 where --text-on-fill-dark measures higher, and in the audit's cases (Ember's 7–14d on the light theme, Verdant→Crimson's 7–14d and 14–21d) would clear 4.5. The cells here take whichever of --text-on-fill-light and --text-on-fill-dark measures higher against the painted fill, and the line under each week names every cell where the app's pick is the other one, with both ratios (hover a cell for its own). The threshold itself is the demo's to change.

Ember ember

Active

Hot red today cooling through orange into warm sand — saturation fades with distance, but every in-window day stays brighter than a plain day. (Founder 2026-08-16: the old ramp's far end smouldered darker than the plain-day grey; urgency = saturation, never dimness.)

>30d--dte-ember-gt30—
21–30d--dte-ember-d21-30—
14–21d--dte-ember-d14-21—
7–14d--dte-ember-d7-14—
3–7d--dte-ember-d3-7—
0–2d--dte-ember-d0-2—
today--dte-ember-today—
Sample week
34>30d
2521–30d
1714–21d
107–14d
53–7d
10–2d
0today

—

Abyss abyss

Cobalt / indigo ramp — today lands exactly on the brand accent, everything else recedes from it. The four far brackets go pale on the light theme.

>30d--dte-abyss-gt30—
21–30d--dte-abyss-d21-30—
14–21d--dte-abyss-d14-21—
7–14d--dte-abyss-d7-14—
3–7d--dte-abyss-d3-7—
0–2d--dte-abyss-d0-2—
today--dte-abyss-today—
Sample week
34>30d
2521–30d
1714–21d
107–14d
53–7d
10–2d
0today

—

Verdant→Crimson verdant-crimson

Green through amber into red — the classic semantic-urgency traffic light. The three far brackets go pale on the light theme.

>30d--dte-verdant-crimson-gt30—
21–30d--dte-verdant-crimson-d21-30—
14–21d--dte-verdant-crimson-d14-21—
7–14d--dte-verdant-crimson-d7-14—
3–7d--dte-verdant-crimson-d3-7—
0–2d--dte-verdant-crimson-d0-2—
today--dte-verdant-crimson-today—
Sample week
34>30d
2521–30d
1714–21d
107–14d
53–7d
10–2d
0today

—

11 · Underlyings

Eight assets, two colours each

data/underlyings.ts is the single source of truth for asset identity. The theme colour is the portfolio heatmap tile's identity hue, used as a low-alpha tile background and a tile-top rule — never for on-tile text, because BTC / GOLD / NVDA measure about 7:1 on the dark row surface but about 2:1 on the light one. The glyph colour is the inline SVG mark's own fill, a legibility recolour in several cases rather than a brand colour. The glyphs below are the demo's own paths — bare at 16 / 24 / 40px, as /design shows them, and in the 20px page-tone disc UnderlyingGlyphs puts a 13px glyph in beside an event, ringed in --border-strong; every fill is read from its --underlying-*-glyph token, so SPACEX, AAPL and the S&P text-mark follow the theme.

Bitcoin BTC --underlying-btc— --underlying-btc-glyph— simple-icons siBitcoin — the exact brand orange, glyph and tile alike.
Ether ETH --underlying-eth— --underlying-eth-glyph— The glyph is recoloured to slate (the literal near-black brand hex disappears on ink surfaces); the tile keeps Ethereum's blue.
Onchain Gold XAU --underlying-gold— --underlying-gold-glyph-face— --underlying-gold-glyph-top— --underlying-gold-glyph-edge— No official mark — a custom flat-fill ingot, two solid tones standing in for a bevel (no gradients).
Nvidia NVDA --underlying-nvda— --underlying-nvda-glyph— simple-icons siNvidia — the exact brand green.
Apple AAPL --underlying-aapl— --underlying-aapl-glyph— The glyph follows --text-strong: the literal mark ships pure black, and a fixed inverse would vanish on the light theme. The tile is a warm grey, since black cannot be a tile colour.
Circle CRCL --underlying-crcl— --underlying-crcl-glyph— Recoloured to Circle's USDC blue — the literal simple-icons violet reads as neither Circle nor legible at glyph size.
SpaceX SPACEX --underlying-spacex— --underlying-spacex-glyph— The glyph follows --text-strong, near-white on dark and near-black on light.
S&P 500 SPX --underlying-sp500— --underlying-sp500-glyph— No brand mark — a bold S&P / 500 text-mark in currentColor, so it inherits ambient tint like any other chrome icon (the dot shows this page's text colour).

/design's UNDERLYING_NOTES still describes the SpaceX glyph as inverted to a fixed off-white literal for dark surfaces, while data/underlyings.ts has since switched it to the --text-strong token (its own comment: the literal dark-surface recolour "was invisible on the light theme"). The reference page's note is stale; the token follows the data file.

12 · Layout

A true 4px grid, cards on a quiet page

The root is 16px — that is the whole fix. The previous 14px root (a density knob) landed Tailwind's spacing on 7px, 14px, 35px and the type scale on sizes like 12.25px. With a 16px root, gap-2 is exactly 8px, h-10 exactly 40px. Prefer the plain utilities; the tokens exist so inline styles and arbitrary values can't drift off the grid either. Each bar is drawn at its token and the readout resolves the same token.

Spacing · --space-*
--space-1—Icon ↔ label, chip inner padding
--space-2—DEFAULT rhythm between elements inside a component
--space-3—Card padding-block, dense row inset
--space-4—Card padding-inline, grid gutter
--space-5—Roomy card padding-inline
--space-6—Between sections in a column
--space-8—Between major page blocks
--space-12—Hero block, empty state
--space-16—Top-of-document breathing
Container and gutter · .sx-container
Root size--root-font-size—
Container--container-max—
Gutter (now)--gutter—16 → 24 → 32 at 768 and 1280px; resize to see it step.

.sx-container centres document-shaped pages (this one) at 1280px. The workspace pages — event, trade, portfolio — stay full-bleed on purpose: a left rail plus a fluid grid, and centring them would waste the half of a wide monitor those layouts exist to fill.

Component sizes · app.component
Control sm / md / lg--control-h-sm—--control-h-md—--control-h-lg—
Button inset--btn-px-sm—--btn-px-md—--btn-px-lg—
Chip filter / tag--chip-h-filter—--chip-h-tag—--chip-px-filter—--chip-px-tag—
Segmented--segmented-pad—--segmented-height—--segmented-option-padding-x—
Rail--rail-w-collapsed—--rail-w-expanded—--rail-header-h—--rail-row-h—
Icons xs / sm / md / lg--icon-xs—--icon-sm—--icon-md—--icon-lg—
Card--card-radius—--card-pad—--card-pad-roomy—
Popover--popover-gap—--popover-side-gap—--popover-side-width—
Modal · day · focus--modal-max-width—--day-size—--focus-ring-width—--focus-ring-offset—

The segmented option's 14px inset (px-3.5) and the popover's 10px side gap are the demo's values and sit off the 4px grid; they are kept as the source has them.

13 · Type scale

Whole pixels on a 16px root

10 · 11 · 12 · 13 · 14 · 16 · 20 · 24 · 32, then a display ramp of 28 · 32 · 40 · 48 · 64 for the calendar's giant day numerals and the splash. Deliberately compressed at the top: a title is separated from its body by weight and colour, not a 2× size jump. Tracking goes negative as size grows (Inter is drawn tight); the all-caps label dropped from 0.14em to 0.06em. The px column is resolved at runtime — a hard-coded value silently goes stale the moment the scale moves.

Content scale · --fs-*
--fs-3xs—Aa — eyebrow chromeMicro tag / eyebrow chrome only
--fs-2xs—Aa — chip labelChip / tag labels, countdown tokens
--fs-xs—Aa — secondary UISecondary UI chrome
--fs-sm—Aa — dense contentDense content — event bars, meta lines
--fs-base—Aa — body copyBODY DEFAULT — prose, titles, day numbers
--fs-md—Aa — card titleCard titles, emphasised readouts
--fs-lg—Aa — headingPanel headings
--fs-xl—Aa — headlinePage headings
--fs-2xl—Aa — hero numeralHero numerals only
Display ramp · --fs-3xl … --fs-7xl · the calendar's day numerals and the splash
--fs-3xl—14text-3xl · 1.1
--fs-4xl—14text-4xl · 1.1 — meets --fs-2xl at 32px, where the content ramp hands over
--fs-5xl—14text-5xl · 1.05
--fs-6xl—14text-6xl · 1
--fs-7xl—14text-7xl · 1
Label tracking--tracking-label—Was 0.14em; the landing's still is.
Snug (16–20px)--tracking-snug—
Tight (24px+)--tracking-tight—

Tailwind reads the same scale as text-3xs … text-7xl through dist/css/app.tailwind.css, with line-heights that loosen as size drops (1.4 at the small end, 1 at the top) and the snug / tight tracking from 16px up.

14 · Radii

Distinctly rounder than a terminal

A card is 12px, a control 8px, a chip a full pill. Roundness is most of what separates a consumer market UI from a trading terminal, so this is where the brand shift is most visible.

--radius-xs—Micro tags, inline markers
--radius-sm—Small chrome, swatches
--radius-md—Buttons, inputs, nav rows
--radius-lg—Cards, panels, modals
--radius-xl—Hero cards
--radius-2xl—Oversized feature surfaces
--radius-full—Chips, tags, segmented controls (rounded-full)

Two of the demo's own drawings sit off this scale: the event pill rounds at 3px, below --radius-xs, and the /design modal mock rounds at 8px where the brief puts "cards, panels, modals" at --radius-lg. The recipes follow the brief for the modal and keep the pill as EventPill.tsx has it.

15 · Shadows

For things that float

A card's edge is its 1px border; the shadow only separates things that FLOAT — popovers, modals, drag targets. Cards get a shadow barely past nothing so they don't look stamped on. The light theme's are softer and tighter: a near-black shadow at dark-theme opacity reads as a harsh smudge on white.

Card--shadow-card—
Raised--shadow-raised—
Popover--shadow-popover—Popovers, modals, the rail while it overlays the page.
16 · Motion

One easing curve, two motion languages

Zoom (scale + opacity, 0.32s) means the VIEW changed — Day, Week, Month, Year. Slide (y + opacity, 0.3s) means you moved WITHIN the same view — previous / next month. Same ease curve, different transform. CSS transitions read the tokens; framer-motion needs raw seconds, so those are number tokens that are not emitted as CSS. A second curve rides along unnamed in the demo — Tailwind's default transition timing function, which every bare transition-colors inherits — and the system names it --ease-ui so a recipe's hover moves the way the demo's does.

Fast--dur-fast—
Base--dur-base—The segmented thumb's slide.
Slow (app)--dur-slow—The landing's is 360ms.
Micro--dur-micro—Every transition-colors: a hover, a pressed chip, a row's tone.
The one curve--ease-out—What the source names: the flip, the pulses, the rail's width.
Tailwind's default--ease-ui—Unnamed in the source — what Button, Chip, the segmented options, the rail's rows and TopicRow ease their colours on.
framer-motion seconds · app.motion.framer · numbers, not CSS
0.15s · microPopover / story-card micro-transitions (0.14–0.16s)
0.14s · popoverA popover's fade (Popover.tsx; the spec drafted 0.16)
0.15s · modal backdropDayEventsModal backdrop fade
0.18s · modal panelDayEventsModal panel entrance
0.3s · slideCalendarMonth SLIDE (within-view nav)
0.32s · zoomCalendarPanel ZOOM (view change)
0.5s · flipThe strategy card's 3D flip
spring 480 / 38The segmented thumb's stiffness / damping
100 · 500 · 200Wheel nav: threshold px, lock ms, gap ms — one gesture, one step

The global reduced-motion rule turns every animation and transition in the app off at once, leaving each instant but intact; the segmented control's thumb and the rail's width change are covered by it.

18 · Icons

Thirty-nine strokes on a 16-unit grid

Every export of components/icons.tsx, inline, drawn as the demo draws them: a 0 0 16 16 viewBox, fill="none", stroke="currentColor" at width 1.6 with round caps and joins, and paths kept inside a 1.5–2 unit margin so the caps never clip. The weight was chosen for 16–18px rendering; at — it is 1.8 device px. Three glyphs keep a different rule on purpose — CheckIcon is stroke 2 so a 9px check inside a checkbox still reads, GripIcon is six filled dots and no stroke, and StarIcon fills with currentColor once watched; HelpIcon's dot is a 2-unit stroke and EyeOffIcon's top arc sits at opacity 0.55. The greeks are stroked letterforms, not <text>: the app's Inter build is a latin subset, so a real Greek codepoint would fall out to whatever the OS happens to have and stop matching the rest of the set. There is no icon colour. Every glyph is currentColor and takes the text tone of whatever it sits in — the cells below inherit this page's --text-body; the first card shows the tones the app actually hands them. The four sizes are the four tokens: --icon-xs — (the rail's sub-rows and its hover flyout, where the glyph is secondary to its label — off the 4px grid, as the source has it), --icon-sm —, --icon-md —, --icon-lg —.

Tones · currentColor on a text token · never a per-icon colour
--text-faint—
an idle rail row, the periodic glyph, an unwatched star
--text-body—
a hovered row; icons inline with prose
--text-strong—
the active rail row
--star—
watched — the one time the star fills
--up—
market direction, up
--down—
market direction, down

An icon beside a label is aria-hidden; an icon-only control carries the label. At 16–18px the stroke holds up at 200% zoom, and the inherited tone is the control's: an idle row's glyph sits at --text-faint and steps to --text-body on hover with its label.

Navigation · what the rail wears, in nav order, and its own controls · 11
CalendarIcon
TradeIcon
StrategyIcon
BulbIcon
GlobeIcon
ChainIcon
BriefcaseIcon
HelpIcon
MenuIcon
ChevronsLeftIcon
ChevronsRightIcon

Each page glyph says what the page sells or is — a dated page, two candlesticks, a document, a bulb, a globe, two links, a case — never a generic pictogram twice. The pair of double chevrons is the rail's pin: collapse («) and expand (»). The three bars are the phone top bar's drawer toggle.

Greeks · Δ ν θ · stroked letterforms, not text · 3
DeltaIcon
VegaIcon
ThetaIcon

Each Trade sub-page is named for the exposure it sells, so the rail draws the letter itself: Δ direction (the first derivative on price), ν volatility (a curved descender into a pointed foot, then a straight riser — a symmetrical curve reads as a plain "u" at rail size), θ yield (the time decay a yield seller collects).

Chrome · disclosure, stepping, dismissal, the theme toggle · 14
ChevronUpIcon
ChevronDownIcon
ChevronLeftIcon
ChevronRightIcon
ArrowRightIcon
XIcon
CheckIcon
PlusIcon
MinusIcon
SearchIcon
SunIcon
MoonIcon
SettingsIcon
DownloadIcon

CheckIcon is the one glyph at stroke 2: it renders at 9px inside a 14px checkbox (the filter checkbox, below) and would vanish at 1.6. The sun shows while in dark mode (click switches to light), the moon while in light. The gear is a rim and then the teeth — the hub-and-spokes it replaced was pixel for pixel the sun.

Market · direction, exposure, cadence, watching · 6
TrendUpIcon
TrendDownIcon
BoltIcon
PeriodicIcon
WalletIcon
StarIcon
StarIcon · filled

The trends carry --up / --down where they mean direction (the Direction toggle). The star is outlined until watched, then filled in --star; the wallet's dot is the set's one filled detail inside a stroked glyph.

Misc · the chart's helper cluster · 5
RestartIcon
FrameResetIcon
EyeIcon
EyeOffIcon
GripIcon

Reset selection, reset the time range, zones panel visible / hidden (the icon carries the current state; the struck eye's top arc sits at opacity 0.55), and the six-dot grab handle — the one glyph with no stroke at all.

19 · Rules

Laws of the terminal

Harvested from the codebase's own comments (/design §12) — every one is enforced by convention, not by a linter. Rendered as /design lists them; four have drifted from the round-12 brief and are flagged under their entry.

  1. 01
    Mono for numerals only.IBM Plex Mono renders prices, scores, countdowns, timestamps — never a heading, label, or button.Stale since round 12: BRAND.md replaced IBM Plex Mono with Inter's tabular figures (font-num); "a real monospace survives only for literal code". The law's intent — numerals in a fixed-width figure, never as chrome — still holds.
  2. 02
    No gradients, no emoji.Flat fills everywhere, including the gold-ingot glyph's two-tone bevel standing in for a gradient.
  3. 03
    14px is the content floor.--fs-sm (14px) is the minimum size for real content; only micro chip/tag/eyebrow chrome goes under it.Stale: on the round-12 scale --fs-sm is — and --fs-base is the 14px body default; globals.css now puts the content floor at "13–14px".
  4. 04
    Hierarchy by tone, not borders.The page → panel → row → hover tone scale is the only separator mechanism for cells, rows, and panels.Softened by round 12: a card now also carries a hairline border — "tone for hierarchy, border for edges" (BRAND.md Rule 3, Card.tsx).
  5. 05
    Color = data semantics, not decoration.up/down is market direction only; accent is interactive/today/selection only; category colors are assigned once and never invented ad hoc."today" no longer belongs in the accent's list: the TODAY block moved to the signal yellow (founder 2026-08-16 round 2, item 9), as /design's own Semantic section says two sections earlier.
  6. 06
    Hover = background only.No hover rings or outlines — a background-tone step is the entire affordance. The only outline in the system is *:focus-visible.
  7. 07
    Emphasis follows events, not position.A calendar day's numeral weight is driven by whether it has events — not by weekday, column, or row.
  8. 08
    One mutually-exclusive background class per element.Never layer two conditional bg-* utilities — Tailwind's stylesheet order, not cn()'s concatenation order, decides which wins.
  9. 09
    The accent is exact.— — never substitute a close-enough periwinkle or blue.
  10. 10
    Solid blocks over outlines.Category/status chips are solid fills with computed contrast text — no leading color dot, no outline-only chip.