@adminiumjs/tokens 0.1.0-rc.1 → 0.1.2

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/LICENSES/FONTS.md CHANGED
@@ -14,4 +14,4 @@ and applies to the copies in `src/fonts/`. The fonts are unmodified upstream bui
14
14
  permits bundling and redistribution with attribution and prohibits selling the fonts standalone.
15
15
 
16
16
  CJK locales (`zh_CN`/`zh_TW`) intentionally use system font stacks (see `src/fonts.css`) and vendor
17
- no CJK fonts — see workplan/02-design-system.md §2.4 and workplan/11-electron.md Open decisions.
17
+ no CJK fonts — see 02-design-system.md §2.4 and 11-electron.md Open decisions.
package/dist/index.d.ts CHANGED
@@ -2,26 +2,53 @@
2
2
  * @adminium/tokens — JS-side constants for the four theming axes plus the pre-hydration
3
3
  * script. CSS custom properties live in the sibling .css files; these exports exist so
4
4
  * ThemeProvider, Storybook toolbars, charts and the desktop shell never hard-code axis
5
- * values. See workplan/02-design-system.md §2.5.
5
+ * values. See 02-design-system.md §2.5.
6
6
  */
7
7
  /** Theme axis. `system` resolves via `matchMedia("(prefers-color-scheme: dark)")`. */
8
8
  export declare const THEMES: readonly ["light", "dark", "system"];
9
9
  export type ThemePref = (typeof THEMES)[number];
10
10
  /** A resolved theme as stamped on `<html data-theme>`. */
11
11
  export type ResolvedTheme = Exclude<ThemePref, "system">;
12
- /** Accent axis: the 8 switchable palettes (`data-accent`), name → hex. */
12
+ /**
13
+ * Accent axis: the 8 switchable palettes (`data-accent`), name → LIGHT-theme hex
14
+ * (`--accent-light` in accents.css).
15
+ *
16
+ * This is ONE HALF of the accent. Each palette also has an `--accent-dark` hex that the dark
17
+ * theme selects into `--accent`, and it is a different colour, drastically so for `black`
18
+ * (#111111 light, #c9c9d4 dark). Anything that PAINTS an accent must therefore either read the
19
+ * CSS variable — which already resolves per theme — or pick from {@link ACCENTS_DARK} when the
20
+ * resolved theme is dark. Rendering `ACCENTS[a]` unconditionally shows the wrong colour on dark;
21
+ * that is what the accent-picker swatch used to do.
22
+ */
13
23
  export declare const ACCENTS: {
14
24
  readonly indigo: "#4f46e5";
15
- readonly blue: "#2563eb";
16
- readonly teal: "#0d9488";
17
- readonly violet: "#7c3aed";
18
- readonly rose: "#e11d48";
19
- readonly red: "#e5484d";
20
- readonly orange: "#ea580c";
25
+ readonly blue: "#1c59e0";
26
+ readonly teal: "#007167";
27
+ readonly violet: "#7936e9";
28
+ readonly rose: "#c40039";
29
+ readonly red: "#c0202f";
30
+ readonly orange: "#b03e00";
21
31
  readonly black: "#111111";
22
32
  };
33
+ /**
34
+ * The dark half (`--accent-dark`). Derived from the light ramp by recipe — see the header of
35
+ * accents.css, which owns the values; these mirror them for JS that cannot read CSS (chart
36
+ * canvases, the desktop shell, docs tables).
37
+ */
38
+ export declare const ACCENTS_DARK: {
39
+ readonly indigo: "#99a5ff";
40
+ readonly blue: "#7fabff";
41
+ readonly teal: "#49bbae";
42
+ readonly violet: "#b39dff";
43
+ readonly rose: "#ff848c";
44
+ readonly red: "#ff8582";
45
+ readonly orange: "#ff885b";
46
+ readonly black: "#c9c9d4";
47
+ };
23
48
  /** `indigo` is the default. */
24
49
  export type Accent = keyof typeof ACCENTS;
50
+ /** The accent hex actually painted for a resolved theme. */
51
+ export declare function accentHex(accent: Accent, theme: ResolvedTheme): string;
25
52
  /** Density axis (`data-density`). */
26
53
  export declare const DENSITIES: readonly ["comfortable", "compact"];
27
54
  export type Density = (typeof DENSITIES)[number];
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,sFAAsF;AACtF,eAAO,MAAM,MAAM,sCAAuC,CAAC;AAC3D,MAAM,MAAM,SAAS,GAAG,CAAC,OAAO,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC;AAChD,0DAA0D;AAC1D,MAAM,MAAM,aAAa,GAAG,OAAO,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;AAEzD,0EAA0E;AAC1E,eAAO,MAAM,OAAO;;;;;;;;;CASV,CAAC;AACX,+BAA+B;AAC/B,MAAM,MAAM,MAAM,GAAG,MAAM,OAAO,OAAO,CAAC;AAE1C,qCAAqC;AACrC,eAAO,MAAM,SAAS,qCAAsC,CAAC;AAC7D,MAAM,MAAM,OAAO,GAAG,CAAC,OAAO,SAAS,CAAC,CAAC,MAAM,CAAC,CAAC;AAEjD,4EAA4E;AAC5E,eAAO,MAAM,IAAI,yBAA0B,CAAC;AAC5C,MAAM,MAAM,GAAG,GAAG,CAAC,OAAO,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC;AAExC,mFAAmF;AACnF,eAAO,MAAM,WAAW,mGASd,CAAC;AAEX,uEAAuE;AACvE,eAAO,MAAM,eAAe,4CAA6C,CAAC;AAE1E;;;;GAIG;AACH,eAAO,MAAM,gBAAgB;;;;;;CAMnB,CAAC;AAEX;;;GAGG;AACH,eAAO,MAAM,YAAY;;;;;;CAMf,CAAC;AAEX,8DAA8D;AAC9D,eAAO,MAAM,aAAa;;;;;CAKhB,CAAC;AAEX,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,sFAAsF;AACtF,eAAO,MAAM,MAAM,sCAAuC,CAAC;AAC3D,MAAM,MAAM,SAAS,GAAG,CAAC,OAAO,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC;AAChD,0DAA0D;AAC1D,MAAM,MAAM,aAAa,GAAG,OAAO,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;AAEzD;;;;;;;;;;GAUG;AACH,eAAO,MAAM,OAAO;;;;;;;;;CASV,CAAC;AAEX;;;;GAIG;AACH,eAAO,MAAM,YAAY;;;;;;;;;CASf,CAAC;AAEX,+BAA+B;AAC/B,MAAM,MAAM,MAAM,GAAG,MAAM,OAAO,OAAO,CAAC;AAE1C,4DAA4D;AAC5D,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,GAAG,MAAM,CAEtE;AAED,qCAAqC;AACrC,eAAO,MAAM,SAAS,qCAAsC,CAAC;AAC7D,MAAM,MAAM,OAAO,GAAG,CAAC,OAAO,SAAS,CAAC,CAAC,MAAM,CAAC,CAAC;AAEjD,4EAA4E;AAC5E,eAAO,MAAM,IAAI,yBAA0B,CAAC;AAC5C,MAAM,MAAM,GAAG,GAAG,CAAC,OAAO,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC;AAExC,mFAAmF;AACnF,eAAO,MAAM,WAAW,mGASd,CAAC;AAEX,uEAAuE;AACvE,eAAO,MAAM,eAAe,4CAA6C,CAAC;AAE1E;;;;GAIG;AACH,eAAO,MAAM,gBAAgB;;;;;;CAMnB,CAAC;AAEX;;;GAGG;AACH,eAAO,MAAM,YAAY;;;;;;CAMf,CAAC;AAEX,8DAA8D;AAC9D,eAAO,MAAM,aAAa;;;;;CAKhB,CAAC;AAEX,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC"}
package/dist/index.js CHANGED
@@ -2,21 +2,50 @@
2
2
  * @adminium/tokens — JS-side constants for the four theming axes plus the pre-hydration
3
3
  * script. CSS custom properties live in the sibling .css files; these exports exist so
4
4
  * ThemeProvider, Storybook toolbars, charts and the desktop shell never hard-code axis
5
- * values. See workplan/02-design-system.md §2.5.
5
+ * values. See 02-design-system.md §2.5.
6
6
  */
7
7
  /** Theme axis. `system` resolves via `matchMedia("(prefers-color-scheme: dark)")`. */
8
8
  export const THEMES = ["light", "dark", "system"];
9
- /** Accent axis: the 8 switchable palettes (`data-accent`), name → hex. */
9
+ /**
10
+ * Accent axis: the 8 switchable palettes (`data-accent`), name → LIGHT-theme hex
11
+ * (`--accent-light` in accents.css).
12
+ *
13
+ * This is ONE HALF of the accent. Each palette also has an `--accent-dark` hex that the dark
14
+ * theme selects into `--accent`, and it is a different colour, drastically so for `black`
15
+ * (#111111 light, #c9c9d4 dark). Anything that PAINTS an accent must therefore either read the
16
+ * CSS variable — which already resolves per theme — or pick from {@link ACCENTS_DARK} when the
17
+ * resolved theme is dark. Rendering `ACCENTS[a]` unconditionally shows the wrong colour on dark;
18
+ * that is what the accent-picker swatch used to do.
19
+ */
10
20
  export const ACCENTS = {
11
21
  indigo: "#4f46e5",
12
- blue: "#2563eb",
13
- teal: "#0d9488",
14
- violet: "#7c3aed",
15
- rose: "#e11d48",
16
- red: "#e5484d",
17
- orange: "#ea580c",
22
+ blue: "#1c59e0",
23
+ teal: "#007167",
24
+ violet: "#7936e9",
25
+ rose: "#c40039",
26
+ red: "#c0202f",
27
+ orange: "#b03e00",
18
28
  black: "#111111",
19
29
  };
30
+ /**
31
+ * The dark half (`--accent-dark`). Derived from the light ramp by recipe — see the header of
32
+ * accents.css, which owns the values; these mirror them for JS that cannot read CSS (chart
33
+ * canvases, the desktop shell, docs tables).
34
+ */
35
+ export const ACCENTS_DARK = {
36
+ indigo: "#99a5ff",
37
+ blue: "#7fabff",
38
+ teal: "#49bbae",
39
+ violet: "#b39dff",
40
+ rose: "#ff848c",
41
+ red: "#ff8582",
42
+ orange: "#ff885b",
43
+ black: "#c9c9d4",
44
+ };
45
+ /** The accent hex actually painted for a resolved theme. */
46
+ export function accentHex(accent, theme) {
47
+ return theme === "dark" ? ACCENTS_DARK[accent] : ACCENTS[accent];
48
+ }
20
49
  /** Density axis (`data-density`). */
21
50
  export const DENSITIES = ["comfortable", "compact"];
22
51
  /** Text direction axis (`dir` on `<html>`). `rtl` iff locale is `ar_EG`. */
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,sFAAsF;AACtF,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAU,CAAC;AAK3D,0EAA0E;AAC1E,MAAM,CAAC,MAAM,OAAO,GAAG;IACrB,MAAM,EAAE,SAAS;IACjB,IAAI,EAAE,SAAS;IACf,IAAI,EAAE,SAAS;IACf,MAAM,EAAE,SAAS;IACjB,IAAI,EAAE,SAAS;IACf,GAAG,EAAE,SAAS;IACd,MAAM,EAAE,SAAS;IACjB,KAAK,EAAE,SAAS;CACR,CAAC;AAIX,qCAAqC;AACrC,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,aAAa,EAAE,SAAS,CAAU,CAAC;AAG7D,4EAA4E;AAC5E,MAAM,CAAC,MAAM,IAAI,GAAG,CAAC,KAAK,EAAE,KAAK,CAAU,CAAC;AAG5C,mFAAmF;AACnF,MAAM,CAAC,MAAM,WAAW,GAAG;IACzB,SAAS;IACT,SAAS;IACT,SAAS;IACT,SAAS;IACT,SAAS;IACT,SAAS;IACT,SAAS;IACT,SAAS;CACD,CAAC;AAEX,uEAAuE;AACvE,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAU,CAAC;AAE1E;;;;GAIG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG;IAC9B,KAAK,EAAE,YAAY;IACnB,MAAM,EAAE,aAAa;IACrB,OAAO,EAAE,cAAc;IACvB,GAAG,EAAE,KAAK;IACV,IAAI,EAAE,MAAM;CACJ,CAAC;AAEX;;;GAGG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG;IAC1B,KAAK,EAAE,gBAAgB;IACvB,MAAM,EAAE,iBAAiB;IACzB,OAAO,EAAE,kBAAkB;IAC3B,MAAM,EAAE,iBAAiB;IACzB,GAAG,EAAE,cAAc;CACX,CAAC;AAEX,8DAA8D;AAC9D,MAAM,CAAC,MAAM,aAAa,GAAG;IAC3B,KAAK,EAAE,QAAQ;IACf,MAAM,EAAE,QAAQ;IAChB,OAAO,EAAE,aAAa;IACtB,MAAM,EAAE,OAAO;CACP,CAAC;AAEX,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,sFAAsF;AACtF,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAU,CAAC;AAK3D;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,OAAO,GAAG;IACrB,MAAM,EAAE,SAAS;IACjB,IAAI,EAAE,SAAS;IACf,IAAI,EAAE,SAAS;IACf,MAAM,EAAE,SAAS;IACjB,IAAI,EAAE,SAAS;IACf,GAAG,EAAE,SAAS;IACd,MAAM,EAAE,SAAS;IACjB,KAAK,EAAE,SAAS;CACR,CAAC;AAEX;;;;GAIG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG;IAC1B,MAAM,EAAE,SAAS;IACjB,IAAI,EAAE,SAAS;IACf,IAAI,EAAE,SAAS;IACf,MAAM,EAAE,SAAS;IACjB,IAAI,EAAE,SAAS;IACf,GAAG,EAAE,SAAS;IACd,MAAM,EAAE,SAAS;IACjB,KAAK,EAAE,SAAS;CACR,CAAC;AAKX,4DAA4D;AAC5D,MAAM,UAAU,SAAS,CAAC,MAAc,EAAE,KAAoB;IAC5D,OAAO,KAAK,KAAK,MAAM,CAAC,CAAC,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;AACnE,CAAC;AAED,qCAAqC;AACrC,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,aAAa,EAAE,SAAS,CAAU,CAAC;AAG7D,4EAA4E;AAC5E,MAAM,CAAC,MAAM,IAAI,GAAG,CAAC,KAAK,EAAE,KAAK,CAAU,CAAC;AAG5C,mFAAmF;AACnF,MAAM,CAAC,MAAM,WAAW,GAAG;IACzB,SAAS;IACT,SAAS;IACT,SAAS;IACT,SAAS;IACT,SAAS;IACT,SAAS;IACT,SAAS;IACT,SAAS;CACD,CAAC;AAEX,uEAAuE;AACvE,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAU,CAAC;AAE1E;;;;GAIG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG;IAC9B,KAAK,EAAE,YAAY;IACnB,MAAM,EAAE,aAAa;IACrB,OAAO,EAAE,cAAc;IACvB,GAAG,EAAE,KAAK;IACV,IAAI,EAAE,MAAM;CACJ,CAAC;AAEX;;;GAGG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG;IAC1B,KAAK,EAAE,gBAAgB;IACvB,MAAM,EAAE,iBAAiB;IACzB,OAAO,EAAE,kBAAkB;IAC3B,MAAM,EAAE,iBAAiB;IACzB,GAAG,EAAE,cAAc;CACX,CAAC;AAEX,8DAA8D;AAC9D,MAAM,CAAC,MAAM,aAAa,GAAG;IAC3B,KAAK,EAAE,QAAQ;IACf,MAAM,EAAE,QAAQ;IAChB,OAAO,EAAE,aAAa;IACtB,MAAM,EAAE,OAAO;CACP,CAAC;AAEX,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC"}
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Pre-hydration script — flash prevention. See workplan/02-design-system.md §4.3.
2
+ * Pre-hydration script — flash prevention. See 02-design-system.md §4.3.
3
3
  *
4
4
  * Inlined (as a string) as the FIRST <script> in <head> of apps/dashboard/index.html,
5
5
  * the Electron renderer HTML and Storybook's preview-head.html, before any stylesheet
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Pre-hydration script — flash prevention. See workplan/02-design-system.md §4.3.
2
+ * Pre-hydration script — flash prevention. See 02-design-system.md §4.3.
3
3
  *
4
4
  * Inlined (as a string) as the FIRST <script> in <head> of apps/dashboard/index.html,
5
5
  * the Electron renderer HTML and Storybook's preview-head.html, before any stylesheet
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adminiumjs/tokens",
3
- "version": "0.1.0-rc.1",
3
+ "version": "0.1.2",
4
4
  "license": "AGPL-3.0-only",
5
5
  "repository": {
6
6
  "type": "git",
package/src/accents.css CHANGED
@@ -1,24 +1,125 @@
1
- /* @adminium/tokens — accent palettes + derived accent tokens. See workplan/02-design-system.md §1.2/§2.3.
1
+ /* @adminium/tokens — accent palettes + derived accent tokens. See 02-design-system.md §1.2/§2.3.
2
2
  Everything accent-derived is computed from the single --accent hex; switching data-accent
3
- changes exactly one value. Derivation recipes are defined here once, never re-derived in components. */
3
+ swaps the light/dark pair below, from which the theme resolves that one hex. Derivation
4
+ recipes are defined here once, never re-derived in components.
5
+
6
+ PER-THEME PAIRS. Each accent now declares two canonical hexes — --accent-light and
7
+ --accent-dark — and the theme selects one into --accent, below. That selection happens here,
8
+ once: components still read --accent (and --accent-soft/-hover/-border/-selection/-glow) only,
9
+ and never branch on theme. Before this, the single light-tuned hex was reused unchanged on dark
10
+ surfaces, where --accent is the focus ring (focus-visible:outline-accent, 113 controls): black
11
+ measured 1.03:1 and indigo 2.92:1 against --surface #141419 — WCAG 2.4.7 and 1.4.11 failures.
12
+
13
+ THE DARK RAMP IS DERIVED FROM THE LIGHT ONE, and that is deliberate — do not re-describe it as
14
+ hand-tuned. Measured, each chromatic dark hex is exactly: its light sibling's OKLCH hue held
15
+ fixed (|dh| <= 0.53deg), OKLCH L raised to ~0.75 (the seven land at 0.724-0.754), and chroma
16
+ pushed to the sRGB gamut edge at that L and hue — six of the seven sit ON the boundary
17
+ (C / Cmax = 1.000); teal alone is held to 0.82 of it (C 0.104 of a possible 0.128) so a
18
+ low-chroma light teal does not turn neon in dark.
19
+ Why a recipe rather than taste: the constraints leave almost nothing to choose. Hue IS the
20
+ accent's identity and must not move; L is pinned from below by the dark gates listed further
21
+ down — at gamut-max chroma the minimum L clearing all of them currently measures 0.664 (teal)
22
+ to 0.702 (rose), and the ramp sits just above that, deliberately not ON it, because that floor
23
+ moves whenever a tint strength or a surface does (dropping dark --accent-soft from 20% to 12%
24
+ below lowered it by ~0.045); and with L and hue fixed the only free parameter left is how much
25
+ chroma sRGB will still hand you. Hand-picking would land in the same place with no way to
26
+ reproduce or re-derive it. What is gated is the measured contrast, not the provenance:
27
+ scripts/contrast-check.mjs re-measures every hex on every run.
28
+ `black` is the one genuinely hand-authored dark value (its hue is not its light sibling's —
29
+ light black is achromatic; C 0.015 is 18% of the max at its L 0.839). See THE BLACK ACCENT
30
+ ON DARK below.
31
+
32
+ Light hexes moved only where AA forced it: indigo (#4f46e5) and black (#111111) are untouched;
33
+ blue, teal, violet, rose, red and orange each took the minimum OKLCH-lightness step down that
34
+ clears the gates (dL from -0.011 for violet to -0.127 for orange), hue held to |dh| <= 0.47deg.
35
+
36
+ What every hex here clears (scripts/contrast-check.mjs measures all of it):
37
+ light white --accent-fg on --accent >= 4.5:1 (primary-button label; teal/red/orange were
38
+ 3.74/3.91/3.56 and are the reason those three moved)
39
+ --accent as text on --surface/-2 >= 4.5:1
40
+ --accent on --accent-soft >= 4.5:1 (active sidebar row, Tag, soft Button) —
41
+ binding on --surface-3, where the 10% tint darkens the chip most; that pass is what
42
+ pulls blue/violet down as well, not just the three that failed the button label
43
+ dark --accent-fg #0f0f14 on --accent >= 4.5:1
44
+ --accent on --bg/--surface/-2/-3 >= 4.5:1 — well past the 3:1 non-text floor the
45
+ focus ring needs, because --accent is also text on those surfaces
46
+ --accent on --accent-soft >= 4.5:1
47
+
48
+ Darkening the light accent, rather than giving teal/red/orange their own --accent-fg, is the
49
+ deliberate choice: a per-accent --accent-fg would fix only the button label and leave
50
+ text-accent-on-surface (3.56-3.91:1) and text-accent-on-accent-soft (3.15-3.44:1) failing,
51
+ and it would put a theme-and-accent conditional back into the token layer. One --accent-fg per
52
+ theme keeps the contract "components read --accent and --accent-fg, full stop".
53
+
54
+ THE BLACK ACCENT ON DARK — the documented override called for in 15-quality.md.
55
+ `black` is the neutral/monochrome accent: it means "no hue, the strongest neutral this theme
56
+ can carry", not literally the colour black. In light that resolves to #111111 (18.88:1 on
57
+ --surface). In dark it cannot: #111111 is 1.03:1 on --surface #141419 — invisible as a focus
58
+ ring, as a fill and as text. It resolves instead to #c9c9d4, a light cool neutral drawn from
59
+ the dark neutral family: 11.18:1 on --surface, 9.38:1 on --surface-3, 11.64:1 against
60
+ --accent-fg — and only 1.49:1 against --fg #f4f4f6, close enough to the text colour that a
61
+ monochrome accent still reads as monochrome, far enough that an accent fill does not read as
62
+ plain body text. */
4
63
  :root,
5
- [data-accent="indigo"] { --accent: #4f46e5; }
6
- [data-accent="blue"] { --accent: #2563eb; }
7
- [data-accent="teal"] { --accent: #0d9488; }
8
- [data-accent="violet"] { --accent: #7c3aed; }
9
- [data-accent="rose"] { --accent: #e11d48; }
10
- [data-accent="red"] { --accent: #e5484d; }
11
- [data-accent="orange"] { --accent: #ea580c; }
12
- [data-accent="black"] { --accent: #111111; }
64
+ [data-accent="indigo"] { --accent-light: #4f46e5; --accent-dark: #99a5ff; }
65
+ [data-accent="blue"] { --accent-light: #1c59e0; --accent-dark: #7fabff; }
66
+ [data-accent="teal"] { --accent-light: #007167; --accent-dark: #49bbae; }
67
+ [data-accent="violet"] { --accent-light: #7936e9; --accent-dark: #b39dff; }
68
+ [data-accent="rose"] { --accent-light: #c40039; --accent-dark: #ff848c; }
69
+ [data-accent="red"] { --accent-light: #c0202f; --accent-dark: #ff8582; }
70
+ [data-accent="orange"] { --accent-light: #b03e00; --accent-dark: #ff885b; }
71
+ [data-accent="black"] { --accent-light: #111111; --accent-dark: #c9c9d4; }
13
72
 
73
+ /* THE 12% TINT CEILING. Every translucent accent tint below is capped at 12%, and that number is
74
+ measured, not chosen: a tint composites over the surface under it, so text painted on the tint
75
+ loses contrast, and the weakest gated foreground (--fg-subtle) on the deepest grey
76
+ (--surface-3) is what binds. With the monochrome accent — the extreme in both themes — the
77
+ worst pair measures 4.557:1 at 12% and 4.444:1 at 13% in dark (4.698 / 4.595 light). So 12% is
78
+ the last strength at which ANY neutral copy on ANY accent tint on ANY surface still clears
79
+ WCAG 1.4.3. scripts/contrast-check.mjs gates all of it (group text-on-soft / text-on-tint).
80
+
81
+ That is why the previous values are gone:
82
+ --accent-soft dark was 20% — measured --fg-muted 4.09:1 and --fg-subtle 3.11:1 on
83
+ --surface-3, both live in the axe sweep (active nav rows, sticky headers, chips).
84
+ --accent-selection was 22% — an earlier note here claimed it "keeps --fg at >=9.9:1", which
85
+ is true and beside the point: ::selection paints over WHATEVER text the user drags across,
86
+ including secondary and tertiary copy, which measured 4.36:1 and 3.33:1.
87
+ --accent-border was 25% — and the note here asserted it "has no consumer … so no WCAG
88
+ obligation attaches". The first half is still literally true of the TOKEN (no `bg-`/
89
+ `border-accent-border` utility and no var(--accent-border) read outside tailwind.css), but
90
+ the conclusion was wrong twice over: an unconsumed token still has to be safe for its FIRST
91
+ consumer, and the same 25%-accent-over-surface recipe IS painted under text today through
92
+ Tailwind's alpha syntax — RankedEntityList's proportional bar is `bg-accent/25` sitting
93
+ behind `text-fg-subtle` rank numbers (3.26:1 dark / 3.48:1 light; `text-fg` on it is fine
94
+ at 7.58 / 8.93). Lowering the token to 12% makes `bg-accent-border` safe; the component's
95
+ own alpha utility is a separate, component-level fix.
96
+ The 1.4.11 reading is unchanged and still correct: these tints are decorative, component state
97
+ is drawn with `border-accent` / `outline-accent` (the full accent, >=4.5:1), so no non-text
98
+ contrast floor attaches to the tints themselves. --accent-glow stays a drop-shadow with no
99
+ foreground. */
14
100
  :root {
101
+ --accent: var(--accent-light);
15
102
  --accent-soft: color-mix(in srgb, var(--accent) 10%, transparent);
16
103
  --accent-hover: color-mix(in srgb, var(--accent) 82%, #000);
17
- --accent-selection: color-mix(in srgb, var(--accent) 22%, transparent);
18
- --accent-border: color-mix(in srgb, var(--accent) 25%, transparent);
104
+ --accent-selection: color-mix(in srgb, var(--accent) 12%, transparent);
105
+ --accent-border: color-mix(in srgb, var(--accent) 12%, transparent);
19
106
  --accent-glow: 0 2px 8px color-mix(in srgb, var(--accent) 36%, transparent);
20
107
  }
21
108
 
109
+ /* Same specificity as :root above, so source order decides — this block must stay last.
110
+ --accent-hover inverts with the theme: mixing toward #000 is "darker, therefore pressed" only
111
+ while --accent is dark. On dark it would push an already-dark hover further into the surface
112
+ (indigo's old dark hover measured 2.22:1 on --surface), so dark mixes toward #fff instead.
113
+ Every dark hover value stays >=8.96:1 against --accent-fg and >=7.22:1 on --surface-3, and
114
+ both halves of that are now gated: `accent-hover` measures the button label on the hover fill
115
+ at 4.5:1, `state-fill` measures the hover fill against the resting surface at the 1.4.11 3:1
116
+ floor — which is the check that fails closed if anyone mixes the dark accent toward #000 again.
117
+
118
+ --accent-soft is 12% here rather than the light ramp's 10% (see THE 12% TINT CEILING above):
119
+ a tint of a LIGHT hue over a near-black surface reads weaker than the mirror case, so dark
120
+ takes the whole allowance. It was 20% until the tint ceiling was measured. */
22
121
  [data-theme="dark"] {
23
- --accent-soft: color-mix(in srgb, var(--accent) 20%, transparent);
122
+ --accent: var(--accent-dark);
123
+ --accent-soft: color-mix(in srgb, var(--accent) 12%, transparent);
124
+ --accent-hover: color-mix(in srgb, var(--accent) 88%, #fff);
24
125
  }
package/src/density.css CHANGED
@@ -1,4 +1,4 @@
1
- /* @adminium/tokens — density axis (data-density). See workplan/02-design-system.md §1.8.
1
+ /* @adminium/tokens — density axis (data-density). See 02-design-system.md §1.8.
2
2
  Two canonical densities; the comps' third "power" density is deliberately dropped. */
3
3
  :root,
4
4
  [data-density="comfortable"] {
@@ -1,7 +1,23 @@
1
- /* @adminium/tokens — canonical theming exceptions. See workplan/02-design-system.md §6.
1
+ /* @adminium/tokens — canonical theming exceptions. See 02-design-system.md §6.
2
2
  Two deliberate breaks from the active theme, implemented as scope classes that re-declare
3
3
  the full token set on a subtree (token cascade does the rest — children need no changes).
4
- These scopes copy canonical hand-tuned values; they do not derive them. */
4
+ These scopes copy canonical hand-tuned values; they do not derive them.
5
+
6
+ They now also pin the ACCENT side of the theme, because accents.css resolves --accent per
7
+ theme (--accent-light / --accent-dark). A dark code block inside a light page would otherwise
8
+ inherit the light accent onto a #0f0f14 surface — where the black accent measures 1.03:1 — and
9
+ vice versa. Re-declaring --accent/--accent-fg/--accent-hover here keeps the exception scopes
10
+ self-consistent without teaching any component about the theme.
11
+
12
+ EVERY accent-derived token has to be re-declared for that to hold, not just --accent itself.
13
+ A custom property's var() is substituted on the element that DECLARES it, so an --accent-soft
14
+ left to accents.css would arrive here already resolved against the ROOT's accent — the light
15
+ accent inside a dark code block. --accent-selection and --accent-border were missing for
16
+ exactly that reason and are pinned below.
17
+
18
+ scripts/contrast-check.mjs replays both scopes as real subtrees (each root theme x each accent
19
+ x each scope) and gates every pair, so the copied values below cannot drift from tokens.css
20
+ without turning CI red. */
5
21
 
6
22
  /* Code/terminal surfaces are ALWAYS dark, both themes:
7
23
  CodeBlock, Terminal, JsonViewer, LogBox, connection-string previews */
@@ -14,9 +30,14 @@
14
30
  --border: rgba(255,255,255,.07);
15
31
  --border-strong: rgba(255,255,255,.13);
16
32
  --fg: #f4f4f6;
17
- --fg-muted: #9a9aa6;
18
- --fg-subtle: #6a6a76;
19
- --accent-soft: color-mix(in srgb, var(--accent) 20%, transparent);
33
+ --fg-muted: #b7b7c3;
34
+ --fg-subtle: #a1a1ad;
35
+ --accent: var(--accent-dark);
36
+ --accent-fg: #0f0f14;
37
+ --accent-soft: color-mix(in srgb, var(--accent) 12%, transparent);
38
+ --accent-hover: color-mix(in srgb, var(--accent) 88%, #fff);
39
+ --accent-selection: color-mix(in srgb, var(--accent) 12%, transparent);
40
+ --accent-border: color-mix(in srgb, var(--accent) 12%, transparent);
20
41
  --pos: #3ecf8e;
21
42
  --pos-soft: rgba(62,207,142,.14);
22
43
  --warn: #e0a458;
@@ -28,12 +49,18 @@
28
49
  --shadow: 0 1px 2px rgba(0,0,0,.4);
29
50
  --shadow-md: 0 6px 20px rgba(0,0,0,.5);
30
51
  --shadow-lg: 0 16px 40px rgba(0,0,0,.6);
31
- /* JSON syntax tokens (fixed, never themed) */
52
+ /* JSON syntax tokens (fixed, never themed). These are TEXT on the surfaces above, so the gate
53
+ measures them like any other foreground. --code-gray (punctuation, nulls, comments) was
54
+ #7d7d8a: 4.71:1 on --surface but 4.27:1 on --surface-2 and 3.79:1 on --surface-3, which
55
+ JsonViewer's alternating rows and LogBox's inset panels both use. Raised by the minimum
56
+ OKLCH lightness step that clears AA on the deepest of them (hue held, drift 0.01deg):
57
+ 4.52 / 5.09 / 5.62 : 1. It stays well below --fg-subtle #a1a1ad, so punctuation still reads
58
+ dimmer than tertiary prose. The other four already cleared 7.5:1+. */
32
59
  --code-blue: #7cc5ff;
33
60
  --code-green: #8fe388;
34
61
  --code-orange: #e6b673;
35
62
  --code-purple: #c8a6ff;
36
- --code-gray: #7d7d8a;
63
+ --code-gray: #8a8a98;
37
64
  }
38
65
 
39
66
  /* Email previews and QR tiles are ALWAYS light, both themes
@@ -47,16 +74,21 @@
47
74
  --border: #ececef;
48
75
  --border-strong: #e2e2e8;
49
76
  --fg: #191920;
50
- --fg-muted: #6b6b76;
51
- --fg-subtle: #9a9aa5;
77
+ --fg-muted: #4a4a54;
78
+ --fg-subtle: #5a5a65;
79
+ --accent: var(--accent-light);
80
+ --accent-fg: #ffffff;
52
81
  --accent-soft: color-mix(in srgb, var(--accent) 10%, transparent);
53
- --pos: #12805c;
82
+ --accent-hover: color-mix(in srgb, var(--accent) 82%, #000);
83
+ --accent-selection: color-mix(in srgb, var(--accent) 12%, transparent);
84
+ --accent-border: color-mix(in srgb, var(--accent) 12%, transparent);
85
+ --pos: #0b7d59;
54
86
  --pos-soft: #e6f5ee;
55
- --warn: #b25e09;
87
+ --warn: #a95800;
56
88
  --warn-soft: #fbf0e2;
57
- --danger: #d1293d;
89
+ --danger: #cf273c;
58
90
  --danger-soft: #fdecec;
59
- --info: #2563eb;
91
+ --info: #1c59e0;
60
92
  --info-soft: #e7edfd;
61
93
  --shadow: 0 1px 2px rgba(20,20,35,.04), 0 1px 3px rgba(20,20,35,.05);
62
94
  --shadow-md: 0 4px 14px rgba(20,20,35,.09);
package/src/fonts.css CHANGED
@@ -1,4 +1,4 @@
1
- /* @adminium/tokens — @font-face + per-locale family stacks. See workplan/02-design-system.md §2.4.
1
+ /* @adminium/tokens — @font-face + per-locale family stacks. See 02-design-system.md §2.4.
2
2
  Font binaries are unmodified upstream builds vendored from @fontsource packages into ./fonts/
3
3
  by scripts/copy-fonts.mjs (never subset locally — "Plex" is an OFL Reserved Font Name).
4
4
  CJK is not self-hosted; zh_CN/zh_TW use system fallback stacks (v1 policy). */
package/src/index.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  * @adminium/tokens — JS-side constants for the four theming axes plus the pre-hydration
3
3
  * script. CSS custom properties live in the sibling .css files; these exports exist so
4
4
  * ThemeProvider, Storybook toolbars, charts and the desktop shell never hard-code axis
5
- * values. See workplan/02-design-system.md §2.5.
5
+ * values. See 02-design-system.md §2.5.
6
6
  */
7
7
 
8
8
  /** Theme axis. `system` resolves via `matchMedia("(prefers-color-scheme: dark)")`. */
@@ -11,20 +11,52 @@ export type ThemePref = (typeof THEMES)[number];
11
11
  /** A resolved theme as stamped on `<html data-theme>`. */
12
12
  export type ResolvedTheme = Exclude<ThemePref, "system">;
13
13
 
14
- /** Accent axis: the 8 switchable palettes (`data-accent`), name → hex. */
14
+ /**
15
+ * Accent axis: the 8 switchable palettes (`data-accent`), name → LIGHT-theme hex
16
+ * (`--accent-light` in accents.css).
17
+ *
18
+ * This is ONE HALF of the accent. Each palette also has an `--accent-dark` hex that the dark
19
+ * theme selects into `--accent`, and it is a different colour, drastically so for `black`
20
+ * (#111111 light, #c9c9d4 dark). Anything that PAINTS an accent must therefore either read the
21
+ * CSS variable — which already resolves per theme — or pick from {@link ACCENTS_DARK} when the
22
+ * resolved theme is dark. Rendering `ACCENTS[a]` unconditionally shows the wrong colour on dark;
23
+ * that is what the accent-picker swatch used to do.
24
+ */
15
25
  export const ACCENTS = {
16
26
  indigo: "#4f46e5",
17
- blue: "#2563eb",
18
- teal: "#0d9488",
19
- violet: "#7c3aed",
20
- rose: "#e11d48",
21
- red: "#e5484d",
22
- orange: "#ea580c",
27
+ blue: "#1c59e0",
28
+ teal: "#007167",
29
+ violet: "#7936e9",
30
+ rose: "#c40039",
31
+ red: "#c0202f",
32
+ orange: "#b03e00",
23
33
  black: "#111111",
24
34
  } as const;
35
+
36
+ /**
37
+ * The dark half (`--accent-dark`). Derived from the light ramp by recipe — see the header of
38
+ * accents.css, which owns the values; these mirror them for JS that cannot read CSS (chart
39
+ * canvases, the desktop shell, docs tables).
40
+ */
41
+ export const ACCENTS_DARK = {
42
+ indigo: "#99a5ff",
43
+ blue: "#7fabff",
44
+ teal: "#49bbae",
45
+ violet: "#b39dff",
46
+ rose: "#ff848c",
47
+ red: "#ff8582",
48
+ orange: "#ff885b",
49
+ black: "#c9c9d4",
50
+ } as const;
51
+
25
52
  /** `indigo` is the default. */
26
53
  export type Accent = keyof typeof ACCENTS;
27
54
 
55
+ /** The accent hex actually painted for a resolved theme. */
56
+ export function accentHex(accent: Accent, theme: ResolvedTheme): string {
57
+ return theme === "dark" ? ACCENTS_DARK[accent] : ACCENTS[accent];
58
+ }
59
+
28
60
  /** Density axis (`data-density`). */
29
61
  export const DENSITIES = ["comfortable", "compact"] as const;
30
62
  export type Density = (typeof DENSITIES)[number];
package/src/motion.css CHANGED
@@ -1,4 +1,4 @@
1
- /* @adminium/tokens — motion vocabulary. See workplan/02-design-system.md §5.
1
+ /* @adminium/tokens — motion vocabulary. See 02-design-system.md §5.
2
2
  Keyframe names keep the comps' nb- prefix for 1:1 traceability when porting.
3
3
  Conventions: micro-transitions .1–.16s ease; entrances use cubic-bezier(.2,.7,.3,1). */
4
4
 
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Pre-hydration script — flash prevention. See workplan/02-design-system.md §4.3.
2
+ * Pre-hydration script — flash prevention. See 02-design-system.md §4.3.
3
3
  *
4
4
  * Inlined (as a string) as the FIRST <script> in <head> of apps/dashboard/index.html,
5
5
  * the Electron renderer HTML and Storybook's preview-head.html, before any stylesheet
package/src/tailwind.css CHANGED
@@ -1,4 +1,4 @@
1
- /* @adminium/tokens — Tailwind v4 theme mapping. See workplan/02-design-system.md §3.1.
1
+ /* @adminium/tokens — Tailwind v4 theme mapping. See 02-design-system.md §3.1.
2
2
  Import order in each app's entry CSS:
3
3
  @import "tailwindcss";
4
4
  @import "@adminium/tokens/index.css";
package/src/tokens.css CHANGED
@@ -1,5 +1,26 @@
1
- /* @adminium/tokens — core semantic tokens. Values are canonical; see workplan/02-design-system.md §1.1.
2
- The dark palette is hand-tuned, never derived from light values. */
1
+ /* @adminium/tokens — core semantic tokens. Values are canonical; see 02-design-system.md §1.1.
2
+ The dark NEUTRAL palette in this file is authored independently of the light one, not derived
3
+ from it: the light/dark OKLCH lightness pairs sum to 1.120-1.223 (--bg 1.120, --surface 1.194,
4
+ --surface-3 1.223, --fg 1.184, --fg-muted 1.196, --fg-subtle 1.184), so no inversion, offset or other single
5
+ rule relates the two columns. That sentence covers THIS file only — the dark ACCENT ramp in
6
+ accents.css IS mechanically derived from the light accents, on purpose, and that file states
7
+ the recipe and why. Do not carry this claim across to it.
8
+
9
+ CONTRAST. Every foreground here clears WCAG 2.1 AA (4.5:1) against the WORST backdrop it is
10
+ actually painted on — which is not --surface, and not even --surface-3, but a translucent tint
11
+ composited over --surface-3: --surface-3 is a real background for tertiary text
12
+ (CalendarLegendFilter's `text-fg-subtle` chip has `hover:bg-surface-3`; CountBadge/DeltaPill/
13
+ AvatarStack paint `bg-surface-3 text-fg-muted`), and accent/semantic tints sit on top of it.
14
+ scripts/contrast-check.mjs measures every such pair across both themes, all 8 accents and the
15
+ exception scopes; do not change a value here without re-running it.
16
+
17
+ The two grey steps below moved together on purpose. --fg-subtle had to darken from #9a9aa5
18
+ (2.471:1 on --surface-3 — the single largest source of contrast failures in the app) far enough
19
+ to clear 4.5:1 on the tinted backdrops too. Holding --fg-muted still would have collapsed the
20
+ secondary/tertiary ladder into one grey, so --fg-muted followed it down. The result is a real
21
+ but narrower ladder — see THE TEXT-LADDER TRADE-OFF at the tokens below for every measured
22
+ number. AA on a 4-surface ramp with tints on top leaves much less room than the original
23
+ palette assumed. */
3
24
  :root {
4
25
  color-scheme: light;
5
26
  --bg: #f6f6f8;
@@ -9,16 +30,35 @@
9
30
  --border: #ececef;
10
31
  --border-strong: #e2e2e8;
11
32
  --fg: #191920;
12
- --fg-muted: #6b6b76;
13
- --fg-subtle: #9a9aa5;
33
+ /* THE TEXT-LADDER TRADE-OFF — read before "restoring" any separation here.
34
+ The three text tiers are closer together than they were, and that is the price of AA on this
35
+ palette, not an oversight. Measured now: muted <-> subtle 1.286:1 in light (was 1.890:1, back
36
+ when the pair was #6b6b76 / #9a9aa5) and 1.287:1 in dark (was 1.917:1, #9a9aa6 / #6a6a76);
37
+ fg <-> muted 1.997:1 light, 1.808:1 dark.
38
+ The rungs bunched because the BOTTOM of the ladder had to come up while the TOP could not
39
+ come down. --fg-subtle is not sized by plain --surface-3 — it clears that comfortably
40
+ (6.038:1 light / 6.025:1 dark) — but by the worst backdrop tertiary text is actually painted
41
+ on: a 12% accent tint OVER --surface-3. With the `black` accent that composites to #d6d6d9 in
42
+ light and #383840 in dark, where the tokens below measure subtle 4.698:1 / 4.557:1, muted
43
+ 6.042:1 / 5.865:1 and --fg 12.065:1 / 10.602:1. --fg is brand-fixed near-black/near-white, so
44
+ everything else has to fit underneath it.
45
+ Pushing --fg-subtle back toward its surface for "more contrast between the greys" therefore
46
+ REMOVES contrast where it is measured and re-breaks WCAG 1.4.3, almost immediately: light has
47
+ two 8-bit steps of headroom (#5c5c67 = 4.555:1 passes, #5d5d68 = 4.486:1 fails) and dark has
48
+ one (#a0a0ac = 4.502:1 passes, #9f9fab = 4.448:1 fails). If the tiers must read further
49
+ apart, widen from the other end — the fg <-> muted gap above is the only slack left — and
50
+ re-run scripts/contrast-check.mjs, which measures the tinted backdrops as well as the plain
51
+ ones. */
52
+ --fg-muted: #4a4a54;
53
+ --fg-subtle: #5a5a65;
14
54
  --accent-fg: #ffffff;
15
- --pos: #12805c;
55
+ --pos: #0b7d59;
16
56
  --pos-soft: #e6f5ee;
17
- --warn: #b25e09;
57
+ --warn: #a95800;
18
58
  --warn-soft: #fbf0e2;
19
- --danger: #d1293d;
59
+ --danger: #cf273c;
20
60
  --danger-soft: #fdecec;
21
- --info: #2563eb;
61
+ --info: #1c59e0;
22
62
  --info-soft: #e7edfd;
23
63
  --shadow: 0 1px 2px rgba(20,20,35,.04), 0 1px 3px rgba(20,20,35,.05);
24
64
  --shadow-md: 0 4px 14px rgba(20,20,35,.09);
@@ -26,6 +66,15 @@
26
66
  --scrim: rgba(10,10,15,.5);
27
67
  }
28
68
 
69
+ /* Each light semantic tone above is the minimum OKLCH-lightness darkening (hue held to <0.3deg)
70
+ that puts it at >=4.5:1 on its own -soft partner, which is where it is smallest — StatusPill
71
+ and Alert set 11.5-13px text in --pos/--warn/--danger/--info on --pos-soft/…-soft. Measured
72
+ after the move: 4.56 / 4.57 / 4.59 / 5.04:1 (the light -soft partners are opaque, so the
73
+ backing surface does not change the number).
74
+ The dark tones are untouched — theirs are translucent, so they DO vary with the surface:
75
+ 5.46-7.16:1 over --surface, 4.51-5.86:1 over --surface-3, the tightest (--danger on
76
+ --surface-3, 4.51:1) still clearing AA. */
77
+
29
78
  [data-theme="dark"] {
30
79
  color-scheme: dark;
31
80
  --bg: #0a0a0d;
@@ -35,8 +84,15 @@
35
84
  --border: rgba(255,255,255,.07);
36
85
  --border-strong: rgba(255,255,255,.13);
37
86
  --fg: #f4f4f6;
38
- --fg-muted: #9a9aa6;
39
- --fg-subtle: #6a6a76;
87
+ /* Same trade-off as light, mirrored: 7.754:1 / 6.025:1 on plain --surface-3 but 5.865:1 /
88
+ 4.557:1 on the 12% black-accent tint over it (#383840), which is what sizes them — 1.287:1
89
+ apart, and --fg-subtle has ONE 8-bit step of headroom left. See THE TEXT-LADDER TRADE-OFF in
90
+ the light block above before touching either. */
91
+ --fg-muted: #b7b7c3;
92
+ --fg-subtle: #a1a1ad;
93
+ /* Dark --accent is a light hue (see accents.css), so the primary-button label inverts:
94
+ white on #99a5ff is 2.3:1, #0f0f14 on it is 8.4:1. One value, no per-accent branch. */
95
+ --accent-fg: #0f0f14;
40
96
  --pos: #3ecf8e;
41
97
  --pos-soft: rgba(62,207,142,.14);
42
98
  --warn: #e0a458;
package/src/viz.css CHANGED
@@ -1,4 +1,4 @@
1
- /* @adminium/tokens — data-viz palette. See workplan/02-design-system.md §1.3.
1
+ /* @adminium/tokens — data-viz palette. See 02-design-system.md §1.3.
2
2
  Fixed 8-color categorical palette (order canonical; series i gets --viz-((i mod 8)+1)),
3
3
  sequential accent ramp for heatmaps/choropleth intensity, and chart area/ribbon fill.
4
4
  @adminium/charts consumes only these vars — no hex literals in chart code. */