ReviewOS

stacks/reportshq

Brand identity and design language: tokens, typography, dark mode

#3
Closed chris opened this 8 days ago ·
8 days ago

Context

ReportsHQ must read as loghq's sibling: editorial-technical, dark-capable, token-driven - but with its own accent and personality (loghq owns rose #e11d48; pick a clearly different accent that suits "reports/insight", e.g. a deep indigo or emerald family, and defend the choice in the design brief). Study /Users/chris/Code/Apps/loghq: public/tokens.css, public/marketing.css, config/crosswind.ts, config/ui.ts, resources/assets/scripts/site-mode.js, resources/stores/theme.ts.

Read the stacks-design-taste skill first; optionally use stacks-brandkit to generate reference imagery before committing to tokens. Be picky.

Scope

  • Design brief committed to the repo: aesthetic direction, the three dials (VARIANCE / MOTION / DENSITY), accent rationale, typography pairing (loghq uses Space Grotesk + JetBrains Mono; choose deliberately - same family kinship is fine, identical is lazy), loaded via config/ui.ts head with font-display: swap.
  • public/tokens.css as the single token source: light + dark palettes, color-scheme declared, WCAG contrast ratios documented per token pair (loghq documents these inline - match that rigor). Do not use crosswind preflights for tokens; in loghq it silently never emitted.
  • Dark mode via [data-theme] + prefers-color-scheme with a pre-paint script so there is no flash; stx owns the theme (configure in site.config.ts; do not add a second competing guard).
  • config/crosswind.ts semantic utilities mapped onto the tokens (bg-canvas, bg-panel, border-line, text-ink/muted/subtle/accent, accent-ink, field, wordmark - mirror loghq's naming so sibling code ports cleanly).
  • Wordmark/logo treatment (type-based is fine; no hand-rolled SVG icon paths - Iconify i-hugeicons-* classes for icons).
  • Favicon + social card assets in public/.

Acceptance

  • A token reference page (dev-only route is fine) renders every token in light and dark with its documented contrast ratio.
  • Pre-flight check from stacks-design-taste Section 14 passes. No em-dashes anywhere in user-visible copy.

Depends on #2. Part of #1 (M1 Foundation). Ground rules: see #1.

8 days ago

Shipped in 0723d55.

Accent: cobalt #3a4fd0 light / #8fa2ff dark. Chosen for what it does not mean. Green, amber and red all carry semantic load in a reporting product (positive, warning, negative), and an accent sharing a hue with a delta makes every number ambiguous, right where the product has to be trustworthy. Blue is the family left that reads as brand rather than as reading. It is also clearly not loghq's rose, which matters because the two will be seen side by side.

The canvas inverts loghq's pairing on purpose: warm off-white #fcfbf9 under a cool accent, where loghq runs a cool canvas under a warm one. The warmth also separates the page from the white chart surfaces sitting on it.

Type: Geist + Geist Mono. The mono is load-bearing, not decorative: every figure in a report, table cell, axis tick and delta is set in it with tabular numerals, so a column of numbers aligns on the digit instead of drifting. Both verified as actually servable before committing to them.

Deltas are never the accent. --pos / --neg / --warn are separate tokens and every delta in the UI is paired with a direction glyph, because colour alone fails for roughly 8% of men and a reports product cannot make its headline numbers unreadable for them. The six-colour chart ramp starts at the accent, so a single-series chart is brand coloured for free, and skips the exact positive green and negative red so a category can never be misread as a direction.

Contrast is measured, not claimed. Every ratio in the tokens file and on the reference page was computed from the shipped hex values; nothing is below 4.5:1, and the lightest step (--text-3, 4.72:1 on canvas) is the smallest one that still clears AA on the worse of the two surfaces.

bun scripts/verify-tokens.ts holds all of it, and now runs in CI. It asserts that every token a utility maps onto is declared in all four theme blocks, that the [data-theme] blocks come after the media query so an explicit choice beats the OS preference, that every block declares color-scheme (otherwise native controls ignore the theme), that each documented ratio is the ratio those colours actually produce, and that text on a filled accent clears AA in both themes. Mutation tested in both directions: renaming a token and darkening one to break a ratio each fail it, and the restored file passes.

Verified in a real browser rather than by eye. Computed styles on /design-tokens, toggling data-theme:

lightdark
body backgroundrgb(252, 251, 249)rgb(11, 13, 18)
text-subtlergb(107, 113, 128)rgb(125, 135, 152)
bg-series-2rgb(13, 116, 144)rgb(34, 211, 238)
accent fill / inkrgb(58, 79, 208) / whitergb(143, 162, 255) / rgb(6, 8, 20)
color-schemelightdark

Each matches its token exactly, and Geist is the resolved family.

Two things worth carrying into later issues, both now in AGENTS.md:

  • stx templates take bare variable names, not Blade's $. Writing @foreach ($surfaces as $surface) does not error, it renders <!-- [Foreach Error [1102]]: $surfaces is not iterable --> and returns 200 with the section silently empty. All five loops on the reference page failed this way at first.
  • Crosswind extracts classes from rendered HTML, so an interpolated class like bg-series- only emits once the loop rendering it works. A broken loop breaks the CSS too, which makes the first symptom look like a styling bug rather than a template one.

The scaffold's config/crosswind.ts also carried content, minify, and a preflight key that is not part of CrosswindConfig at all, so it read as configuration while doing nothing. The satisfies clause now rejects that class of mistake at build time.

Not in scope here and deliberately left: the marketing shell and app chrome (#19, #13), and the social card image, which wants the real landing composition to crop from.

Sign in to comment on this issue.