@ponchia/ui 0.3.5 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +945 -0
- package/README.md +31 -7
- package/behaviors/index.d.ts +11 -0
- package/behaviors/index.js +109 -0
- package/classes/index.d.ts +3 -0
- package/classes/index.js +3 -0
- package/classes/vscode.css-custom-data.json +4 -12
- package/css/dataviz.css +96 -0
- package/css/dots.css +70 -4
- package/css/skins.css +54 -0
- package/css/tokens.css +4 -10
- package/dist/bronto.css +1 -1
- package/dist/css/dataviz.css +1 -0
- package/dist/css/dots.css +1 -1
- package/dist/css/skins.css +1 -0
- package/dist/css/tokens.css +1 -1
- package/docs/adr/0001-color-system.md +272 -0
- package/docs/contrast.md +170 -51
- package/docs/reference.md +13 -9
- package/docs/theming.md +66 -1
- package/docs/usage.md +99 -0
- package/glyphs/glyphs.d.ts +115 -0
- package/glyphs/glyphs.js +1063 -0
- package/llms.txt +54 -4
- package/package.json +63 -5
- package/react/index.d.ts +36 -0
- package/react/index.js +67 -0
- package/solid/index.d.ts +36 -0
- package/solid/index.js +67 -0
- package/tokens/charts.d.ts +37 -0
- package/tokens/charts.js +96 -0
- package/tokens/charts.json +61 -0
- package/tokens/index.d.ts +3 -3
- package/tokens/index.js +4 -8
- package/tokens/index.json +8 -16
- package/tokens/resolved.json +9 -13
- package/tokens/skins.d.ts +27 -0
- package/tokens/skins.js +62 -0
- package/tokens/tokens.dtcg.json +4 -24
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
# ADR-0001 — Color system: governed evolution beyond monochrome
|
|
2
|
+
|
|
3
|
+
Status: accepted (all steps 1–8 implemented in 0.4.0) · 2026-05-31 ·
|
|
4
|
+
supersedes the informal "monochrome + one accent" framing in `README.md` /
|
|
5
|
+
`package.json`
|
|
6
|
+
|
|
7
|
+
> This is the **color constitution** for `@ponchia/ui`. It is forward-looking:
|
|
8
|
+
> nothing here changes the default build. It defines the tiers color is
|
|
9
|
+
> allowed to occupy, the rules that keep restraint load-bearing, and the
|
|
10
|
+
> ordered, backward-compatible path to expand expression without diluting the
|
|
11
|
+
> brand. Companion to [`architecture.md`](../architecture.md).
|
|
12
|
+
|
|
13
|
+
## Context
|
|
14
|
+
|
|
15
|
+
`@ponchia/ui` took its aesthetic from Nothing: monochrome surfaces, one
|
|
16
|
+
rationed red accent, dot-matrix motifs. The driving question for this ADR:
|
|
17
|
+
**is monochrome creatively restrictive, and should we evolve past Nothing's
|
|
18
|
+
literal one-hue constraint?**
|
|
19
|
+
|
|
20
|
+
Two facts frame the answer.
|
|
21
|
+
|
|
22
|
+
**1. We are already not monochrome — and that was the right call.** The README
|
|
23
|
+
says "monochrome, one rationed accent." The CSS says otherwise. We already
|
|
24
|
+
ship a full **functional status palette** (`--success`, `--warning`,
|
|
25
|
+
`--danger`, `--info`, each with light/dark/`-soft` variants, contrast-tuned
|
|
26
|
+
per theme, wired into `ui-alert` / `ui-toast` / `ui-meter`). It is rationed —
|
|
27
|
+
status-only, always paired with a leading dot/shape, never decorative — but it
|
|
28
|
+
exists. So the real model already has **three *active* tiers nobody wrote
|
|
29
|
+
down** (neutral + accent + status); the two new axes this ADR adds (display
|
|
30
|
+
expression, data-viz) are tiers 3–4. The question is not "should we abandon
|
|
31
|
+
monochrome" (we did, carefully, around 0.3.4) but **"what is our color theory
|
|
32
|
+
for the next axis."**
|
|
33
|
+
|
|
34
|
+
**2. Inspiration is not doctrine.** The thing worth inheriting from Nothing is
|
|
35
|
+
the *attitude* — restraint, intentionality, signal-over-noise — **not** the
|
|
36
|
+
literal one-hue limit. "We do not decorate with color; we signal with it" is a
|
|
37
|
+
stronger, more defensible principle than "we have no color," and we are most
|
|
38
|
+
of the way to it already. Monochrome *is* creatively restrictive, but only on
|
|
39
|
+
three specific axes: **categorical distinction** (tags/legends past ~3 items),
|
|
40
|
+
**multi-series data-viz** (one accent + grays cannot draw a 5-line chart — we
|
|
41
|
+
already felt this; `tokens/resolved.json` exists because charts needed real
|
|
42
|
+
color), and **brand/emotional range** (one hue at a time). What monochrome
|
|
43
|
+
*buys* and we must not casually spend: identity differentiation, forced
|
|
44
|
+
hierarchy discipline, accessibility-by-default, and the scarcity that makes the
|
|
45
|
+
one accent *mean* something.
|
|
46
|
+
|
|
47
|
+
### What SOTA looks like (2025–2026)
|
|
48
|
+
|
|
49
|
+
| Practice | Who | Relevance to us |
|
|
50
|
+
| --- | --- | --- |
|
|
51
|
+
| **Perceptual color space** (equal numeric step = equal perceived step) | Tailwind v4 (OKLCH+P3), Radix 3 (P3), Material 3 (HCT) | We derive `--accent-1..6` via `color-mix(in srgb,…)`; OKLCH is the modern substrate and is **CSS-native** — no dependency cost |
|
|
52
|
+
| **Relative color syntax** `oklch(from var(--base) calc(l-.1) c h)` | CSS Color 5 | ~90% support, Baseline in 2026 — lets a single hue derive a whole tonal ramp natively |
|
|
53
|
+
| **WCAG 2.2 to pass, APCA to design** | Radix text steps spec'd in APCA Lc | WCAG 2.2 AA stays the legal/hard gate; APCA (Lc, accounts for size/weight, WCAG 3 candidate) as an **advisory** track |
|
|
54
|
+
| **Role-bound scale steps** (1–2 bg, 3–5 component, 6–8 border, 9–10 solid, 11–12 text) | Radix 12-step | Our tiers are coarser; the role-bound scale is where depth lives if we ever need it |
|
|
55
|
+
| **Contrast-first generation** (pick a target Lc, derive the swatch) | Adobe Leonardo, Material dynamic | Natural evolution of "one `--accent` derives everything" |
|
|
56
|
+
| **Categorical color is its own discipline** | Wong / IBM / ColorBrewer | Hard limit **6–8 hues**, ≥30 CIELAB lightness delta per pair, **redundant encoding** (shape/pattern/label) — our dot-matrix wheelhouse |
|
|
57
|
+
|
|
58
|
+
SOTA is **not "more hues by default."** It is CSS-native token systems,
|
|
59
|
+
perceptual color, role-based scales, contrast-aware generation, and *governed
|
|
60
|
+
escape hatches*. Our restraint is already SOTA-adjacent; the work is to
|
|
61
|
+
modernize the engine and add opt-in expression without spending the brand.
|
|
62
|
+
|
|
63
|
+
## Decision
|
|
64
|
+
|
|
65
|
+
Adopt a **five-tier color model** with a fixed governance contract. Color must
|
|
66
|
+
*earn its place by encoding meaning*; the discipline moves from "absence of
|
|
67
|
+
color" to "color is tiered, token-only, contrast-gated, and off-by-default."
|
|
68
|
+
|
|
69
|
+
| Tier | Name | What it is | Default state |
|
|
70
|
+
| --- | --- | --- | --- |
|
|
71
|
+
| **0** | Neutral canvas | The grayscale ramp — surfaces, lines, text | Always on |
|
|
72
|
+
| **1** | Brand accent | One themeable `--accent`, derives `--accent-1..6` | Always on (red) |
|
|
73
|
+
| **2** | Functional status | `success` / `warning` / `danger` / `info` — signal only | Always on |
|
|
74
|
+
| **3** | Display expression | Luminance, dot density/intensity, scan cadence, pulse, reveal timing — the dot-matrix substrate | Opt-in tokens |
|
|
75
|
+
| **4** | Categorical / data-viz | A 6–8 color colorblind-safe scale + sequential/diverging ramps — **charts only** | Opt-in module, never bundled |
|
|
76
|
+
|
|
77
|
+
### The six rules (the constitution)
|
|
78
|
+
|
|
79
|
+
Each rule notes how it is held — **[gate]** = mechanically enforced by
|
|
80
|
+
`check:color-policy`; **[other gate]** = enforced elsewhere in `npm run check`
|
|
81
|
+
/ the e2e suite; **[norm]** = a review norm, not yet machine-checked. Don't
|
|
82
|
+
read "rule" as "all six are CI-enforced" — only what's tagged **[gate]** is.
|
|
83
|
+
|
|
84
|
+
1. **Token-only.** No raw chromatic color in component CSS — every color is a
|
|
85
|
+
tiered token (grays / `color-mix()` endpoints excepted). **[gate]** —
|
|
86
|
+
`check:color-policy` invariants 1 (tier classification) + 3 (raw-color scan).
|
|
87
|
+
2. **Contrast-gated.** Every UI text/bg pairing passes **WCAG 2.2 AA**; **APCA
|
|
88
|
+
Lc is advisory** beside the matrix, never the sole gate while WCAG 3 is a
|
|
89
|
+
Working Draft. **[other gate]** — `check:contrast` + the e2e axe suite, not
|
|
90
|
+
`check:color-policy`.
|
|
91
|
+
3. **Off-by-default.** The default red accent, all token names, and all visual
|
|
92
|
+
baselines remain **unchanged**; the strict-mono purist keeps their exact
|
|
93
|
+
system; everything new is opt-in. **[other gate]** — `check:dist`
|
|
94
|
+
byte-equality + visual baselines.
|
|
95
|
+
4. **Semantic tier names.** A token's name declares its tier. **Status is never
|
|
96
|
+
decorative**; **chart color is never UI chrome.** No `primary/secondary`.
|
|
97
|
+
**[gate]** for "every color token is tiered" + the reserved `--chart/--cat/
|
|
98
|
+
--data-*` namespace (invariants 1–2); **[norm]** for "status never
|
|
99
|
+
decorative" (needs usage context a static gate can't see).
|
|
100
|
+
5. **One active UI accent per scope.** No second brand accent. "Duotone" lives
|
|
101
|
+
in Tier 3 (dots/display ornament), not as a second interactive accent.
|
|
102
|
+
**[norm]**.
|
|
103
|
+
6. **Meaning never by color or motion alone.** Redundant encoding (shape, dot,
|
|
104
|
+
label) is mandatory — satisfies WCAG 1.4.1 / 2.2.2 and is on-brand for the
|
|
105
|
+
dot-matrix. Reduced-motion users must get equivalent information statically.
|
|
106
|
+
**[other gate]** — the e2e axe/`forced-colors`/reduced-motion suite.
|
|
107
|
+
|
|
108
|
+
### The brand's answer to "decorative color": luminance + time
|
|
109
|
+
|
|
110
|
+
For a *dot-matrix display* system, the authentic expressive channel is not
|
|
111
|
+
hue — it is **brightness and motion**, exactly like a real LED/phosphor panel
|
|
112
|
+
(and we already shipped reveal/pulse in 0.3.6). Tier 3 is the strategic
|
|
113
|
+
substitute for decorative polychrome: it deepens identity instead of diluting
|
|
114
|
+
it, and no competitor in this niche owns it.
|
|
115
|
+
|
|
116
|
+
### Resolving the `--orange` orphan — RESOLVED (removed, 0.4.0)
|
|
117
|
+
|
|
118
|
+
Audit finding: `--orange` / `--orange-soft` were defined in every token file
|
|
119
|
+
(`tokens/index.js`, `.d.ts`, `tokens.dtcg.json`, `resolved.json`,
|
|
120
|
+
`css/tokens.css`, `reference.md`) but **consumed by no component and documented
|
|
121
|
+
nowhere** — an untiered, dangling hue. Resolution: **removed** (not adopted as
|
|
122
|
+
a categorical seed — there is no data-viz module or demand yet, and a stray
|
|
123
|
+
top-level hue contradicts the tier model). If categorical color lands it ships
|
|
124
|
+
as a governed Tier-4 module (step 7), not a loose token. The new
|
|
125
|
+
`check:color-policy` gate now makes a future untiered hue a hard CI failure.
|
|
126
|
+
|
|
127
|
+
## Backward-compatibility freeze
|
|
128
|
+
|
|
129
|
+
Hard acceptance criteria for every step below:
|
|
130
|
+
|
|
131
|
+
- **Default rendered output is unchanged** — the red accent defaults, the
|
|
132
|
+
resolved value of every token a component references, and the light/dark
|
|
133
|
+
**visual baselines** are identical. (Removing a token nothing resolves —
|
|
134
|
+
e.g. `--orange` in 0.4.0 — does change `dist/bronto.css` *bytes* but not what
|
|
135
|
+
any selector renders; that is cleanup, not a visual change. The earlier draft
|
|
136
|
+
said "dist bytes unchanged" — the precise invariant is *rendered output*.)
|
|
137
|
+
- Token **names** are not renamed/repurposed (removals of provably-unreferenced
|
|
138
|
+
tokens follow the CONTRIBUTING.md deprecation-policy exception).
|
|
139
|
+
- The existing `--accent-1..6` **sRGB `color-mix` derivation is not silently
|
|
140
|
+
changed.** `scripts/gen-resolved.mjs` only evaluates `color-mix(in srgb,…)`;
|
|
141
|
+
for any other space it returns `null`, and a `null` token is **dropped
|
|
142
|
+
entirely** from `resolved.json` (not defaulted) — so a consumer doing
|
|
143
|
+
`resolved.dark['--accent-3']` would get `undefined` and may throw. Combined
|
|
144
|
+
with the rendered-pixel change, switching the ramp to OKLCH is a breaking
|
|
145
|
+
change twice over. Stable names alone do not make it non-breaking. Any OKLCH
|
|
146
|
+
migration of the **existing** ramp is a documented **minor/RC** decision
|
|
147
|
+
after tooling + visual review, never a silent patch.
|
|
148
|
+
|
|
149
|
+
## Roadmap (ordered, backward-compatible)
|
|
150
|
+
|
|
151
|
+
1. **This ADR.** Make the constitution explicit before adding surface. *(done)*
|
|
152
|
+
2. **`check:color-policy` gate.** Fail on: raw chromatic color in component CSS;
|
|
153
|
+
any untiered palette token; use of the reserved `--chart/--cat/--data-*`
|
|
154
|
+
namespace. Resolve `--orange`. *(done — `scripts/check-color-policy.mjs`,
|
|
155
|
+
wired into `npm run check`; `--orange` removed)*
|
|
156
|
+
3. **Tier 3 expression tokens.** *(done — `css/dots.css`)* `--dotmatrix-glow`
|
|
157
|
+
(phosphor bloom on lit cells), `--dotmatrix-pulse-min` (the `--pulse` floor),
|
|
158
|
+
`--dotmatrix-reveal-step` (scan cadence); density stays on
|
|
159
|
+
`--dotmatrix-cols/-gap/-dot`. All default to a no-op, so the default render
|
|
160
|
+
is byte-for-byte unchanged; reduced-motion still kills the animations.
|
|
161
|
+
4. **Optional display colorways** *(done — `tokens/skins.js` →
|
|
162
|
+
`css/skins.css`, exported as `./css/skins.css`, NOT in the default bundle)*:
|
|
163
|
+
`data-bronto-skin="amber-crt | phosphor-green | e-ink"`. **A root-level
|
|
164
|
+
choice, like `data-theme`** (apply on `:root`/`<html>`) — it must live there
|
|
165
|
+
because the accent's derived family (`--accent-strong/-text/-soft`,
|
|
166
|
+
`--bg-accent`, `--field-dot-accent`, `--accent-1..6`) is `color-mix(…
|
|
167
|
+
var(--accent) …)` declared on `:root` and only re-evaluates against the new
|
|
168
|
+
accent on the element that carries it; a skin on a subtree would leave that
|
|
169
|
+
family stale, so the selectors are `:root`-anchored and a subtree skin
|
|
170
|
+
no-ops. Each skin re-points only `--accent` (+ a dark `--dotmatrix-glow`);
|
|
171
|
+
the family + dot-matrix + glyphs follow automatically. Single-hue per skin →
|
|
172
|
+
keeps the one-accent discipline while unlocking range across skins. No
|
|
173
|
+
multi-hue palette. (A future per-subtree skin would need to re-declare the
|
|
174
|
+
derived family — deferred; whole-page is the natural colorway scope.) Skins
|
|
175
|
+
inherit the existing `forced-colors` + `print` behavior in `css/base.css` /
|
|
176
|
+
`css/forms.css` (they touch only the accent, never the canvas).
|
|
177
|
+
5. **OKLCH for new work first.** *(done for colorways — accents authored in
|
|
178
|
+
OKLCH in `tokens/skins.js`; the contrast tooling now parses `oklch()` →
|
|
179
|
+
sRGB, so skin accents are gated, not eyeballed.)* The **core** `--accent-1..6`
|
|
180
|
+
ramp stays sRGB (decision below). For any future OKLCH in contractual token
|
|
181
|
+
files (the core ramp), still upgrade `gen-resolved` first and add a
|
|
182
|
+
gamut-aware fallback (`@supports`, sRGB-first), and consider `light-dark()`
|
|
183
|
+
to collapse the twice-written dark palette — both are in the same CSS
|
|
184
|
+
Color 5 / Baseline-2026 bracket.
|
|
185
|
+
6. **APCA advisory reporting.** *(done — `scripts/gen-contrast.mjs`)* APCA-W3
|
|
186
|
+
0.1.9 `Lc` is computed beside the WCAG ratio for every pairing (core + skins)
|
|
187
|
+
in `docs/contrast.md`. **Advisory only** — WCAG 2.1 AA stays the hard gate.
|
|
188
|
+
7. **Categorical chart module.** *(done in 0.4.0 — real demand arrived
|
|
189
|
+
(dashboards); `tokens/charts.js` → `css/dataviz.css` + `tokens/charts.json`
|
|
190
|
+
+ `tokens/charts.d.ts`, gated by `check:charts`.)* Hybrid accent-led
|
|
191
|
+
(series 1 = `var(--accent)`) + Okabe-Ito colourblind-safe series 2–8 + a
|
|
192
|
+
sequential and a diverging ramp; **dot-matrix `--chart-pattern-*` fills** as
|
|
193
|
+
the mandated second channel. Gated for distinguishability under normal +
|
|
194
|
+
simulated protan/deutan/tritan vision (OKLab ΔE), `var(--chart-*)` forbidden
|
|
195
|
+
in core component CSS (charts-only), never in the default bundle. The
|
|
196
|
+
categorical fills are authored as the Okabe-Ito sRGB hex **verbatim** (a
|
|
197
|
+
CVD-proven *set* — re-deriving per-theme in OKLCH broke its colourblind
|
|
198
|
+
safety, which the gate caught); the ramps are OKLCH. A chart colour's WCAG
|
|
199
|
+
ratio vs the background is reported **advisory** (a fill is not body text;
|
|
200
|
+
the pattern + ΔE gate carry distinguishability).
|
|
201
|
+
8. **OKLCH `--accent-1..6` ramp migration.** *(done in 0.4.0)* Steps 1–4 mix
|
|
202
|
+
the accent toward `--bg` `in oklch` (perceptually even). `gen-resolved` was
|
|
203
|
+
upgraded first to resolve `color-mix(in oklch,…)` → hex (bit-for-bit matching
|
|
204
|
+
the browser, incl. powerless-hue for near-neutral endpoints), so the
|
|
205
|
+
contractual `resolved.json`/DTCG never drop the tokens. Verified no rendered
|
|
206
|
+
change: `--accent-1..6` is consumed by **no** component (only JS/chart
|
|
207
|
+
consumers + the theme playground), so no visual baseline moved — the feared
|
|
208
|
+
"breaking + needs baseline review" turned out to touch only token *values*
|
|
209
|
+
(non-contractual under 0.x).
|
|
210
|
+
|
|
211
|
+
## Risks & mitigations
|
|
212
|
+
|
|
213
|
+
| Risk | Mitigation |
|
|
214
|
+
| --- | --- |
|
|
215
|
+
| **Brand dilution** (secondary accents, decorative hues creep in) | Rule 4/5; `check:color-policy`; off-by-default |
|
|
216
|
+
| **Compat break** from OKLCH ramp swap | Freeze above; OKLCH for *new* work first; minor/RC only, never silent |
|
|
217
|
+
| **Tooling drift** (resolver/DTCG/contrast/baseline assume sRGB) | Upgrade tooling *before* OKLCH enters token files; add computed-style tests |
|
|
218
|
+
| **Chart color leakage** into UI chrome | `check:color-policy` forbids chart tokens in core CSS; docs forbid it |
|
|
219
|
+
| **Motion/status misuse** carrying sole meaning | Rule 6; reduced-motion equivalence; status stays locked, never skin-derived |
|
|
220
|
+
| **APCA overreach** | Advisory only; WCAG 2.2 AA stays the hard gate while WCAG 3 is draft |
|
|
221
|
+
|
|
222
|
+
## Verification plan
|
|
223
|
+
|
|
224
|
+
- Existing `npm run check`, `npm test`, token/DTCG/resolved/dist/visual/a11y
|
|
225
|
+
gates stay green and **unchanged** for the default build.
|
|
226
|
+
- New `check:color-policy` (raw color, status misuse, chart leakage, untiered
|
|
227
|
+
hue).
|
|
228
|
+
- Contrast matrices extended to: default light/dark, `prefers-contrast`,
|
|
229
|
+
`forced-colors`, **every shipped skin**, and chart tokens if shipped.
|
|
230
|
+
- APCA output as non-blocking generated docs.
|
|
231
|
+
- Playwright computed-style tests for `oklch()` / `color-mix(in oklch,…)`
|
|
232
|
+
before any core reliance.
|
|
233
|
+
- Verify `prefers-reduced-motion`, `prefers-contrast`, `forced-colors`, and
|
|
234
|
+
native `accent-color` for every skin.
|
|
235
|
+
- Confirm optional skin/chart CSS is exported + packed but **excluded** from
|
|
236
|
+
default `dist/bronto.css`.
|
|
237
|
+
|
|
238
|
+
## Decisions taken (0.4.0)
|
|
239
|
+
|
|
240
|
+
Resolved while implementing steps 3–6 (the open questions the earlier draft
|
|
241
|
+
flagged):
|
|
242
|
+
|
|
243
|
+
- **`--accent-1..6` is semver-stable and stays sRGB.** No silent ramp change.
|
|
244
|
+
→ step 8 (default-ramp OKLCH migration) **deferred** — visual-diff review
|
|
245
|
+
cost outweighs the gain; reconsider as an opt-in engine / explicit minor.
|
|
246
|
+
- **Colorways ship as a supported package export** (`./css/skins.css`),
|
|
247
|
+
opt-in, excluded from the default bundle. Not docs-only recipes.
|
|
248
|
+
- **Colorways are root-level** (`data-bronto-skin` on `:root`, like
|
|
249
|
+
`data-theme`) — see step 4 for why per-subtree is deferred.
|
|
250
|
+
- **OKLCH is within the existing browser floor** (Chrome 111+/Safari 16.4+/
|
|
251
|
+
Firefox 121+ all support `oklch()`); used for skin accents now. The contrast
|
|
252
|
+
tooling parses it so skins are gated.
|
|
253
|
+
- **Build tooling stays dependency-free.** APCA + the OKLCH→sRGB conversion are
|
|
254
|
+
hand-rolled in `scripts/gen-contrast.mjs` (zero new deps), matching the
|
|
255
|
+
zero-dependency stance.
|
|
256
|
+
- **Data-viz categorical color (step 7) shipped in 0.4.0** — real demand
|
|
257
|
+
arrived (dashboards). Built as a governed Tier-4 module (opt-in, charts-only,
|
|
258
|
+
leakage-gated, CVD-distinguishability-gated). `--cat/--data-*` remain reserved
|
|
259
|
+
alternative-name guards; `--chart-*` is now the live data-viz namespace.
|
|
260
|
+
- **No second UI accent / "duotone" stays Tier-3 ornament only** (rule 5).
|
|
261
|
+
|
|
262
|
+
## Method
|
|
263
|
+
|
|
264
|
+
Grounded in web research (OKLCH/relative-color/`color-mix` baseline; APCA vs
|
|
265
|
+
WCAG; Radix 12-step; Material 3 HCT; Adobe Leonardo; colorblind-safe
|
|
266
|
+
categorical limits) and a 14-leg AgentMix `deep` multi-POV pass
|
|
267
|
+
(Pi/MiniMax, Aider, OpenCode, Crush, Codex + architecture / implementation-risk
|
|
268
|
+
/ testing / security / operations / contrarian legs). Key reconciled dissent:
|
|
269
|
+
several legs wanted an immediate OKLCH ramp swap — rejected as a silent break
|
|
270
|
+
(see freeze); the claim that `--accent-1..6` already serves as a categorical
|
|
271
|
+
scale was rejected (a same-hue ramp encodes sequence/intensity, not independent
|
|
272
|
+
categories); deriving status from skins was rejected (status stays locked).
|
package/docs/contrast.md
CHANGED
|
@@ -38,63 +38,182 @@ Overall: **all contractual pairings meet their floor ✅**.
|
|
|
38
38
|
|
|
39
39
|
## Light theme
|
|
40
40
|
|
|
41
|
-
| Foreground | Background | Role | Held to | Ratio | Verdict |
|
|
42
|
-
| --- | --- | --- | --- | --- | --- |
|
|
43
|
-
| `--text` | `--bg` | Body text on page background | AA text (4.5:1) | 17.98:1 | ✅ pass |
|
|
44
|
-
| `--text` | `--surface` | Body text on a card/panel | AA text (4.5:1) | 19.80:1 | ✅ pass |
|
|
45
|
-
| `--text` | `--surface-muted` | Body text on a muted panel | AA text (4.5:1) | 16.74:1 | ✅ pass |
|
|
46
|
-
| `--text-soft` | `--bg` | Secondary text on page background | AA text (4.5:1) | 11.16:1 | ✅ pass |
|
|
47
|
-
| `--text-soft` | `--surface` | Secondary text on a card | AA text (4.5:1) | 12.29:1 | ✅ pass |
|
|
48
|
-
| `--text-soft` | `--surface-muted` | Secondary text on a muted panel | AA text (4.5:1) | 10.39:1 | ✅ pass |
|
|
49
|
-
| `--text-dim` | `--bg` | Dim/meta text on page background | AA text (4.5:1) | 5.09:1 | ✅ pass |
|
|
50
|
-
| `--text-dim` | `--surface` | Dim/meta text on a card | AA text (4.5:1) | 5.60:1 | ✅ pass |
|
|
51
|
-
| `--text-dim` | `--surface-muted` | Dim/meta text on a muted panel | AA text (4.5:1) | 4.74:1 | ✅ pass |
|
|
52
|
-
| `--accent-text` | `--bg` | Accent text on page background | AA text (4.5:1) | 6.32:1 | ✅ pass |
|
|
53
|
-
| `--accent-text` | `--surface` | Accent text on a card | AA text (4.5:1) | 6.96:1 | ✅ pass |
|
|
54
|
-
| `--button-text` | `--accent` | Label on the primary button | AA text (4.5:1) | 5.18:1 | ✅ pass |
|
|
55
|
-
| `--focus-ring` | `--bg` | Focus ring vs page background | UI / large (3:1) | 4.71:1 | ✅ pass |
|
|
56
|
-
| `--focus-ring` | `--surface` | Focus ring vs a card | UI / large (3:1) | 5.18:1 | ✅ pass |
|
|
57
|
-
| `--accent` | `--bg` | Accent fill vs page background | UI / large (3:1) | 4.71:1 | ✅ pass |
|
|
58
|
-
| `--success` | `--surface` | Success indicator vs a card | UI / large (3:1) | 5.04:1 | ✅ pass |
|
|
59
|
-
| `--warning` | `--surface` | Warning indicator vs a card | UI / large (3:1) | 5.60:1 | ✅ pass |
|
|
60
|
-
| `--danger` | `--surface` | Danger indicator vs a card | UI / large (3:1) | 6.21:1 | ✅ pass |
|
|
61
|
-
| `--info` | `--surface` | Info indicator vs a card | UI / large (3:1) | 5.77:1 | ✅ pass |
|
|
62
|
-
| `--line-strong` | `--surface` | Strong hairline vs a card | Decorative (1.4.11-exempt) | 2.39:1 | ℹ️ not gated |
|
|
41
|
+
| Foreground | Background | Role | Held to | Ratio | APCA _(advisory)_ | Verdict |
|
|
42
|
+
| --- | --- | --- | --- | --- | --- | --- |
|
|
43
|
+
| `--text` | `--bg` | Body text on page background | AA text (4.5:1) | 17.98:1 | Lc 99 | ✅ pass |
|
|
44
|
+
| `--text` | `--surface` | Body text on a card/panel | AA text (4.5:1) | 19.80:1 | Lc 106 | ✅ pass |
|
|
45
|
+
| `--text` | `--surface-muted` | Body text on a muted panel | AA text (4.5:1) | 16.74:1 | Lc 94 | ✅ pass |
|
|
46
|
+
| `--text-soft` | `--bg` | Secondary text on page background | AA text (4.5:1) | 11.16:1 | Lc 91 | ✅ pass |
|
|
47
|
+
| `--text-soft` | `--surface` | Secondary text on a card | AA text (4.5:1) | 12.29:1 | Lc 98 | ✅ pass |
|
|
48
|
+
| `--text-soft` | `--surface-muted` | Secondary text on a muted panel | AA text (4.5:1) | 10.39:1 | Lc 87 | ✅ pass |
|
|
49
|
+
| `--text-dim` | `--bg` | Dim/meta text on page background | AA text (4.5:1) | 5.09:1 | Lc 71 | ✅ pass |
|
|
50
|
+
| `--text-dim` | `--surface` | Dim/meta text on a card | AA text (4.5:1) | 5.60:1 | Lc 78 | ✅ pass |
|
|
51
|
+
| `--text-dim` | `--surface-muted` | Dim/meta text on a muted panel | AA text (4.5:1) | 4.74:1 | Lc 67 | ✅ pass |
|
|
52
|
+
| `--accent-text` | `--bg` | Accent text on page background | AA text (4.5:1) | 6.32:1 | Lc 75 | ✅ pass |
|
|
53
|
+
| `--accent-text` | `--surface` | Accent text on a card | AA text (4.5:1) | 6.96:1 | Lc 82 | ✅ pass |
|
|
54
|
+
| `--button-text` | `--accent` | Label on the primary button | AA text (4.5:1) | 5.18:1 | Lc 79 | ✅ pass |
|
|
55
|
+
| `--focus-ring` | `--bg` | Focus ring vs page background | UI / large (3:1) | 4.71:1 | Lc 67 | ✅ pass |
|
|
56
|
+
| `--focus-ring` | `--surface` | Focus ring vs a card | UI / large (3:1) | 5.18:1 | Lc 73 | ✅ pass |
|
|
57
|
+
| `--accent` | `--bg` | Accent fill vs page background | UI / large (3:1) | 4.71:1 | Lc 67 | ✅ pass |
|
|
58
|
+
| `--success` | `--surface` | Success indicator vs a card | UI / large (3:1) | 5.04:1 | Lc 75 | ✅ pass |
|
|
59
|
+
| `--warning` | `--surface` | Warning indicator vs a card | UI / large (3:1) | 5.60:1 | Lc 78 | ✅ pass |
|
|
60
|
+
| `--danger` | `--surface` | Danger indicator vs a card | UI / large (3:1) | 6.21:1 | Lc 79 | ✅ pass |
|
|
61
|
+
| `--info` | `--surface` | Info indicator vs a card | UI / large (3:1) | 5.77:1 | Lc 78 | ✅ pass |
|
|
62
|
+
| `--line-strong` | `--surface` | Strong hairline vs a card | Decorative (1.4.11-exempt) | 2.39:1 | Lc 47 | ℹ️ not gated |
|
|
63
63
|
|
|
64
64
|
## Dark theme
|
|
65
65
|
|
|
66
|
-
| Foreground | Background | Role | Held to | Ratio | Verdict |
|
|
67
|
-
| --- | --- | --- | --- | --- | --- |
|
|
68
|
-
| `--text` | `--bg` | Body text on page background | AA text (4.5:1) | 18.76:1 | ✅ pass |
|
|
69
|
-
| `--text` | `--surface` | Body text on a card/panel | AA text (4.5:1) | 17.47:1 | ✅ pass |
|
|
70
|
-
| `--text` | `--surface-muted` | Body text on a muted panel | AA text (4.5:1) | 15.55:1 | ✅ pass |
|
|
71
|
-
| `--text-soft` | `--bg` | Secondary text on page background | AA text (4.5:1) | 12.04:1 | ✅ pass |
|
|
72
|
-
| `--text-soft` | `--surface` | Secondary text on a card | AA text (4.5:1) | 11.22:1 | ✅ pass |
|
|
73
|
-
| `--text-soft` | `--surface-muted` | Secondary text on a muted panel | AA text (4.5:1) | 9.98:1 | ✅ pass |
|
|
74
|
-
| `--text-dim` | `--bg` | Dim/meta text on page background | AA text (4.5:1) | 5.69:1 | ✅ pass |
|
|
75
|
-
| `--text-dim` | `--surface` | Dim/meta text on a card | AA text (4.5:1) | 5.30:1 | ✅ pass |
|
|
76
|
-
| `--text-dim` | `--surface-muted` | Dim/meta text on a muted panel | AA text (4.5:1) | 4.72:1 | ✅ pass |
|
|
77
|
-
| `--accent-text` | `--bg` | Accent text on page background | AA text (4.5:1) | 6.88:1 | ✅ pass |
|
|
78
|
-
| `--accent-text` | `--surface` | Accent text on a card | AA text (4.5:1) | 6.41:1 | ✅ pass |
|
|
79
|
-
| `--button-text` | `--accent` | Label on the primary button | AA text (4.5:1) | 5.95:1 | ✅ pass |
|
|
80
|
-
| `--focus-ring` | `--bg` | Focus ring vs page background | UI / large (3:1) | 5.95:1 | ✅ pass |
|
|
81
|
-
| `--focus-ring` | `--surface` | Focus ring vs a card | UI / large (3:1) | 5.55:1 | ✅ pass |
|
|
82
|
-
| `--accent` | `--bg` | Accent fill vs page background | UI / large (3:1) | 5.95:1 | ✅ pass |
|
|
83
|
-
| `--success` | `--surface` | Success indicator vs a card | UI / large (3:1) | 8.70:1 | ✅ pass |
|
|
84
|
-
| `--warning` | `--surface` | Warning indicator vs a card | UI / large (3:1) | 10.66:1 | ✅ pass |
|
|
85
|
-
| `--danger` | `--surface` | Danger indicator vs a card | UI / large (3:1) | 6.00:1 | ✅ pass |
|
|
86
|
-
| `--info` | `--surface` | Info indicator vs a card | UI / large (3:1) | 8.41:1 | ✅ pass |
|
|
87
|
-
| `--line-strong` | `--surface` | Strong hairline vs a card | Decorative (1.4.11-exempt) | 2.01:1 | ℹ️ not gated |
|
|
66
|
+
| Foreground | Background | Role | Held to | Ratio | APCA _(advisory)_ | Verdict |
|
|
67
|
+
| --- | --- | --- | --- | --- | --- | --- |
|
|
68
|
+
| `--text` | `--bg` | Body text on page background | AA text (4.5:1) | 18.76:1 | Lc 99 | ✅ pass |
|
|
69
|
+
| `--text` | `--surface` | Body text on a card/panel | AA text (4.5:1) | 17.47:1 | Lc 99 | ✅ pass |
|
|
70
|
+
| `--text` | `--surface-muted` | Body text on a muted panel | AA text (4.5:1) | 15.55:1 | Lc 98 | ✅ pass |
|
|
71
|
+
| `--text-soft` | `--bg` | Secondary text on page background | AA text (4.5:1) | 12.04:1 | Lc 71 | ✅ pass |
|
|
72
|
+
| `--text-soft` | `--surface` | Secondary text on a card | AA text (4.5:1) | 11.22:1 | Lc 71 | ✅ pass |
|
|
73
|
+
| `--text-soft` | `--surface-muted` | Secondary text on a muted panel | AA text (4.5:1) | 9.98:1 | Lc 70 | ✅ pass |
|
|
74
|
+
| `--text-dim` | `--bg` | Dim/meta text on page background | AA text (4.5:1) | 5.69:1 | Lc 37 | ✅ pass |
|
|
75
|
+
| `--text-dim` | `--surface` | Dim/meta text on a card | AA text (4.5:1) | 5.30:1 | Lc 37 | ✅ pass |
|
|
76
|
+
| `--text-dim` | `--surface-muted` | Dim/meta text on a muted panel | AA text (4.5:1) | 4.72:1 | Lc 36 | ✅ pass |
|
|
77
|
+
| `--accent-text` | `--bg` | Accent text on page background | AA text (4.5:1) | 6.88:1 | Lc 45 | ✅ pass |
|
|
78
|
+
| `--accent-text` | `--surface` | Accent text on a card | AA text (4.5:1) | 6.41:1 | Lc 45 | ✅ pass |
|
|
79
|
+
| `--button-text` | `--accent` | Label on the primary button | AA text (4.5:1) | 5.95:1 | Lc 43 | ✅ pass |
|
|
80
|
+
| `--focus-ring` | `--bg` | Focus ring vs page background | UI / large (3:1) | 5.95:1 | Lc 41 | ✅ pass |
|
|
81
|
+
| `--focus-ring` | `--surface` | Focus ring vs a card | UI / large (3:1) | 5.55:1 | Lc 40 | ✅ pass |
|
|
82
|
+
| `--accent` | `--bg` | Accent fill vs page background | UI / large (3:1) | 5.95:1 | Lc 41 | ✅ pass |
|
|
83
|
+
| `--success` | `--surface` | Success indicator vs a card | UI / large (3:1) | 8.70:1 | Lc 58 | ✅ pass |
|
|
84
|
+
| `--warning` | `--surface` | Warning indicator vs a card | UI / large (3:1) | 10.66:1 | Lc 68 | ✅ pass |
|
|
85
|
+
| `--danger` | `--surface` | Danger indicator vs a card | UI / large (3:1) | 6.00:1 | Lc 43 | ✅ pass |
|
|
86
|
+
| `--info` | `--surface` | Info indicator vs a card | UI / large (3:1) | 8.41:1 | Lc 56 | ✅ pass |
|
|
87
|
+
| `--line-strong` | `--surface` | Strong hairline vs a card | Decorative (1.4.11-exempt) | 2.01:1 | Lc 10 | ℹ️ not gated |
|
|
88
|
+
|
|
89
|
+
## Display colorways (skins)
|
|
90
|
+
|
|
91
|
+
The opt-in `data-bronto-skin` colorways (`@ponchia/ui/css/skins.css`,
|
|
92
|
+
authored in `tokens/skins.js`) re-point the one accent to a different single
|
|
93
|
+
hue. Because a shipped colorway is part of the framework — not an arbitrary
|
|
94
|
+
consumer re-brand — **its accent is gated to the same floors as the core**:
|
|
95
|
+
the primary-button label, accent-as-text, and the focus ring/accent fill. Only
|
|
96
|
+
the accent-touching pairings are shown (a skin leaves the canvas and status
|
|
97
|
+
palette untouched). Accents are authored in OKLCH; `--accent-text` is the
|
|
98
|
+
`color-mix`-derived `--accent-strong`, exactly as in the core palette.
|
|
99
|
+
|
|
100
|
+
### Amber CRT — light
|
|
101
|
+
|
|
102
|
+
| Foreground | Background | Role | Held to | Ratio | APCA _(advisory)_ | Verdict |
|
|
103
|
+
| --- | --- | --- | --- | --- | --- | --- |
|
|
104
|
+
| `--accent-text` | `--bg` | Accent text on page background | AA text (4.5:1) | 6.75:1 | Lc 79 | ✅ pass |
|
|
105
|
+
| `--accent-text` | `--surface` | Accent text on a card | AA text (4.5:1) | 7.44:1 | Lc 86 | ✅ pass |
|
|
106
|
+
| `--button-text` | `--accent` | Label on the primary button | AA text (4.5:1) | 5.66:1 | Lc 83 | ✅ pass |
|
|
107
|
+
| `--focus-ring` | `--bg` | Focus ring vs page background | UI / large (3:1) | 5.14:1 | Lc 71 | ✅ pass |
|
|
108
|
+
| `--focus-ring` | `--surface` | Focus ring vs a card | UI / large (3:1) | 5.66:1 | Lc 78 | ✅ pass |
|
|
109
|
+
| `--accent` | `--bg` | Accent fill vs page background | UI / large (3:1) | 5.14:1 | Lc 71 | ✅ pass |
|
|
110
|
+
|
|
111
|
+
### Amber CRT — dark
|
|
112
|
+
|
|
113
|
+
| Foreground | Background | Role | Held to | Ratio | APCA _(advisory)_ | Verdict |
|
|
114
|
+
| --- | --- | --- | --- | --- | --- | --- |
|
|
115
|
+
| `--accent-text` | `--bg` | Accent text on page background | AA text (4.5:1) | 12.97:1 | Lc 75 | ✅ pass |
|
|
116
|
+
| `--accent-text` | `--surface` | Accent text on a card | AA text (4.5:1) | 12.08:1 | Lc 75 | ✅ pass |
|
|
117
|
+
| `--button-text` | `--accent` | Label on the primary button | AA text (4.5:1) | 11.88:1 | Lc 72 | ✅ pass |
|
|
118
|
+
| `--focus-ring` | `--bg` | Focus ring vs page background | UI / large (3:1) | 11.88:1 | Lc 70 | ✅ pass |
|
|
119
|
+
| `--focus-ring` | `--surface` | Focus ring vs a card | UI / large (3:1) | 11.07:1 | Lc 70 | ✅ pass |
|
|
120
|
+
| `--accent` | `--bg` | Accent fill vs page background | UI / large (3:1) | 11.88:1 | Lc 70 | ✅ pass |
|
|
121
|
+
|
|
122
|
+
### E-ink — light
|
|
123
|
+
|
|
124
|
+
| Foreground | Background | Role | Held to | Ratio | APCA _(advisory)_ | Verdict |
|
|
125
|
+
| --- | --- | --- | --- | --- | --- | --- |
|
|
126
|
+
| `--accent-text` | `--bg` | Accent text on page background | AA text (4.5:1) | 12.23:1 | Lc 93 | ✅ pass |
|
|
127
|
+
| `--accent-text` | `--surface` | Accent text on a card | AA text (4.5:1) | 13.47:1 | Lc 100 | ✅ pass |
|
|
128
|
+
| `--button-text` | `--accent` | Label on the primary button | AA text (4.5:1) | 11.74:1 | Lc 101 | ✅ pass |
|
|
129
|
+
| `--focus-ring` | `--bg` | Focus ring vs page background | UI / large (3:1) | 10.66:1 | Lc 90 | ✅ pass |
|
|
130
|
+
| `--focus-ring` | `--surface` | Focus ring vs a card | UI / large (3:1) | 11.74:1 | Lc 97 | ✅ pass |
|
|
131
|
+
| `--accent` | `--bg` | Accent fill vs page background | UI / large (3:1) | 10.66:1 | Lc 90 | ✅ pass |
|
|
132
|
+
|
|
133
|
+
### E-ink — dark
|
|
134
|
+
|
|
135
|
+
| Foreground | Background | Role | Held to | Ratio | APCA _(advisory)_ | Verdict |
|
|
136
|
+
| --- | --- | --- | --- | --- | --- | --- |
|
|
137
|
+
| `--accent-text` | `--bg` | Accent text on page background | AA text (4.5:1) | 13.98:1 | Lc 80 | ✅ pass |
|
|
138
|
+
| `--accent-text` | `--surface` | Accent text on a card | AA text (4.5:1) | 13.02:1 | Lc 79 | ✅ pass |
|
|
139
|
+
| `--button-text` | `--accent` | Label on the primary button | AA text (4.5:1) | 12.86:1 | Lc 76 | ✅ pass |
|
|
140
|
+
| `--focus-ring` | `--bg` | Focus ring vs page background | UI / large (3:1) | 12.86:1 | Lc 75 | ✅ pass |
|
|
141
|
+
| `--focus-ring` | `--surface` | Focus ring vs a card | UI / large (3:1) | 11.98:1 | Lc 74 | ✅ pass |
|
|
142
|
+
| `--accent` | `--bg` | Accent fill vs page background | UI / large (3:1) | 12.86:1 | Lc 75 | ✅ pass |
|
|
143
|
+
|
|
144
|
+
### Phosphor Green — light
|
|
145
|
+
|
|
146
|
+
| Foreground | Background | Role | Held to | Ratio | APCA _(advisory)_ | Verdict |
|
|
147
|
+
| --- | --- | --- | --- | --- | --- | --- |
|
|
148
|
+
| `--accent-text` | `--bg` | Accent text on page background | AA text (4.5:1) | 6.22:1 | Lc 77 | ✅ pass |
|
|
149
|
+
| `--accent-text` | `--surface` | Accent text on a card | AA text (4.5:1) | 6.85:1 | Lc 83 | ✅ pass |
|
|
150
|
+
| `--button-text` | `--accent` | Label on the primary button | AA text (4.5:1) | 5.19:1 | Lc 81 | ✅ pass |
|
|
151
|
+
| `--focus-ring` | `--bg` | Focus ring vs page background | UI / large (3:1) | 4.71:1 | Lc 69 | ✅ pass |
|
|
152
|
+
| `--focus-ring` | `--surface` | Focus ring vs a card | UI / large (3:1) | 5.19:1 | Lc 75 | ✅ pass |
|
|
153
|
+
| `--accent` | `--bg` | Accent fill vs page background | UI / large (3:1) | 4.71:1 | Lc 69 | ✅ pass |
|
|
154
|
+
|
|
155
|
+
### Phosphor Green — dark
|
|
156
|
+
|
|
157
|
+
| Foreground | Background | Role | Held to | Ratio | APCA _(advisory)_ | Verdict |
|
|
158
|
+
| --- | --- | --- | --- | --- | --- | --- |
|
|
159
|
+
| `--accent-text` | `--bg` | Accent text on page background | AA text (4.5:1) | 14.54:1 | Lc 82 | ✅ pass |
|
|
160
|
+
| `--accent-text` | `--surface` | Accent text on a card | AA text (4.5:1) | 13.55:1 | Lc 82 | ✅ pass |
|
|
161
|
+
| `--button-text` | `--accent` | Label on the primary button | AA text (4.5:1) | 13.75:1 | Lc 80 | ✅ pass |
|
|
162
|
+
| `--focus-ring` | `--bg` | Focus ring vs page background | UI / large (3:1) | 13.75:1 | Lc 79 | ✅ pass |
|
|
163
|
+
| `--focus-ring` | `--surface` | Focus ring vs a card | UI / large (3:1) | 12.81:1 | Lc 79 | ✅ pass |
|
|
164
|
+
| `--accent` | `--bg` | Accent fill vs page background | UI / large (3:1) | 13.75:1 | Lc 79 | ✅ pass |
|
|
165
|
+
|
|
166
|
+
## Data-viz palette (advisory)
|
|
167
|
+
|
|
168
|
+
The opt-in Tier-4 chart palette (`@ponchia/ui/css/dataviz.css`, authored in
|
|
169
|
+
`tokens/charts.js`) is gated differently: categorical series are held to
|
|
170
|
+
**mutual distinguishability under normal + simulated protan/deutan/tritan
|
|
171
|
+
vision** (`check:charts`, OKLab ΔE), and colour is **never the sole signal** —
|
|
172
|
+
each series ships a matching `--chart-pattern-*` dot-matrix fill. So the
|
|
173
|
+
WCAG ratios below are **advisory** (a chart fill is not body text); use them to
|
|
174
|
+
pick a darker series for thin lines/points, or rely on the pattern. Series 1 is
|
|
175
|
+
the brand accent.
|
|
176
|
+
|
|
177
|
+
### Light theme — categorical vs `--bg`
|
|
178
|
+
|
|
179
|
+
| Series | Colour | Ratio _(advisory)_ | APCA _(advisory)_ |
|
|
180
|
+
| --- | --- | --- | --- |
|
|
181
|
+
| 1 _(accent)_ | `#d71921` | 4.71:1 | Lc 67 |
|
|
182
|
+
| 2 | `#e69f00` | 2.05:1 | Lc 37 |
|
|
183
|
+
| 3 | `#56b4e9` | 2.10:1 | Lc 39 |
|
|
184
|
+
| 4 | `#009e73` | 3.11:1 | Lc 54 |
|
|
185
|
+
| 5 | `#f0e442` | 1.20:1 | Lc 9 |
|
|
186
|
+
| 6 | `#0072b2` | 4.71:1 | Lc 68 |
|
|
187
|
+
| 7 | `#cc79a7` | 2.78:1 | Lc 51 |
|
|
188
|
+
| 8 | `#4d5358` | 7.08:1 | Lc 80 |
|
|
189
|
+
|
|
190
|
+
### Dark theme — categorical vs `--bg`
|
|
191
|
+
|
|
192
|
+
| Series | Colour | Ratio _(advisory)_ | APCA _(advisory)_ |
|
|
193
|
+
| --- | --- | --- | --- |
|
|
194
|
+
| 1 _(accent)_ | `#ff3b41` | 5.95:1 | Lc 41 |
|
|
195
|
+
| 2 | `#e69f00` | 9.32:1 | Lc 58 |
|
|
196
|
+
| 3 | `#56b4e9` | 9.10:1 | Lc 57 |
|
|
197
|
+
| 4 | `#009e73` | 6.14:1 | Lc 41 |
|
|
198
|
+
| 5 | `#f0e442` | 15.88:1 | Lc 88 |
|
|
199
|
+
| 6 | `#0072b2` | 4.05:1 | Lc 27 |
|
|
200
|
+
| 7 | `#cc79a7` | 6.86:1 | Lc 45 |
|
|
201
|
+
| 8 | `#4d5358` | 2.69:1 | Lc 15 |
|
|
88
202
|
|
|
89
203
|
## Scope & caveats
|
|
90
204
|
|
|
91
|
-
- This gates the **framework token contract
|
|
92
|
-
re-brands. Re-accenting via
|
|
93
|
-
(see [theming.md](theming.md)
|
|
94
|
-
the guarantee for the *shipped*
|
|
205
|
+
- This gates the **framework token contract** (core palette **and** every
|
|
206
|
+
shipped colorway), not arbitrary consumer re-brands. Re-accenting via
|
|
207
|
+
`--accent` yourself is your contrast obligation (see [theming.md](theming.md)
|
|
208
|
+
→ "Re-brand obligations"); this table is the guarantee for the *shipped*
|
|
209
|
+
palettes and skins only.
|
|
95
210
|
- Status colours (`--success` / `--warning` / `--danger`) are gated
|
|
96
211
|
as **indicators** (3:1). If you set status text *as body copy* on a
|
|
97
212
|
surface, verify 4.5:1 yourself or pair it with an icon/label.
|
|
98
|
-
-
|
|
99
|
-
|
|
100
|
-
|
|
213
|
+
- The gated **Ratio** is sRGB WCAG 2.1 AA — the testable legal / axe-compatible
|
|
214
|
+
baseline the e2e suite also asserts. The **APCA** column (APCA-W3 0.1.9 `Lc`)
|
|
215
|
+
is **advisory only**: a perceptual cross-check that, unlike WCAG 2.1's
|
|
216
|
+
symmetric ratio, accounts for polarity and is the WCAG 3 candidate. It does
|
|
217
|
+
**not** gate the build while WCAG 3 is a Working Draft — use it to tune
|
|
218
|
+
quality, not to override the WCAG 2.1 floor. As a rough read, body text wants
|
|
219
|
+
`Lc` ≥ 75 and non-text/large ≥ 45–60.
|
package/docs/reference.md
CHANGED
|
@@ -9,7 +9,7 @@ rendering of every class is the kitchen-sink demo:
|
|
|
9
9
|
**<https://ponchia.github.io/bronto-ui/>**. Theming knobs and the token
|
|
10
10
|
contract: [docs/theming.md](theming.md).
|
|
11
11
|
|
|
12
|
-
-
|
|
12
|
+
- 263 classes across 110 component groups
|
|
13
13
|
- Import the typed registry: `import { cls, ui, cx } from '@ponchia/ui/classes'`
|
|
14
14
|
- Tokens as data: `import { cssVars, tokens, themeColor } from '@ponchia/ui/tokens'`
|
|
15
15
|
|
|
@@ -329,6 +329,8 @@ each one matches a real selector in the stylesheet.
|
|
|
329
329
|
| `cls.dotmatrixCell` | `ui-dotmatrix__cell` | part |
|
|
330
330
|
| `cls.dotmatrixCellAccent` | `ui-dotmatrix__cell--accent` | modifier |
|
|
331
331
|
| `cls.dotmatrixCellHot` | `ui-dotmatrix__cell--hot` | modifier |
|
|
332
|
+
| `cls.dotmatrixPulse` | `ui-dotmatrix--pulse` | modifier |
|
|
333
|
+
| `cls.dotmatrixReveal` | `ui-dotmatrix--reveal` | modifier |
|
|
332
334
|
|
|
333
335
|
### `.ui-dotrule`
|
|
334
336
|
|
|
@@ -391,6 +393,12 @@ each one matches a real selector in the stylesheet.
|
|
|
391
393
|
| `cls.hint` | `ui-hint` | base |
|
|
392
394
|
| `cls.hintError` | `ui-hint--error` | modifier |
|
|
393
395
|
|
|
396
|
+
### `.ui-icon`
|
|
397
|
+
|
|
398
|
+
| Registry key | Class | Kind |
|
|
399
|
+
| --- | --- | --- |
|
|
400
|
+
| `cls.icon` | `ui-icon` | base |
|
|
401
|
+
|
|
394
402
|
### `.ui-input`
|
|
395
403
|
|
|
396
404
|
| Registry key | Class | Kind |
|
|
@@ -911,10 +919,10 @@ Exact mirror of the `:root` blocks in `css/tokens.css`
|
|
|
911
919
|
| `--z-overlay` | `30` |
|
|
912
920
|
| `--z-popover` | `50` |
|
|
913
921
|
| `--z-toast` | `60` |
|
|
914
|
-
| `--accent-1` | `color-mix(in
|
|
915
|
-
| `--accent-2` | `color-mix(in
|
|
916
|
-
| `--accent-3` | `color-mix(in
|
|
917
|
-
| `--accent-4` | `color-mix(in
|
|
922
|
+
| `--accent-1` | `color-mix(in oklch, var(--accent) 8%, var(--bg))` |
|
|
923
|
+
| `--accent-2` | `color-mix(in oklch, var(--accent) 16%, var(--bg))` |
|
|
924
|
+
| `--accent-3` | `color-mix(in oklch, var(--accent) 32%, var(--bg))` |
|
|
925
|
+
| `--accent-4` | `color-mix(in oklch, var(--accent) 60%, var(--bg))` |
|
|
918
926
|
| `--accent-5` | `var(--accent)` |
|
|
919
927
|
| `--accent-6` | `var(--accent-strong)` |
|
|
920
928
|
| `--surface-1` | `var(--bg)` |
|
|
@@ -966,8 +974,6 @@ Exact mirror of the `:root` blocks in `css/tokens.css`
|
|
|
966
974
|
| `--success-soft` | `rgb(47, 125, 79, 0.12)` |
|
|
967
975
|
| `--warning` | `#806414` |
|
|
968
976
|
| `--warning-soft` | `rgb(128, 100, 20, 0.13)` |
|
|
969
|
-
| `--orange` | `#a85f32` |
|
|
970
|
-
| `--orange-soft` | `rgb(168, 95, 50, 0.13)` |
|
|
971
977
|
| `--danger` | `#c01622` |
|
|
972
978
|
| `--danger-soft` | `rgb(192, 22, 34, 0.1)` |
|
|
973
979
|
| `--info` | `#1f63c4` |
|
|
@@ -1004,8 +1010,6 @@ Exact mirror of the `:root` blocks in `css/tokens.css`
|
|
|
1004
1010
|
| `--success-soft` | `rgb(78, 194, 126, 0.14)` |
|
|
1005
1011
|
| `--warning` | `#d8bd72` |
|
|
1006
1012
|
| `--warning-soft` | `rgb(216, 189, 114, 0.14)` |
|
|
1007
|
-
| `--orange` | `#d08c5b` |
|
|
1008
|
-
| `--orange-soft` | `rgb(208, 140, 91, 0.15)` |
|
|
1009
1013
|
| `--danger` | `#ff4d54` |
|
|
1010
1014
|
| `--danger-soft` | `rgb(255, 77, 84, 0.15)` |
|
|
1011
1015
|
| `--info` | `#6fb0e6` |
|
package/docs/theming.md
CHANGED
|
@@ -141,7 +141,72 @@ coarser-grained handles.
|
|
|
141
141
|
these; override one to slot your app's own layers around the
|
|
142
142
|
framework's without specificity/`z-index` wars.
|
|
143
143
|
|
|
144
|
-
All four tiers are in the DTCG export and the JS token model.
|
|
144
|
+
All four tiers are in the DTCG export and the JS token model. The full
|
|
145
|
+
five-tier colour model and its rules live in
|
|
146
|
+
[ADR-0001](adr/0001-color-system.md).
|
|
147
|
+
|
|
148
|
+
## Display colorways (`data-bronto-skin`)
|
|
149
|
+
|
|
150
|
+
Opt-in single-hue colorways, shipped as a **separate** entrypoint (never in
|
|
151
|
+
the default bundle):
|
|
152
|
+
|
|
153
|
+
```html
|
|
154
|
+
<link rel="stylesheet" href="@ponchia/ui/css/skins.css" />
|
|
155
|
+
<html data-theme="dark" data-bronto-skin="phosphor-green">
|
|
156
|
+
…
|
|
157
|
+
</html>
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
`data-bronto-skin="amber-crt | phosphor-green | e-ink"` is a **root-level**
|
|
161
|
+
choice, like `data-theme` — put it on `:root`/`<html>`. It re-points the one
|
|
162
|
+
`--accent` (per theme, authored in OKLCH); the derived family, focus ring,
|
|
163
|
+
dot-matrix and glyphs follow automatically, and status colours + the neutral
|
|
164
|
+
canvas are untouched. A colorway is _not_ a second accent — it swaps the one
|
|
165
|
+
you have, so the one-accent discipline holds.
|
|
166
|
+
|
|
167
|
+
- **Root-level only.** The accent's derived family (`--accent-strong`/`-text`,
|
|
168
|
+
`--field-dot-accent`, `--accent-1..6`, …) is `color-mix(… var(--accent) …)`
|
|
169
|
+
declared on `:root`; it only re-evaluates on the element that carries it. A
|
|
170
|
+
skin on a subtree would leave that family stale, so the selectors are
|
|
171
|
+
`:root`-anchored and a subtree skin simply no-ops.
|
|
172
|
+
- **Phosphor bloom.** The `amber-crt` / `phosphor-green` skins set
|
|
173
|
+
`--dotmatrix-glow` (a Tier-3 display knob, default `0`) in dark, so the
|
|
174
|
+
dot-matrix gains a CRT-style glow. Set it yourself on any `.ui-dotmatrix`
|
|
175
|
+
to tune the bloom.
|
|
176
|
+
- **Contrast-gated.** Every shipped skin accent meets the same WCAG AA / 3:1
|
|
177
|
+
floors as the core palette — see [contrast.md](contrast.md) → "Display
|
|
178
|
+
colorways". (Your _own_ `--accent` re-brand is still your obligation; the
|
|
179
|
+
guarantee covers the shipped palettes and skins.)
|
|
180
|
+
|
|
181
|
+
## Data-viz palette (`@ponchia/ui/css/dataviz.css`)
|
|
182
|
+
|
|
183
|
+
Opt-in Tier-4 chart colours for dashboards — **charts only, never UI chrome**
|
|
184
|
+
(a build gate fails on `var(--chart-*)` in component CSS), and never in the
|
|
185
|
+
default bundle.
|
|
186
|
+
|
|
187
|
+
```html
|
|
188
|
+
<link rel="stylesheet" href="@ponchia/ui/css/dataviz.css" />
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
```js
|
|
192
|
+
// resolved hex for canvas / SVG / Chart.js etc.
|
|
193
|
+
import charts from '@ponchia/ui/charts.json' with { type: 'json' };
|
|
194
|
+
const series = charts.dark.categorical; // ['#ff3b41', '#e69f00', …] — series 0 = accent
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
- **Categorical `--chart-1..8`** — hybrid accent-led: series 1 is the live
|
|
198
|
+
`var(--accent)` (your brand leads), series 2–8 are the Okabe-Ito
|
|
199
|
+
colourblind-safe set. The set is **gated for mutual distinguishability under
|
|
200
|
+
normal + simulated protanopia/deuteranopia/tritanopia** (OKLab ΔE).
|
|
201
|
+
- **Sequential `--chart-seq-1..6`** — single-hue light→dark, for
|
|
202
|
+
heatmaps/intensity. **Diverging `--chart-div-1..7`** — blue↔neutral↔orange,
|
|
203
|
+
for ±/gains-losses.
|
|
204
|
+
- **Pattern fills `--chart-pattern-1..8`** — a dot-matrix second channel so
|
|
205
|
+
colour is never the sole signal (WCAG 1.4.1). Pair colour N with pattern N:
|
|
206
|
+
`background: var(--chart-2); background-image: var(--chart-pattern-2); background-size: var(--chart-pattern-size); --chart-pattern-ink: rgb(0 0 0 / .34);`
|
|
207
|
+
- A chart colour's WCAG ratio vs the background is published **advisory** in
|
|
208
|
+
[contrast.md](contrast.md) (a fill is not body text) — pick a darker series
|
|
209
|
+
for thin lines/points, or lean on the pattern.
|
|
145
210
|
|
|
146
211
|
## Accessibility markup contracts
|
|
147
212
|
|