# Greenville Triumph Dashboard System - Build Contract

If you read only this file, you can still build a brand-correct Triumph
dashboard. Live reference and examples: https://design.greenvilletriumph.club/

This is the **dashboard** system: staff-facing tools, ops surfaces, ticketing
and revenue dashboards. On brand, tuned for dense data on a screen.

---

## 1. Paste this into `<head>`

```html
<meta name="color-scheme" content="only dark">
<meta name="darkreader-lock">
<meta name="theme-color" content="#14181f">
<style>:root,html,body{background:#14181f!important;color:#f2fafa!important;color-scheme:only dark;}</style>

<link rel="stylesheet" href="https://design.greenvilletriumph.club/tokens.css">
<link rel="stylesheet" href="https://design.greenvilletriumph.club/components.css">
```

All three faces (Fabiola Capitals, Inter, JetBrains Mono) load automatically
through `@font-face` in tokens.css; no Google Fonts link needed. Best starting
point: clone `https://design.greenvilletriumph.club/starter.html` and edit
content only.

---

## 2. Hard rules (non-negotiable)

- Use `var(--token-name)` in CSS. Never hardcode a hex, rgba, or oklch value. (Chart.js datasets are the only exception, see section 6.)
- Fabiola is the hero only: one `.h-display` per page, weight 400. Every other heading is Inter. Never put Fabiola on h2/h3, never weight above 400 on it.
- Seven brand colors, never an eighth. Surfaces are the derived slate ladder, not new colors.
- One accent per layout: Electric Lime (`--color-blue`) OR Light Green (`--color-pop`), never both.
- SIZE LAW: `font-size` only ever takes a `--text-*` token, never a raw rem/px value. `--text-xs` is the absolute floor; NOTHING renders smaller, including captions, footnotes, chart annotations, status pills, and count bubbles. Never shrink a registry component's type in page CSS (`.kpi-label { font-size: 0.65rem }` is the canonical sin). Labels too small, everywhere, was the system's longest-running failure; these tokens are the fix.
- Every uppercase mono label is `--text-label` / `--weight-semibold` / `--track-label` / muted. That covers eyebrows, section numbers, KPI and stat labels, table headers, nav links, and any key/dt-style label you invent. Do not re-implement the role with a local class at a smaller size; use the registry class or match its spec exactly.
- Labels never wear the accent. Triumph Green means interactive or live (links, active nav, CTAs, live counters, NOW states), nothing else. Exactly ONE featured kicker per page (the hero's) may opt in via `.eyebrow-accent` (`--weight-bold` / `--track-kicker` / accent).
- Tracking only via the tokens: `--track-label` (working labels), `--track-kicker` (the one featured kicker), `--track-subhead` (`.subsection-label`). Never type a literal letter-spacing value on a label. Wide tracking on tiny text is the recurring disease; the tokens are sized to the label tier.
- Subheaders are HEADINGS, not labels. Any header of a content group (`.subsection-label`, timeline group headers, map group titles) renders at `--text-lg` minimum with tracking at or under `--track-subhead`. `h6` and `.eyebrow` are labels; they never head a content group. Tiny mono caps as subheaders is the #1 recurring failure; do not reintroduce it.
- Triumph Green is never body text on a light surface (fails contrast). Use Greenville Blue or Deep Night.
- Yellow (`--color-warning`) is for genuine warning state only, never decorative.
- Sharp corners by default (`--radius` is 0). `--radius-full` only on pills, badges, toggle thumbs.
- Lift on hover (`translateY`), never scale. No gradient page or section backgrounds.
- Tabular-nums on every numeric column.
- Divided cell rows (stat bands, scoreboards) and left-bar accents use symmetric horizontal padding so text NEVER touches a divider or accent bar. Flush only the first cell of each row to the container edge.
- No em-dashes in copy. No "GVL Triumph" (use "Greenville Triumph"). No restricted-access framing.

---

## 3. Tokens (most-used; full set in tokens.css)

```
/* canvas + elevation ladder (slate, derived from Deep Night) */
--color-bg          page canvas (#14181f)
--color-surface-1   faint band (alternate sections: <section class="bg-surface">)
--color-surface-2   card / panel
--color-surface-3   raised / hover
--color-surface-4   overlay / drawer
--color-band-navy   the one deep Greenville-Blue band (use sparingly)

/* text */
--color-text        primary (#f2fafa)   --color-text-soft  secondary (#d2e6e5)
--color-text-muted  labels/meta         --color-text-faint disabled/placeholder
--text-alpha-90/70/50/30/15   Upstate Fog at opacity; for text on a colored/variable bg

/* borders */
--color-border  --color-border-emphasis  --color-border-strong

/* accents (one per layout) */
--color-accent  #61a631 Triumph Green   --color-blue #69b3e7 sky   --color-pop #97d700 lime
--accent-subtle (row tint)   --accent-muted (active/focus)

/* status (signals only) */
--color-success  --color-warning  --color-danger  --color-info  (+ each -subtle)

/* chart series (hex; stays in the seven colors) */
--series-green #61a631   --series-sky #69b3e7   --series-lime #97d700   --series-mist #d2e6e5

/* type */
--font-display Fabiola Capitals (.h-display only)   --font-sans Inter   --font-mono JetBrains Mono
--text-xs (THE FLOOR) ... --text-4xl (fluid)   --text-display (Fabiola hero only)
--text-label   the one size for every uppercase mono label (~15px at 1440)
--track-label / --track-kicker / --track-subhead   the only letter-spacing values labels may use
--weight-light 300 ... --weight-black 900   (body default 400; 300 for large lede only)

/* spacing + layout */
--space-xs/sm/md/lg/xl + --space-section   (fluid)   --space-1..16 (fixed 4px base)
--content-max 1200px   --content-wide 1440px   --gutter   --nav-h 60px
--radius 0   --radius-full 999px
--shadow-sm/md/lg   --shadow-float/-float-lg (overlays: dropdowns, toasts, popovers)
--duration/-instant/-fast/-slow   --ease-out/-in/-in-out/-spring (spring overshoots via linear() where supported)
```

---

## 4. Layout pattern (the canonical dashboard shape)

Sticky nav with scroll-spy, then banded numbered sections, continuous scroll.

```html
<header class="nav"><div class="nav-inner container-wide">
  <a class="nav-brand" href="#top"><img src="https://design.greenvilletriumph.club/images/brand/triumph-wordmark-on-dark.png" alt="">Name</a>
  <nav class="nav-links" id="nav"><a href="#overview">Overview</a> ...</nav>
</div></header>

<section id="overview" class="bg-surface"><div class="container-wide">
  <div class="section-header">
    <span class="section-number">01 · Overview</span>
    <h2 class="section-title">Headline.</h2>
    <p class="section-desc">One supporting sentence.</p>
  </div>
  <!-- content -->
</div></section>
```

Containers: `.container` (1200, reading), `.container-wide` (1440, dashboards),
`.container-full` (full bleed). Grids: `.grid-kpi`, `.grid-cards`, `.grid-2/3/4`,
and `.grid` (12-col) with `.col-4 .col-8` spans. Alternate `class="bg-surface"`
on sections for banded rhythm.

---

## 5. Component classes (registry)

```
nav, nav-inner, nav-brand, nav-links, nav-actions       sticky top nav
section-header, section-number, section-title,          section chrome
  section-desc, section-lede, subsection-label
card (+ card-raised, card-flush), card-head/title/desc/body/foot
kpi (marker) / kpi-tile (boxed) + kpi-label/value/foot  KPIs, in .grid-kpi
stat-band > stat > stat-eyebrow/value/meta              headline number row
exec-hero-card > exec-hero-eyebrow/title/sub +          executive scoreboard
  exec-scoreboard > exec-stat (.is-lead) >                (page lead, use once;
  exec-stat-label/value/sub                                value is --text-4xl)
title-hero > title-hero-mark/kicker/title +             title-card hero (matchday
  title-hero-rail > thr-item (.is-live) >                 guides, one-pagers; poster
  thr-label/value                                          Fabiola - NOT dashboards)
data-table (+ .dense, tr.total, td.num/.pos/.neg)       wrap in .table-scroll
btn (+ btn-solid/outline/ghost, btn-sm)                 buttons
input-wrap > input-underline                            underline input
toggle-input + toggle-track > toggle-thumb              switch
tabs > tab.active   |   segmented > button.active       tabs / segmented
toolbar > toolbar-group / toolbar-spacer                filter bar
chip.active + chip-x                                     filter chips
badge (+ -pill, -success/-warning/-danger/-info/-accent)
pill (+ .live pulse, .success/.warning/.danger/.info)
delta.up / .down / .flat                                change chip
progress > span[style=width] (+ .warning/.danger; .progress-block)
sparkline                                               Chart.js micro slot
chart-card > chart-head + chart-canvas[height]          chart panel
chart-legend                                            manual legend row
frosted                                                 glass overlay (nav/overlays ONLY)
drawer-scrim + drawer (.open) > drawer-head/body        slide-in panel
toast-stack > toast (+ -success/-danger/-info/-warning) ephemeral feedback (JS-driven)
empty-state / error-state                               states
stale.fresh/.aging/.dead                                "updated Xm ago"
skeleton / skeleton-row                                 loading
guideline.do / .dont > guideline-label + ul             do/don't docs
hover-lift / hover-border / hover-glow                  hover affordances
reveal (+ .in)                                          scroll fade-up; REQUIRES JS observer + noscript
```

---

## 6. Chart.js

Pinned CDN with SRI:

```html
<script defer src="https://cdn.jsdelivr.net/npm/chart.js@4.4.7/dist/chart.umd.min.js"
  integrity="sha384-vsrfeLOOY6KuIYKDlmVH5UiBmgIdB1oEf7p01YgWHuqmOHfZr374+odEv96n9tNC"
  crossorigin="anonymous"></script>
```

Canvas cannot read CSS variables, so pull palette values with the helper:

```js
const tok = n => getComputedStyle(document.documentElement).getPropertyValue(n).trim();
Chart.defaults.font.family = "'JetBrains Mono', monospace";
Chart.defaults.font.size = 12.5;   // size law applies to charts too: never below 12
Chart.defaults.color = tok('--color-text-muted');
// dataset colors: tok('--series-green'), tok('--series-sky'), tok('--series-lime'), tok('--series-mist')
// grid + ticks: tok('--color-border'), tok('--color-text-muted')
```

---

## 7. Before you ship

Both stylesheets linked (tokens before components). Dark-mode lock present.
Fabiola only on the hero at weight 400, every other heading Inter. No hex/rgba/oklch
literals except Chart.js. One accent per layout. Tabular-nums on numbers.
No font-size below `--text-xs`, no raw rem/px font-sizes, every uppercase label at
`--text-label` with a `--track-*` token, no accent on labels (one `.eyebrow-accent`
kicker max), subheaders at `--text-lg` minimum, chart type at 12px minimum.
"Greenville Triumph", never "GVL Triumph". No em-dashes, no restricted-access framing.

The brand book of record is https://brandpad.io/greenville-triumph/ (humans, login).
This system implements it for dashboards.
