rastrillo / fiesta Public

Clone
git clone https://amadan.net/rastrillo/fiesta

Plain git — no account needed to clone.

Download

Download this file

1/* signal — graphite and one live cobalt. Structure lives in tokens.css;
2 this file is colour, type and shape.
3
4 ── Direction ─────────────────────────────────────────────────────────
5 THESIS: contrast is spent, not sprinkled. One cobalt marks the place a
6 decision is being made and nothing else borrows it. The arrangement
7 this refuses is the default product-dashboard palette — mid-grey text,
8 an accent tint smeared across every surface, and pillowy corners that
9 make a destructive button look like a suggestion.
10 OWN-WORLD: an anodised instrument faceplate. Cool graphite neutrals
11 with a blue undertone, silk-screened labels, one cobalt indicator that
12 reads as powered. Corners are cut, not moulded — 4px and 2px, the
13 radius of a milled edge. Shadows are short and dense: a panel seated
14 on a panel, never floating over it. Type is grotesk-first, the
15 workshop label face rather than a display serif.
16 MODE: operate. Assertive is not the same as loud — the page is calm
17 graphite until something is actionable, and then it is unmistakable.
18 RISK, stated: near-black plus one bright accent is a well-worn look,
19 and the way this earns its own ground is restraint plus the shape
20 axis, not a brighter blue. If a screen ends up with cobalt in four
21 places, the theme is being used against its grain.
22 FINISH: every pair in both schemes is measured below and gated by
23 ui/contrast_test.go; the direction above is the contract the palette
24 is audited against.
25
26 ── One block, both schemes ───────────────────────────────────────────
27 Every token is declared once, as light-dark(<light>, <dark>), under a
28 single :root that sets color-scheme: light dark. The two rules at the
29 bottom are the whole toggle: setting color-scheme on
30 :root[data-theme=…] re-resolves every light-dark() in the file at
31 once, in both directions. Tokens with one value in both schemes — the
32 font stack, the radii — are written plain.
33
34 light-dark() is a colour function: it may only stand where a <color>
35 is expected. So a shadow token wraps its colour in it and writes the
36 geometry once, outside the call — `0 4px 10px light-dark(a, b)`, never
37 `light-dark(0 4px 10px a, …)`, which no browser would accept as a
38 box-shadow.
39
40 ── WCAG 2.2 AA, measured ─────────────────────────────────────────────
41 Normal text needs 4.5:1; large text and UI component boundaries need
42 3:1. Every pair below was computed from these exact hex values, in
43 both schemes. If you change a token, recompute its rows.
44
45 LIGHT ratio requirement
46 --rst-text on --rst-surface 19.32 4.5:1 text
47 --rst-text on --rst-bg 17.72 4.5:1 text
48 --rst-text on --rst-surface-2 16.93 4.5:1 text
49 --rst-text on --rst-accent-soft 16.68 4.5:1 text
50 --rst-text-muted on --rst-surface 8.76 4.5:1 text
51 --rst-text-muted on --rst-bg 8.04 4.5:1 text
52 --rst-text-muted on --rst-surface-2 7.68 4.5:1 text
53 --rst-text-muted on --rst-accent-soft 7.57 4.5:1 text
54 --rst-text-faint on --rst-surface 6.13 4.5:1 text
55 --rst-text-faint on --rst-bg 5.62 4.5:1 text
56 --rst-text-faint on --rst-surface-2 5.37 4.5:1 text
57 --rst-text-faint on --rst-accent-soft 5.29 4.5:1 text
58 --rst-accent on --rst-surface 5.50 4.5:1 text + focus ring
59 --rst-accent on --rst-bg 5.04 4.5:1 text + focus ring
60 --rst-accent on --rst-surface-2 4.82 4.5:1 text
61 --rst-accent on --rst-accent-soft 4.75 4.5:1 text
62 --rst-on-accent on --rst-accent 5.50 4.5:1 button label
63 --rst-on-accent on --rst-accent-strong 7.77 4.5:1 button label, hover
64 --rst-line-strong on --rst-surface 4.70 3:1 control border
65 --rst-line-strong on --rst-bg 4.31 3:1 control border
66 --rst-line-strong on --rst-surface-2 4.12 3:1 control border
67 pill neutral fg on its own tint 8.36 4.5:1 text
68 pill positive fg on its own tint 6.07 4.5:1 text
69 pill warning fg on its own tint 6.21 4.5:1 text
70 pill negative fg on its own tint 6.00 4.5:1 text
71 --rst-on-accent on negative fg (fill) 7.58 4.5:1 danger button label
72
73 DARK ratio requirement
74 --rst-text on --rst-surface 15.05 4.5:1 text
75 --rst-text on --rst-bg 16.30 4.5:1 text
76 --rst-text on --rst-surface-2 15.59 4.5:1 text
77 --rst-text on --rst-accent-soft 14.47 4.5:1 text
78 --rst-text-muted on --rst-surface 7.85 4.5:1 text
79 --rst-text-muted on --rst-bg 8.51 4.5:1 text
80 --rst-text-muted on --rst-surface-2 8.14 4.5:1 text
81 --rst-text-muted on --rst-accent-soft 7.55 4.5:1 text
82 --rst-text-faint on --rst-surface 6.58 4.5:1 text
83 --rst-text-faint on --rst-bg 7.13 4.5:1 text
84 --rst-text-faint on --rst-surface-2 6.82 4.5:1 text
85 --rst-text-faint on --rst-accent-soft 6.33 4.5:1 text
86 --rst-accent on --rst-surface 6.06 4.5:1 text + focus ring
87 --rst-accent on --rst-bg 6.56 4.5:1 text + focus ring
88 --rst-accent on --rst-surface-2 6.28 4.5:1 text
89 --rst-accent on --rst-accent-soft 5.82 4.5:1 text
90 --rst-on-accent on --rst-accent 6.79 4.5:1 button label
91 --rst-on-accent on --rst-accent-strong 9.77 4.5:1 button label, hover
92 --rst-line-strong on --rst-surface 4.65 3:1 control border
93 --rst-line-strong on --rst-bg 5.03 3:1 control border
94 --rst-line-strong on --rst-surface-2 4.81 3:1 control border
95 pill neutral fg on its own tint 9.26 4.5:1 text
96 pill positive fg on its own tint 9.07 4.5:1 text
97 pill warning fg on its own tint 10.00 4.5:1 text
98 pill negative fg on its own tint 7.84 4.5:1 text
99 --rst-on-accent on negative fg (fill) 9.25 4.5:1 danger button label
100
101 The tightest pair in the set is --rst-accent on --rst-accent-soft in
102 light, at 4.75:1 against a 4.5:1 floor — the cobalt is as bright as it
103 can be and still be readable as text on its own tint. Lighten it and
104 the gate goes red; that is the intended tension.
105
106 --rst-line is a hairline divider, not a control border, so 1.4.11's
107 3:1 does not apply to it. Control borders use --rst-line-strong.
108
109 --rst-header-rule is not in the table either, and deliberately so
110 (design doc §6-v2.2): the page header's rule is decoration, not
111 structure — the heading's size, weight and spacing carry the
112 structure — so no contrast floor applies to it and it is not in
113 ui/contrast_test.go's pair table. Do not argue it up to 3:1.
114
115 --rst-shadow-pop, --rst-shadow-knob, --rst-shadow-lift and
116 --rst-overlay are not in the table: they paint a translucent layer
117 over whatever sits underneath, not text over a fixed background, so a
118 contrast ratio does not apply.
119 ──────────────────────────────────────────────────────────────────── */
120
121:root {
122 color-scheme: light dark;
123
124 /* Type: grotesk first, on purpose. The stack asks for the neutral
125 workhorse grotesks a machine actually has before it falls back to
126 whatever the platform calls its UI face. No webfont: a theme is one
127 stylesheet and must not open a connection. */
128 --rst-font: "Helvetica Neue", Helvetica, "Segoe UI Variable Text", "Segoe UI", Roboto, Arial, ui-sans-serif, system-ui, sans-serif;
129
130 /* Shape: milled, not moulded. */
131 --rst-radius: 4px;
132 --rst-radius-sm: 2px;
133 --rst-radius-pill: 999px;
134
135 /* Surfaces and lines: graphite with a blue undertone, so the cobalt
136 reads as the only warm-running thing on the panel. */
137 --rst-bg: light-dark(#f4f5f8, #0b0d10);
138 --rst-surface: light-dark(#ffffff, #14171c);
139 --rst-surface-2: light-dark(#eef0f4, #101318);
140 --rst-line: light-dark(#dde0e7, #232830);
141 --rst-line-strong: light-dark(#6d7482, #7b828f);
142
143 /* The page header's rule, derived rather than authored: the cobalt
144 folded into the line at 45%, so a theme that changes its accent
145 gets a matching rule with no second value to keep in step. signal
146 spends the tint it earns — the rule is visibly blue, which is the
147 loudest this theme is anywhere it is not asking for a click.
148 Decorative: it carries no contrast floor. */
149 --rst-header-rule: color-mix(in oklab, var(--rst-accent) 45%, var(--rst-line));
150
151 /* Text */
152 --rst-text: light-dark(#0c0e12, #e9ebef);
153 --rst-text-muted: light-dark(#454b57, #a4acb9);
154 --rst-text-faint: light-dark(#5b6270, #959dac);
155
156 /* Accent: one electric cobalt. It appears where a decision is made and
157 nowhere else. */
158 --rst-accent: light-dark(#1a56ff, #6d90ff);
159 --rst-accent-strong: light-dark(#0f3fd6, #9ab2ff);
160 --rst-accent-soft: light-dark(#e9eeff, #121a30);
161 --rst-on-accent: light-dark(#ffffff, #05070d);
162
163 /* Status tones: saturated and unambiguous. A pill is never the
164 accent, so an error can never read as a link. */
165 --rst-tone-neutral-fg: light-dark(#3b414d, #c6cdd8);
166 --rst-tone-neutral-bg: light-dark(#e6e8ee, #232830);
167 --rst-tone-positive-fg: light-dark(#08624a, #4fdba2);
168 --rst-tone-positive-bg: light-dark(#d6efe6, #082720);
169 --rst-tone-warning-fg: light-dark(#7a4708, #f3c669);
170 --rst-tone-warning-bg: light-dark(#f8e5c2, #2a2009);
171 --rst-tone-negative-fg: light-dark(#a8121f, #ff8f99);
172 --rst-tone-negative-bg: light-dark(#fbdee0, #2e1418);
173
174 /* Depth: short throw, tight blur, high alpha. Seated, not floating. */
175 --rst-shadow-pop: 0 4px 10px light-dark(rgba(12, 14, 18, 0.22), rgba(0, 0, 0, 0.62));
176 --rst-shadow-knob: 0 1px 2px light-dark(rgba(12, 14, 18, 0.45), rgba(0, 0, 0, 0.6));
177 --rst-shadow-lift: 0 1px 2px light-dark(rgba(12, 14, 18, 0.2), rgba(0, 0, 0, 0.5));
178 --rst-overlay: light-dark(rgba(9, 11, 16, 0.55), rgba(3, 4, 7, 0.72));
179}
180
181/* The whole toggle. Setting color-scheme re-resolves every light-dark()
182 above, so an explicit choice beats the OS in both directions without
183 restating a colour. */
184:root[data-theme="light"] { color-scheme: light; }
185:root[data-theme="dark"] { color-scheme: dark; }
186