Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file removed civiccore-ui/tokens/.gitkeep
Empty file.
221 changes: 221 additions & 0 deletions civiccore-ui/tokens/tokens-reference.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>CivicSuite — Design Tokens Reference</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<!-- No webfont CDN: CivicSuite makes no outbound calls. This reference renders in
the token font stack's local/fallback faces (Inter/Source Serif 4/JetBrains Mono
if installed, else the system serif/sans/mono fallbacks defined in tokens.css). -->
<link rel="stylesheet" href="tokens.css" />
<style>
/* This page consumes tokens.css and demonstrates it. It is itself AA-compliant. */
body { margin: 0; background: var(--paper); color: var(--ink); font-family: var(--font-sans); font-size: 14px; line-height: 1.5; -webkit-font-smoothing: antialiased; }
.wrap { max-width: 1080px; margin: 0 auto; padding: 48px 32px 96px; }
header.doc { border-bottom: 1px solid var(--gold); padding-bottom: 20px; margin-bottom: 8px; }
.kick { font-family: var(--font-serif); font-size: 11px; letter-spacing: 0.18em; text-transform: uppercase; color: var(--gold-2); margin-bottom: 10px; }
h1 { font-family: var(--font-serif); font-size: 34px; font-weight: 600; letter-spacing: -0.01em; margin: 0 0 8px; }
.lede { font-size: 15px; color: var(--ink-2); max-width: 64ch; margin: 0; }
h2 { font-family: var(--font-serif); font-size: 22px; font-weight: 600; margin: 48px 0 4px; }
.sec-sub { font-size: 13px; color: var(--ink-3); margin: 0 0 20px; max-width: 68ch; }
h3 { font-size: 12px; letter-spacing: 0.06em; text-transform: uppercase; color: var(--ink-3); font-weight: 600; margin: 28px 0 12px; }

.callout { display: flex; gap: 12px; padding: 14px 16px; border-radius: var(--radius); background: var(--warn-soft); border: 1px solid rgba(138,90,20,0.25); color: var(--warn); font-size: 13px; margin: 20px 0; }
.callout b { display: block; color: var(--ink); margin-bottom: 2px; }
.callout .n { font-family: var(--font-mono); font-weight: 500; }

.swatches { display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: 14px; }
.sw { border: 1px solid var(--rule); border-radius: var(--radius); overflow: hidden; background: var(--card); }
.sw .chip { height: 68px; display: flex; align-items: flex-end; padding: 8px 10px; }
.sw .chip .ratio { font-family: var(--font-mono); font-size: 10.5px; padding: 2px 6px; border-radius: 4px; background: rgba(255,255,255,0.85); color: var(--ink-2); }
.sw .meta { padding: 8px 10px; }
.sw .name { font-family: var(--font-mono); font-size: 12px; font-weight: 500; }
.sw .hex { font-family: var(--font-mono); font-size: 11px; color: var(--ink-3); }
.sw .note { font-size: 11px; color: var(--ink-3); margin-top: 4px; line-height: 1.4; }

table.contrast { width: 100%; border-collapse: collapse; font-size: 13px; margin-top: 4px; }
table.contrast th { text-align: left; font-size: 10.5px; letter-spacing: 0.06em; text-transform: uppercase; color: var(--ink-3); font-weight: 600; padding: 8px 10px; border-bottom: 1px solid var(--rule); }
table.contrast td { padding: 9px 10px; border-bottom: 1px solid var(--rule); vertical-align: middle; }
table.contrast .pair { font-family: var(--font-mono); font-size: 12px; }
table.contrast .r { font-family: var(--font-mono); font-variant-numeric: tabular-nums; text-align: right; }
.tag { display: inline-flex; align-items: center; gap: 4px; font-size: 10.5px; font-weight: 500; padding: 2px 8px; border-radius: 100px; }
.tag.pass { background: var(--ok-soft); color: var(--ok); }
.tag.aaa { background: var(--info-soft); color: var(--info); }
.tag.large { background: var(--gold-soft); color: var(--gold-strong); }
.tag.fail { background: var(--err-soft); color: var(--err); }
.demo-cell { display: inline-block; padding: 3px 10px; border-radius: 6px; font-weight: 500; }

/* type scale */
.type-row { display: grid; grid-template-columns: 120px 1fr; gap: 16px; align-items: baseline; padding: 12px 0; border-bottom: 1px solid var(--rule); }
.type-row .lbl { font-family: var(--font-mono); font-size: 11px; color: var(--ink-3); }
.t-display { font-family: var(--font-serif); font-size: 34px; font-weight: 600; letter-spacing: -0.01em; }
.t-title { font-family: var(--font-serif); font-size: 26px; font-weight: 600; }
.t-h { font-family: var(--font-serif); font-size: 16px; font-weight: 600; }
.t-body { font-size: 14px; }
.t-small { font-size: 12px; color: var(--ink-2); }
.t-mono { font-family: var(--font-mono); font-size: 12px; color: var(--ink-2); }

/* status pills */
.pills { display: flex; flex-wrap: wrap; gap: 8px; }
.pill { display: inline-flex; align-items: center; gap: 5px; font-size: 11px; font-weight: 500; padding: 3px 9px; border-radius: 100px; }
.pill::before { content:""; width:6px; height:6px; border-radius:50%; background: currentColor; }
.pill.ok { background: var(--ok-soft); color: var(--ok); }
.pill.warn { background: var(--warn-soft); color: var(--warn); }
.pill.err { background: var(--err-soft); color: var(--err); }
.pill.info { background: var(--info-soft); color: var(--info); }
.pill.gold { background: var(--gold-soft); color: var(--gold-strong); }

/* density demo */
.density-demo { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
.dcard { border: 1px solid var(--rule); border-radius: var(--radius-lg); overflow: hidden; background: var(--card); }
.dcard .dh { padding: var(--pad); border-bottom: 1px solid var(--rule); font-weight: 600; font-size: 13px; background: var(--paper-2); }
.drow { display: flex; align-items: center; height: var(--row-h); padding: 0 var(--pad); border-bottom: 1px solid var(--rule); font-size: 13px; gap: var(--gap); }
.drow:last-child { border-bottom: 0; }
.drow .dchip { font-family: var(--font-mono); font-size: 11px; color: var(--ink-3); }
</style>
</head>
<body>
<div class="wrap">
<header class="doc">
<div class="kick">CivicSuite · Design System</div>
<h1>Design Tokens Reference</h1>
<p class="lede">The visual reference for <span class="mono">civiccore-ui/tokens/tokens.css</span> — the suite's single token source. Every value here is rendered live from the token file. Contrast ratios are WCAG 2.x against the noted surface; AA requires 4.5:1 for normal text, 3:1 for large text and UI boundaries.</p>
</header>

<div class="callout">
<div>
<b>Three AA corrections applied during extraction</b>
<span class="n">--gold</span> is accent/border/large-text only (white-on-gold 3.22:1, a fail for body text) — text-bearing gold is <span class="n">--gold-2</span>. New <span class="n">--gold-strong</span> carries gold text on pale gold surfaces (5.42:1 where <span class="n">--gold-2</span> was only 4.31:1). <span class="n">--ink-4</span> is decorative-only (2.48:1) — never text.
</div>
</div>

<h2>Neutrals &amp; ink</h2>
<p class="sec-sub">Surfaces run paper → card; ink runs primary → decorative. <span class="mono">--ink-3</span> is the readable-text floor; <span class="mono">--ink-4</span> below it is for lines and disabled glyphs only.</p>
<div class="swatches" id="sw-neutral"></div>

<h2>Civic brand</h2>
<p class="sec-sub">Navy is the primary brand and is safe for solid fills with white text. Gold is warm accent — mind the three text rules above.</p>
<div class="swatches" id="sw-civic"></div>

<h2>Status</h2>
<p class="sec-sub">Each status token pairs with its own <span class="mono">*-soft</span> surface. All pairs verified AA; <span class="mono">--warn</span> is the tightest at 4.79:1 — do not lighten it.</p>
<div class="pills" style="margin-bottom:20px">
<span class="pill ok">Verified</span>
<span class="pill warn">Needs review</span>
<span class="pill err">Blocked</span>
<span class="pill info">In progress</span>
<span class="pill gold">Beta</span>
</div>
<div class="swatches" id="sw-status"></div>

<h2>Contrast ledger</h2>
<p class="sec-sub">The load-bearing text pairings, with measured ratios. This table is the acceptance record for the AA corrections.</p>
<table class="contrast">
<thead><tr><th>Foreground / background</th><th>Sample</th><th class="r">Ratio</th><th>Grade</th></tr></thead>
<tbody id="ctable"></tbody>
</table>

<h2>Typography</h2>
<p class="sec-sub">Serif for display and headings, sans for UI and body, mono for data, IDs, and audit hashes.</p>
<div id="typescale"></div>

<h2>Density</h2>
<p class="sec-sub">Density changes metric tokens only (<span class="mono">--row-h</span>, <span class="mono">--pad</span>, <span class="mono">--gap</span>) — never color or type. Left is comfortable (default), right is compact.</p>
<div class="density-demo">
<div class="dcard">
<div class="dh">Comfortable</div>
<div class="drow"><span>Ordinance 2026-14</span><span class="dchip" style="margin-left:auto">§2.1</span></div>
<div class="drow"><span>Ordinance 2026-15</span><span class="dchip" style="margin-left:auto">§2.2</span></div>
<div class="drow"><span>Ordinance 2026-16</span><span class="dchip" style="margin-left:auto">§2.3</span></div>
</div>
<div class="dcard" data-density="compact">
<div class="dh">Compact</div>
<div class="drow"><span>Ordinance 2026-14</span><span class="dchip" style="margin-left:auto">§2.1</span></div>
<div class="drow"><span>Ordinance 2026-15</span><span class="dchip" style="margin-left:auto">§2.2</span></div>
<div class="drow"><span>Ordinance 2026-16</span><span class="dchip" style="margin-left:auto">§2.3</span></div>
</div>
</div>
</div>

<script>
const neutral = [
['--paper', '#faf8f3', 'var(--ink)', 'Page background'],
['--paper-2', '#f3efe5', 'var(--ink)', 'Raised / alt surface'],
['--paper-3', '#ebe5d6', 'var(--ink)', 'Sunken surface'],
['--card', '#ffffff', 'var(--ink)', 'Primary surface'],
['--rule', '#e3dccb', 'var(--ink-2)', 'Hairline rule'],
['--rule-strong', '#cabe9f', 'var(--ink)', 'Emphasized rule'],
['--ink', '#1a2330', '#fff', 'Primary text · 15.0:1'],
['--ink-2', '#3a4554', '#fff', 'Secondary text · 9.2:1'],
['--ink-3', '#6b7280', '#fff', 'Tertiary text · 4.58:1 (floor)'],
['--ink-4', '#9aa0ab', '#fff', 'DECORATIVE ONLY · 2.48:1 — never text'],
];
const civic = [
['--navy', '#1a3a52', '#fff', 'Primary brand · white 11.9:1'],
['--navy-2', '#122a3d', '#fff', 'Pressed / darker'],
['--navy-soft', '#e6edf3', 'var(--navy)', 'Navy tint surface'],
['--gold', '#b08a2e', '#fff', 'Accent/border/LARGE text only · 3.22:1'],
['--gold-2', '#8a6a1f', '#fff', 'Text-bearing gold · white 5.05:1'],
['--gold-strong', '#785b16', '#fff', 'Gold text on pale gold · 5.42:1'],
['--gold-soft', '#f5edd6', 'var(--gold-strong)', 'Gold tint surface'],
['--seal', '#6b3a1f', '#fff', 'Seal red-brown'],
];
const status = [
['--ok', '#2f6b3a', '#fff', 'on --ok-soft 5.24:1'],
['--warn', '#8a5a14', '#fff', 'on --warn-soft 4.79:1'],
['--err', '#8a2a2a', '#fff', 'on --err-soft 6.40:1'],
['--info', '#1f4a6b', '#fff', 'on --info-soft 7.44:1'],
];
function render(id, rows) {
document.getElementById(id).innerHTML = rows.map(([name, hex, fg, note]) => `
<div class="sw">
<div class="chip" style="background:${hex}">
<span class="ratio" style="color:${fg.startsWith('var')?'':fg};background:${fg==='#fff'?'rgba(0,0,0,0.28)':'rgba(255,255,255,0.85)'};color:${fg==='#fff'?'#fff':'var(--ink-2)'}">${hex}</span>
</div>
<div class="meta">
<div class="name">${name}</div>
<div class="note">${note}</div>
</div>
</div>`).join('');
}
render('sw-neutral', neutral);
render('sw-civic', civic);
render('sw-status', status);

const ledger = [
['--ink on --paper', 'var(--paper)', 'var(--ink)', '15.0:1', 'aaa', 'AAA'],
['--ink-2 on --paper', 'var(--paper)', 'var(--ink-2)', '9.2:1', 'aaa', 'AAA'],
['--ink-3 on --paper', 'var(--paper)', 'var(--ink-3)', '4.58:1', 'pass', 'AA (floor)'],
['--ink-4 on --paper', 'var(--paper)', 'var(--ink-4)', '2.48:1', 'fail', 'FAIL — decorative only'],
['#fff on --navy', 'var(--navy)', '#fff', '11.9:1', 'aaa', 'AAA'],
['#fff on --gold', 'var(--gold)', '#fff', '3.22:1', 'fail', 'FAIL for body · OK large'],
['#fff on --gold-2', 'var(--gold-2)', '#fff', '5.05:1', 'pass', 'AA'],
['--gold-2 on --card', 'var(--card)', 'var(--gold-2)', '5.05:1', 'pass', 'AA'],
['--gold-2 on --gold-soft', 'var(--gold-soft)', 'var(--gold-2)', '4.31:1', 'fail', 'FAIL — use --gold-strong'],
['--gold-strong on --gold-soft', 'var(--gold-soft)', 'var(--gold-strong)', '5.42:1', 'pass', 'AA'],
['--ok on --ok-soft', 'var(--ok-soft)', 'var(--ok)', '5.24:1', 'pass', 'AA'],
['--warn on --warn-soft', 'var(--warn-soft)', 'var(--warn)', '4.79:1', 'pass', 'AA (tight)'],
['--err on --err-soft', 'var(--err-soft)', 'var(--err)', '6.40:1', 'pass', 'AA'],
['--info on --info-soft', 'var(--info-soft)', 'var(--info)', '7.44:1', 'pass', 'AA'],
];
document.getElementById('ctable').innerHTML = ledger.map(([pair, bg, fg, ratio, cls, grade]) => `
<tr>
<td class="pair">${pair}</td>
<td><span class="demo-cell" style="background:${bg};color:${fg}">Aa civic</span></td>
<td class="r">${ratio}</td>
<td><span class="tag ${cls==='fail'?'fail':cls==='large'?'large':cls==='aaa'?'aaa':'pass'}">${grade}</span></td>
</tr>`).join('');

const types = [
['Display · serif 34', 't-display', 'Meetings, agendas, minutes'],
['Title · serif 26', 't-title', 'Ordinance 2026-14'],
['Heading · serif 16', 't-h', 'Public notice checklist'],
['Body · sans 14', 't-body', 'The clerk drafts, a person reviews, the record decides.'],
['Small · sans 12', 't-small', 'Last verified 2026-07-03 · city-core beta'],
['Mono · 12', 't-mono', 'AUDIT#3f9a1c · §2.1 · v1.0.2'],
];
document.getElementById('typescale').innerHTML = types.map(([lbl, cls, sample]) => `
<div class="type-row"><span class="lbl">${lbl}</span><span class="${cls}">${sample}</span></div>`).join('');
</script>
</body>
</html>
Loading