@kanzo-tech/theme 0.14.0 → 0.15.1
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/README.md +19 -16
- package/dist/index.d.ts +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/themes/acid.css +1 -1
- package/themes/bank-dark.css +3 -5
- package/themes/bank-private-dark.css +3 -5
- package/themes/bank-private.css +3 -5
- package/themes/bank.css +3 -5
- package/themes/catppuccin-latte-dark.css +3 -5
- package/themes/catppuccin-latte.css +3 -5
- package/themes/catppuccin-mocha-dark.css +3 -5
- package/themes/catppuccin-mocha.css +3 -5
- package/themes/cmyk.css +1 -1
- package/themes/coffee.css +1 -1
- package/themes/cyberpunk.css +1 -1
- package/themes/dim.css +1 -1
- package/themes/dracula-dark.css +3 -5
- package/themes/dracula.css +3 -5
- package/themes/forest.css +1 -1
- package/themes/kanzo-dark.css +3 -5
- package/themes/kanzo.css +6 -9
- package/themes/lemonade.css +1 -1
- package/themes/lofi.css +1 -1
- package/themes/luxury.css +1 -1
- package/themes/monochrome-dark.css +3 -5
- package/themes/monochrome.css +3 -5
- package/themes/night.css +1 -1
- package/themes/nord-dark.css +3 -5
- package/themes/nord.css +3 -5
- package/themes/sunset.css +1 -1
- package/themes/synthwave.css +1 -1
- package/themes/wireframe.css +1 -1
- package/tokens.css +5 -5
package/README.md
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
# @kanzo-tech/theme
|
|
2
2
|
|
|
3
|
-
The
|
|
4
|
-
|
|
3
|
+
The themes and the axes a user layers over them. **A theme is one flat block of CSS, and it carries
|
|
4
|
+
one mode.**
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
The themes in `themes/` are **provisional test themes** — sixteen written here to exercise the
|
|
7
|
+
vocabulary and thirteen imported from daisyUI — that the guards measure. They are not a curated set;
|
|
8
|
+
names, values and the count will change, and a product ships its own.
|
|
9
|
+
|
|
10
|
+
- **Theme** — `packages/theme/themes/<name>.css`, hand-written source. Its colours, the shape knobs,
|
|
11
|
+
optionally its font stacks, and its own `color-scheme`. Selected with `data-theme`.
|
|
8
12
|
- **Radius** — `none` · `xs` · `sm` · `md` · `lg`, a user preference over the theme's own three
|
|
9
13
|
radius knobs.
|
|
10
14
|
- **Font** / **Mono font** — `--font-sans` / `--font-heading` / `--font-mono`.
|
|
@@ -22,9 +26,9 @@ types.
|
|
|
22
26
|
color-scheme: light;
|
|
23
27
|
--background: #fbfcfd; --foreground: #10151c;
|
|
24
28
|
--primary: #1f6feb; --primary-foreground: #ffffff;
|
|
25
|
-
/* …
|
|
29
|
+
/* …the rest of the colours, then the rest of the shape knobs… */
|
|
26
30
|
--radius-box: 0.75rem; --radius-field: 0.5rem; --radius-selector: 0.25rem;
|
|
27
|
-
--stroke: 1px; --
|
|
31
|
+
--stroke: 1px; --relief: 0;
|
|
28
32
|
}
|
|
29
33
|
```
|
|
30
34
|
|
|
@@ -39,11 +43,10 @@ declared in `:root` would inherit already substituted.
|
|
|
39
43
|
**Light and dark are two themes.** There is no second block and no `.dark` that flips a token; the
|
|
40
44
|
class survives only as the selector for the `dark:` variant at the call sites that still ask for one.
|
|
41
45
|
|
|
42
|
-
**There is no derivation.** A
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
catches a mistake.
|
|
46
|
+
**There is no derivation and there are no step tokens.** A tint is a percentage on the role,
|
|
47
|
+
computed where it is used — `bg-destructive/7`, or `color-mix(in oklab, var(--destructive) 7%,
|
|
48
|
+
transparent)` where no utility reaches. The author answers for AA, and guards over the shipped themes
|
|
49
|
+
are what catch a mistake.
|
|
47
50
|
|
|
48
51
|
## How the other axes work
|
|
49
52
|
|
|
@@ -52,7 +55,7 @@ Every axis is a `data-*` attribute **on `<html>`**, and the token values behind
|
|
|
52
55
|
|
|
53
56
|
| Axis | Attribute | Sets |
|
|
54
57
|
|---|---|---|
|
|
55
|
-
| radius | `data-radius` | `--radius` |
|
|
58
|
+
| radius | `data-radius` | `--radius-box` / `--radius-field` / `--radius-selector` |
|
|
56
59
|
| font | `data-font` | `--font-sans` |
|
|
57
60
|
| monoFont | `data-mono-font` | `--font-mono` |
|
|
58
61
|
| density | `data-font-size` | the root font-size |
|
|
@@ -77,8 +80,9 @@ Read or change the live preferences with `useKanzoTheme()`, or drop in the ready
|
|
|
77
80
|
|
|
78
81
|
## Dark mode
|
|
79
82
|
|
|
80
|
-
Not owned here.
|
|
81
|
-
|
|
83
|
+
Not owned here. `.dark` on `<html>` says which side is worn: the provider writes the theme chosen for
|
|
84
|
+
that side to `data-theme`, and the `dark:` variant keys off the class. If you already run a theme
|
|
85
|
+
manager, hand it to the provider:
|
|
82
86
|
|
|
83
87
|
```tsx
|
|
84
88
|
import { useTheme } from "next-themes";
|
|
@@ -125,5 +129,4 @@ fails on one that declares a partial set.
|
|
|
125
129
|
`pnpm gen` runs it; CI regenerates and fails on any diff.
|
|
126
130
|
|
|
127
131
|
**`tokens.css` and `themes/*.css` are NOT generated.** They are hand-written source, and a guard that
|
|
128
|
-
regenerated them would have nothing to regenerate them from.
|
|
129
|
-
colour stopped being output.
|
|
132
|
+
regenerated them would have nothing to regenerate them from.
|
package/dist/index.d.ts
CHANGED
|
@@ -395,7 +395,7 @@ export type CorePrefKey = Exclude<keyof ThemePrefs, "sections">;
|
|
|
395
395
|
* and one chain answers for colour, geometry and a contributed choice alike.
|
|
396
396
|
*
|
|
397
397
|
* That is daisyUI's insight, in the mechanism this repo already had: their theme carries the
|
|
398
|
-
* geometry (`--radius-box`, `--size-field`, `--
|
|
398
|
+
* geometry (`--radius-box`, `--size-field`, `--relief`) in the same document as the colours, so a
|
|
399
399
|
* tenant ships a coherent whole rather than a panel of unrelated knobs. Ours went half-way there
|
|
400
400
|
* when a palette became a document; the half not taken was that radius, density and the fonts had no
|
|
401
401
|
* document-level default at all — only a user could move them.
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../src/sections.ts","../src/obligations.ts","../src/ink.ts","../src/index.ts"],"sourcesContent":["/**\n * Sections: appearance vocabulary contributed by the package that owns it.\n *\n * The core never learns that a section exists. It stores sections **opaquely**, resolves a token by\n * hierarchical fallback, and validates against a manifest the owning package hands it. Contributing\n * is *using* a name, not registering a type here — which is what keeps the one-way door shut\n * structurally rather than by discipline: `@kanzo-tech/theme` gains no reference to\n * `@kanzo-tech/graph`, and `boundary.test.ts` keeps passing because there is nothing to import.\n *\n * ## Resolution is Neovim's, validation is not\n *\n * A dotted name falls back to its parent: `--graph-point-size-min` → `--point-size-min` → the\n * default the manifest declared. Neovim's highlight groups work exactly this way\n * (`@foo.bar.lang → @foo.bar → @foo`) and it is why a section costs the core nothing.\n *\n * **Neovim pays a price we do not: there, a typo degrades silently to the parent.** Here the\n * manifest *declares* its tokens, so resolution goes by fallback and validation goes against the\n * declaration. That combination — contribute *and* validate — is the part no surveyed system does:\n * VS Code's `contributes.colors` and Emacs contribute without validating the namespace, Vanilla\n * Extract validates completely and therefore cannot have optional sections at all, and StyleX\n * retracted that shape. This is unmapped ground, and `sections.test.ts` says what it cannot prove.\n *\n * ## A default may be derived, and that is what makes white-label flow\n *\n * VS Code spells derived defaults as imperative transforms (`darken`, `transparent`, `oneOf`). Ours\n * is a **CSS value**, which is the same idea with nothing between it and the browser:\n * `var(--primary)` points at what the theme publishes, and `color-mix(in oklab, var(--primary) 20%,\n * transparent)` derives from it. A bank changing its theme moves both — **without the core knowing\n * what a graph is**, and without anyone copying a hex.\n *\n * **It was an object with three shapes** — `{kind:\"step\"|\"alpha\", ramp, step}` and\n * `{kind:\"role\", token}` — declarative against the tenant's *ramps*. Two of the three named a\n * reference tier that no longer exists, and the third was `var()` wearing a wrapper. Collapsing them\n * removed a type that had to be declared structurally in two packages at once, so it also removed\n * the drift between the two declarations.\n *\n * That is why this is not the thing that was refused earlier. Minting a *role* in the core's\n * vocabulary for one consumer is how seventeen level-names happened. A section token lives in the\n * owner's namespace, is declared by the owner, and costs nothing to whoever never installs it.\n */\n\nexport interface SectionTokenDecl {\n /**\n * A CSS value. A literal, a `var()` at what the theme publishes, or a `color-mix()` derived from\n * one — and nothing needs to resolve it but the browser.\n */\n default: string;\n /** What the token is for, in one line. Shown by a panel that lists a section's vocabulary. */\n doc: string;\n}\n\n/** What every declaration carries, whatever its kind. */\ninterface PrefCommon {\n /** The value when nothing else answers. Always a string — see {@link SectionPrefDecl}. */\n default: string;\n /** What the preference does, in one line. Shown by a surface beside the control. */\n doc: string;\n /**\n * What a surface CALLS it. Falls back to the key, which is what every surface used to draw.\n *\n * The options carried labels and the preference did not, so a generic control had a key for a\n * legend: a dock offering the force coefficients announced `link-spring`. A name is authored where\n * the values are, like everything else here — and it is not the `doc`, which is a sentence about\n * what the thing does rather than the two words a control is titled with.\n */\n label?: string;\n /**\n * The stored value is a map keyed by the resolved appearance, not a plain string.\n *\n * One field on the declaration rather than a second table beside it. It is only expressible\n * because appearance resolves *first* — the pre-hydration script has to resolve it to write\n * `.dark` — so by the time anything reads this, the side to index by is known.\n */\n byAppearance?: true;\n /**\n * The attribute to write on `<html>` — **only where CSS has to react.**\n *\n * Most contributed preferences have none. The graph's look is read by JS and pushed into a\n * renderer's config; an editor's font size would want one. Leaving it out is what keeps the\n * pre-hydration script from growing by a line for every optional package a host installs, which is\n * the cost the four core axes pay for having attributes at all.\n */\n attr?: string;\n}\n\n/** One thing a `choice` may offer. `value` is what is stored; `label` is what a person reads. */\nexport interface PrefOption {\n value: string;\n label: string;\n}\n\n/**\n * Where a choice's options come from, when an author cannot list them.\n *\n * The one thing a TENANT publishes that nobody can know at authoring time: the themes they wrote.\n * Closed on purpose — a source is something the provider already receives and can hand to a\n * resolver, not a hook for a package to fetch from.\n *\n * It was two, `\"palettes\"` and `\"identities\"`, while a palette contained brands and a control had to\n * ask which level it was filling. A brand is a theme, so there is one level and one list.\n */\nexport type PrefSource = \"themes\";\n\n/**\n * A list, or the name of the list's owner.\n *\n * This is the same move {@link SectionTokenDecl} already makes for colour — a **binding** instead of\n * a value — so the vocabulary stays one. It is what `theme` needs: its options are\n * a client's brands, and an author who typed them would be authoring the client's product.\n */\nexport type PrefOptions = readonly PrefOption[] | { from: PrefSource };\n\n/** What the host publishes, for the declarations that name a source. */\nexport type PrefSources = Partial<Record<PrefSource, readonly PrefOption[]>>;\n\n/**\n * The options a declaration offers — `null` when it names a source nobody has answered yet.\n *\n * `null` rather than `[]`, and the distinction is load-bearing: \"this tenant published nothing\",\n * \"the host has not wired the prop\" and \"the document is still being fetched\" are indistinguishable\n * from here, and every one of them must leave a stored value alone. An empty list would read as\n * *nothing is legal* and quietly reset a user's brand on the first render before the fetch lands —\n * which is the failure `useRetirement` guards with `options.length > 0` one layer up.\n */\nexport function prefOptions(\n decl: SectionPrefDecl,\n sources?: PrefSources,\n): readonly PrefOption[] | null {\n if (decl.kind !== \"choice\") return null;\n if (Array.isArray(decl.options)) return decl.options;\n const list = sources?.[(decl.options as { from: PrefSource }).from];\n return list && list.length > 0 ? list : null;\n}\n\n/**\n * One preference a section offers its user — the half of a manifest the person on the screen decides.\n *\n * **Three kinds, and the set is closed.** A section may offer a value it has measured; it may not\n * offer a text field, and there is deliberately no `custom` escape. That is the same line the colour\n * layer holds one level up — choose among what somebody validated, never author — and it is what\n * lets any surface render any section without knowing which package wrote it.\n *\n * The three arrived together with their call sites rather than ahead of them. `choice` shipped\n * alone and covered the graph's look; the eleven controls it could not express are what asked for\n * the other two — three toggles and two scalars in the graph's Display, six coefficients in its\n * simulation dock, every one of them hand-rolled with its own wiring.\n *\n * **\"Follow the system\" is an option and not a fourth kind.** It is the option whose value is `\"\"`,\n * and the convention is already load-bearing here: `identity` and `palette` default to `\"\"`, and the\n * write rule removes the attribute at the default — which is exactly what *the OS decides* means in\n * CSS, where there is no third keyword either. Declared as `{ value: \"\", label: \"System\" }` it is a\n * thing a control can offer, so getting back to it stops being the panel's `Reset` button's private\n * power.\n *\n * **A value is a string in all three**, and that is a decision rather than an oversight. One storage\n * shape means an unrecognised namespace rides through a write untouched without the core parsing\n * it; and a value that can be written to a `data-*` attribute needs no second spelling on its way\n * out. A renderer parses what its own kind means — `\"true\"`, `\"0.42\"` — and the parsing is one\n * place, {@link prefNumber} and {@link prefBoolean}.\n */\nexport type SectionPrefDecl =\n /** Pick one of a closed list — or of a list only the tenant can write. */\n | (PrefCommon & {\n kind: \"choice\";\n /** Everything a control may offer, in order. Order is the section's, and a surface keeps it. */\n options: PrefOptions;\n })\n /** On or off. Stored as `\"true\"` / `\"false\"`. */\n | (PrefCommon & { kind: \"toggle\" })\n /**\n * A number within declared bounds.\n *\n * The bounds are the section's own claim about what it will honour, so a stored value outside them\n * is declined the same way a retired option is: a slider that used to run to 5 and now stops at 3\n * must not paint 5 because storage remembers it.\n */\n | (PrefCommon & { kind: \"range\"; min: number; max: number; step: number });\n\n/** Read a `toggle`'s value. Anything that is not exactly `\"true\"` is off. */\nexport const prefBoolean = (value: string): boolean => value === \"true\";\n\n/** Read a `range`'s value. `NaN` never escapes: a corrupt string answers the declared minimum. */\nexport function prefNumber(value: string, decl: { min: number }): number {\n const n = Number.parseFloat(value);\n return Number.isFinite(n) ? n : decl.min;\n}\n\n/**\n * What a tenant may say about a section's choice — **selection, never authorship.**\n *\n * A document may fix a choice or withhold it; it may not invent a value for it. The options are the\n * ones the owning package declared and measured, and a policy picks among them or removes the\n * control. That is the same line the colour half holds — a user chooses among colours somebody\n * validated and never authors one — and it is what stops this field becoming the runtime palette\n * authoring the retired axes exist to prevent.\n */\nexport interface SectionPrefPolicy {\n /** Fixed to this value. No control is offered and a stored preference does not apply. */\n pinned?: string;\n /** Not offered. The default applies, and a stored preference is kept but does not apply. */\n hidden?: boolean;\n /** Offered, but starting somewhere other than the manifest says. */\n default?: string;\n}\n\n/**\n * What a package publishes to contribute a section. Reached by subpath, never imported here.\n *\n * **One manifest carries both halves**, and they are both optional. A package may contribute tokens\n * without preferences (a vocabulary a document decides), preferences without tokens (a choice that\n * paints nothing of its own), or both. Two exports would let a namespace and a version drift apart\n * while describing the same section, and the namespace is the whole of the contract.\n */\nexport interface SectionManifest {\n /** The namespace segment. `\"graph\"` produces `--graph-*`. */\n namespace: string;\n version: number;\n /** Keys are token names **without** the namespace: `\"marquee\"`, `\"point-size-min\"`. */\n tokens?: Readonly<Record<string, SectionTokenDecl>>;\n /** Keys are preference names, unqualified: `\"look\"`. */\n prefs?: Readonly<Record<string, SectionPrefDecl>>;\n}\n\n/** A tenant's policy for one section, keyed by preference name. */\nexport type SectionPolicy = Readonly<Record<string, SectionPrefPolicy>>;\n\n/** Where a resolved preference came from — a panel says so, and a test asserts on it. */\nexport type PrefOrigin = \"pinned\" | \"stored\" | \"policy\" | \"default\";\n\nexport interface ResolvedPref {\n value: string;\n via: PrefOrigin;\n /** Whether a control should be drawn at all. `false` for pinned and for withheld. */\n offered: boolean;\n}\n\n/**\n * One chain, most specific first — the sibling of {@link resolveSectionToken}.\n *\n * Writing it as a chain is not decoration: the token half already answers *the first member that\n * answers, and says which one it used*, and a preference resolved by some other order would be a\n * second mechanism wearing the first one's vocabulary.\n *\n * A stored value that is not in `options` is ignored rather than applied. A section's options are\n * the values it has measured; a stale one from a version the package no longer ships is exactly the\n * case where falling back beats honouring what storage happens to hold.\n */\nexport function resolvePref(\n decl: SectionPrefDecl,\n stored: string | undefined,\n policy?: SectionPrefPolicy,\n /** What the tenant published, for a choice whose options name a source rather than listing them. */\n sources?: PrefSources,\n): ResolvedPref {\n const legal = (v: string | undefined): v is string => {\n // `typeof` rather than a check against `undefined`, though the parameter says `string`: this\n // reads a blob out of a browser's storage, and a corrupt one holds whatever it holds. Without\n // it, an axis whose options a tenant owns — where there is no list to judge against — writes\n // `data-identity=\"[object Object]\"`, which is inert, survives in devtools, and is evidence of a\n // bug we chose not to have. The provider used to carry this test beside its write loop.\n if (typeof v !== \"string\") return false;\n // One gate per kind, and every kind has one. A preference with no notion of an illegal value\n // would let storage outlive the declaration that gave it meaning — which is the version-skew\n // case this whole chain exists to survive.\n if (decl.kind === \"choice\") {\n const options = prefOptions(decl, sources);\n // A source nobody has answered yet cannot judge anything, so it judges nothing: the stored\n // value stands and the retirement notice — which is the surface that knows how to *tell* a\n // user their brand is gone — decides later. See {@link prefOptions}.\n return options === null || options.some((o) => o.value === v);\n }\n if (decl.kind === \"toggle\") return v === \"true\" || v === \"false\";\n const n = Number.parseFloat(v);\n return Number.isFinite(n) && n >= decl.min && n <= decl.max;\n };\n\n if (legal(policy?.pinned)) return { value: policy.pinned, via: \"pinned\", offered: false };\n const offered = !policy?.hidden;\n if (offered && legal(stored)) return { value: stored, via: \"stored\", offered };\n if (legal(policy?.default)) return { value: policy.default, via: \"policy\", offered };\n return { value: decl.default, via: \"default\", offered };\n}\n\n/**\n * Check a section's stored preferences against the manifest that owns it.\n *\n * The preference sibling of {@link validateSection}, and the same argument: a name the manifest\n * never declared is reported rather than resolving to something plausible. What differs is that a\n * preference also has a closed set of legal *values*, so a stored value outside `options` is\n * reported too — `resolvePref` already refuses to apply it, and this is what says so out loud.\n */\nexport function validatePrefs(\n manifest: SectionManifest,\n stored: Readonly<Record<string, string>>,\n /** As {@link resolvePref}'s. A choice whose source is unanswered is not judged, and not reported. */\n sources?: PrefSources,\n): Problem[] {\n const problems: Problem[] = [];\n const prefs = manifest.prefs ?? {};\n for (const [key, value] of Object.entries(stored)) {\n const decl = prefs[key];\n if (!decl) {\n problems.push({\n token: key,\n kind: \"unknown\",\n detail: `\"${manifest.namespace}\" declares no preference \"${key}\"`,\n });\n continue;\n }\n // A switch rather than a ternary chain: the range arm needs `decl` narrowed to reach `min`, and\n // `kind === \"choice\" ? … : kind === \"toggle\" ? … : …` does not narrow the last branch.\n let ok: boolean;\n let expected: string;\n switch (decl.kind) {\n case \"choice\": {\n const options = prefOptions(decl, sources);\n if (options === null) continue;\n const values = options.map((o) => o.value);\n ok = values.includes(value);\n expected = values.join(\", \");\n break;\n }\n case \"toggle\": {\n ok = value === \"true\" || value === \"false\";\n expected = \"true, false\";\n break;\n }\n default: {\n const n = Number.parseFloat(value);\n ok = Number.isFinite(n) && n >= decl.min && n <= decl.max;\n expected = `${decl.min}–${decl.max}`;\n }\n }\n if (!ok) {\n problems.push({\n token: key,\n kind: \"shadowed\",\n detail:\n `\"${value}\" is not a legal ${manifest.namespace}.${key} (${expected}). ` +\n \"It resolves to the default instead.\",\n });\n }\n }\n return problems;\n}\n\n/** A stored appearance document. `sections` is opaque: the core never parses a section's payload. */\nexport interface LookDocument {\n id: string;\n label: string;\n version: number;\n /**\n * Keyed by namespace. **Unknown sections are legal and are preserved**, which is a product\n * requirement rather than politeness: a client signs their document before they buy the graph,\n * and the section a later package adds must survive every load and save in between.\n */\n sections: Readonly<Record<string, Readonly<Record<string, string>>>>;\n}\n\n/**\n * The fallback chain for a dotted token, most specific first.\n *\n * `--graph-point-size-min` → `--point-size-min`. One segment is dropped at a time from the left,\n * because the namespace is the qualifier: a section asks for its own value and inherits the shared\n * one when it has none of its own.\n */\nexport function fallbackChain(token: string): string[] {\n const bare = token.replace(/^--/, \"\");\n const parts = bare.split(\"-\");\n const out: string[] = [];\n for (let i = 0; i < parts.length; i++) out.push(`--${parts.slice(i).join(\"-\")}`);\n return out;\n}\n\n/**\n * Resolve a section token against a document, then against the manifest's default.\n *\n * `declared` is what the cascade or the document already provides, keyed by full token name. The\n * first member of the chain it answers wins; if none does, the manifest's default is returned —\n * and a default is returned as written, because it is already a CSS value: the browser is what\n * resolves it, on the element that uses it.\n */\nexport function resolveSectionToken(\n token: string,\n declared: Readonly<Record<string, string>>,\n manifest: SectionManifest,\n): { value: string; via: string } | null {\n for (const candidate of fallbackChain(token)) {\n const hit = declared[candidate];\n if (hit !== undefined) return { value: hit, via: candidate };\n }\n const bare = token.replace(new RegExp(`^--${manifest.namespace}-`), \"\");\n const decl = (manifest.tokens ?? {})[bare];\n return decl ? { value: decl.default, via: `${manifest.namespace}:default` } : null;\n}\n\nexport interface Problem {\n token: string;\n /** `unknown` — not declared by the manifest. `shadowed` — would silently fall back to a parent. */\n kind: \"unknown\" | \"shadowed\";\n detail: string;\n}\n\n/**\n * Check a section's stored payload against the manifest that owns it.\n *\n * This is the half Neovim does not have, and it is why a typo cannot degrade in silence: a name the\n * manifest never declared is reported rather than quietly resolving to whatever parent happens to\n * exist. Validation is **per section**, so a document carrying a section for an absent package is\n * simply not validated — it is not an error, and `sections.test.ts` pins that.\n */\nexport function validateSection(\n manifest: SectionManifest,\n stored: Readonly<Record<string, string>>,\n): Problem[] {\n const problems: Problem[] = [];\n for (const token of Object.keys(stored)) {\n const prefix = `--${manifest.namespace}-`;\n if (!token.startsWith(prefix)) {\n problems.push({\n token,\n kind: \"unknown\",\n detail: `not in the \"${manifest.namespace}\" namespace — a section may only declare its own`,\n });\n continue;\n }\n const bare = token.slice(prefix.length);\n if (!(bare in (manifest.tokens ?? {}))) {\n const parent = fallbackChain(token)[1];\n problems.push({\n token,\n kind: \"shadowed\",\n detail:\n `\"${manifest.namespace}\" declares no \"${bare}\". Unvalidated, this would fall back to ` +\n `${parent ?? \"nothing\"} and paint something plausible — which is the silent-typo failure ` +\n `this check exists to prevent.`,\n });\n }\n }\n return problems;\n}\n\n/** Read a section's payload. Absent, or a package you do not have, answers an empty record. */\nexport function sectionOf(doc: LookDocument, namespace: string): Readonly<Record<string, string>> {\n return doc.sections[namespace] ?? {};\n}\n\n/**\n * Write a section back, preserving every other section byte for byte.\n *\n * The preservation is the point: a host that never installed `@kanzo-tech/graph` still round-trips\n * a document containing a graph section, because the core treats the payload as opaque data rather\n * than as something to parse into a type it knows.\n */\nexport function withSection(\n doc: LookDocument,\n namespace: string,\n tokens: Readonly<Record<string, string>>,\n): LookDocument {\n return { ...doc, sections: { ...doc.sections, [namespace]: tokens } };\n}\n","import themeDataJson from \"../theme-data.json\" with { type: \"json\" };\n\n/**\n * What the density axis owes — the third section's obligations, in the graph section's shape.\n *\n * Density sets the root font-size the whole `rem` scale resolves against, so it is a **multiplier\n * on every size the component layer declares**. That is what makes it a section rather than a\n * preference: it has a bar it can break.\n *\n * ## Whose obligation is it\n *\n * The reading this was written from: the base height belongs to `@kanzo-tech/ui`, the multiplier to\n * density, and WCAG measures the *product* — so the obligation sits with whoever can break it.\n *\n * **Measured, that reading needs one correction, and the correction matters.** The eleven genuine\n * 2.5.8 failures found in the showcases fail at the **default** density too (20.0 px tall, centres\n * 22.0 px apart) and clear only at comfortable. Density did not move a passing target to failing;\n * it made an already-failing one worse. So density is not the *owner* of the bar — it is a\n * multiplier on a bar **both** layers can break, and the component that declared a 1.25rem row\n * broke it first. The obligation is stated here because this is where the multiplier lives and\n * where the axis can be graded; the fix for a specific failure may well belong in `ui`.\n */\nexport interface Obligation {\n id: string;\n subject: string;\n measured: number | null;\n threshold: number | null;\n holds: \">=\" | \"<=\" | \"<\";\n unit: string;\n against: string;\n reason: string;\n}\n\n/** Root font-size per density, read from the generated table rather than re-typed. */\nconst DENSITIES: Record<string, string> = themeDataJson.densities;\n\nconst px = (v: string) => Number.parseFloat(v);\n\n/** The smallest root font-size the axis offers. Derived, so a new density step is graded too. */\nconst minRoot = Math.min(...Object.values(DENSITIES).map(px));\n\n/**\n * The library's smallest interactive height, in `rem`, at the smallest density.\n *\n * `Button size=\"xs\"` is `h-6` — 1.5rem. Written here as the rem figure and multiplied by the axis,\n * because the axis is what this file can grade; the 1.5 is `@kanzo-tech/ui`'s to change and a test\n * there is what would catch it moving.\n */\nconst SMALLEST_CONTROL_REM = 1.5;\n\nexport const OBLIGATIONS: readonly Obligation[] = [\n {\n id: \"target-size\",\n subject: \"the smallest interactive control, at the smallest density\",\n measured: Number((SMALLEST_CONTROL_REM * minRoot).toFixed(2)),\n threshold: 24,\n holds: \">=\",\n unit: \"CSS px\",\n against: \"WCAG 2.5.8 Target Size (Minimum), AA\",\n reason:\n \"Every size in the library is `rem`, so the density axis multiplies every target. The \" +\n \"smallest control is `Button size=\\\"xs\\\"` at 1.5rem, which is exactly 24px at the default \" +\n \"root and 21px at compact. Measured over the app-shell, workspace and metadata-form \" +\n \"showcases at compact, **no `size=\\\"xs\\\"` button failed**: every undersized target was saved \" +\n \"by 2.5.8's spacing exception. The eleven genuine failures were a hand-rolled 1.25rem list \" +\n \"row, and they fail at the default density too — see the docblock above on ownership.\",\n },\n {\n id: \"x-height\",\n subject: \"the smallest type size the scale emits, at the smallest density\",\n measured: null,\n threshold: null,\n holds: \">=\",\n unit: \"CSS px\",\n against: \"not established — WCAG sets no minimum type size, and this repo has set none either\",\n reason:\n \"Real and not gradeable, and saying so beats inventing a bar. `--kanzo-font-size-xs` is \" +\n \"0.625rem, which is 10px at the default root and 8.75px at compact. What would close it: a \" +\n \"house floor with a reason. The honest anchor already exists in the source — the IDE tier \" +\n \"justifies `--kanzo-font-size-base` as 13px, 'below Tailwind text-xs', which means the scale \" +\n \"was designed against a floor nobody wrote down. Writing that floor down is the work.\",\n },\n];\n\nexport interface Check extends Obligation {\n measured: number;\n threshold: number;\n ok: boolean;\n}\n\n/**\n * Grade every obligation carrying a number.\n *\n * Ungradeable rows are skipped rather than defaulted to passing, for the same reason the graph's\n * are: a bar nobody established cannot be cleared, and reporting one green is how a guard comes to\n * test a corpus of zero.\n */\nexport function check(): Check[] {\n return OBLIGATIONS.filter(\n (o): o is Obligation & { measured: number; threshold: number } =>\n o.measured !== null && o.threshold !== null,\n ).map((o) => ({\n ...o,\n ok:\n o.holds === \">=\"\n ? o.measured >= o.threshold\n : o.holds === \"<=\"\n ? o.measured <= o.threshold\n : o.measured < o.threshold,\n }));\n}\n","// The ink a fill carries, and the maths under it.\n//\n// A theme is a flat block of hex and stays one — nothing here runs in a browser painting a page.\n// This is authoring-time: it answers \"you picked this fill, what text goes on it?\" once, in a form,\n// and the value it returns is written into the document as a literal. `a-theme-is-one-flat-block`\n// is the rule this serves, not the one it bends: the derivation that decision cut was a\n// multi-second categorical search shipped beside a stylesheet, and what replaced it was nobody\n// computing the boring half at all — fifty-four hand-typed declarations per theme.\n//\n// ## The rule is daisyUI's, recovered from its output\n//\n// daisyUI has no derivation either: zero `oklch(from …)`, zero `color-mix` in 5.7.20, and\n// `var(--color-primary-content)` is used bare 136 times with no fallback. But its 35 themes were\n// clearly generated, and the formula reads straight off them. Of 280 fill/ink pairs, 156 carry the\n// fill's hue to four decimal places, and within those:\n//\n// · fill light, ink dark → L × 0.2 (97/109 within ±0.5pp) C × 0.2 (101/109 within ±0.005)\n// · fill dark, ink light → L = 0.8 + 0.2·L (exact on 31/47)\n//\n// One sentence: **carry the hue, cut the chroma to a fifth, and pull the lightness 80% of the way\n// to the nearer end.** The 47 pairs that miss are pure-black fills where a person chose pure white,\n// and the 124 that do not share the hue at all were adjusted by hand afterwards.\n//\n// ## Where we do not follow it\n//\n// daisyUI picks the branch on a lightness threshold — measured, its ink goes dark from L≥58.9 and\n// light up to L≤56.9. Choosing instead by *which branch actually contrasts more* flips a little\n// earlier (53–55% depending on chroma, so the two rules disagree over a narrow band) and it is the\n// better question to ask, because the band they disagree over is exactly where the threshold picks\n// the unreadable one.\n//\n// So {@link inkFor} takes daisyUI's value on the branch that contrasts more, and treats 4.5:1 as a\n// floor rather than a hope: if that value does not clear AA it pushes the lightness on to the end,\n// then lets the chroma go, then tries the other branch. Measured over 9,178 in-gamut fills spanning\n// L, C and hue:\n//\n// | | below AA 4.5:1 | worst |\n// |---|---|---|\n// | daisyUI's own 280 shipped pairs | 40 (14.3%) | 3.04:1 |\n// | the formula alone | 1,041 (11.3%) | 3.16:1 |\n// | {@link inkFor} | **0** | **4.50:1** |\n//\n// The second departure is `LIGHT_PULL`, and it has its own note. Between the two, the distance from\n// the plain formula is worth stating twice because the two numbers say different things: over\n// daisyUI's own shipped pairs the median difference is ΔE 0.60 and 21 of 23 land within 8, so on\n// the colours daisyUI actually chose these are the same rule. Over the synthetic sweep — which is\n// mostly fills nobody would pick — 4,414 of 9,178 come back byte-identical and the median\n// difference is ΔE 4.62, a just-noticeable step, concentrated on the pale branch.\n//\n// ## What this cannot prove\n//\n// **That the pair looks good.** AA is a floor on legibility, not a judgement about a palette, and a\n// pair can clear 4.5:1 and still be ugly. The studio shows the number beside the swatch for that\n// reason: the rule proposes, the author disposes, and what ships is whatever hex is in the file.\n//\n// **Anything about the fill's own contrast** against the surface it sits on. That is a different\n// pair and `status.test.ts` is what measures it, over the themes that actually ship.\n\n/** A colour in OKLCh. `l` is 0–1 (not a percentage), `h` is degrees. */\nexport interface Oklch {\n l: number;\n c: number;\n h: number;\n}\n\nconst toLinear = (c: number) => (c <= 0.04045 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4);\nconst toGamma = (c: number) => (c <= 0.0031308 ? 12.92 * c : 1.055 * c ** (1 / 2.4) - 0.055);\nconst clamp = (v: number) => Math.min(255, Math.max(0, Math.round(v * 255)));\n\n/** A hex as OKLCh. */\nexport function oklch(hex: string): Oklch {\n const h = hex.trim().replace(/^#/, \"\");\n const [r = 0, g = 0, b = 0] = [0, 2, 4].map((i) =>\n toLinear(Number.parseInt(h.slice(i, i + 2), 16) / 255),\n );\n const lp = Math.cbrt(0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b);\n const mp = Math.cbrt(0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b);\n const sp = Math.cbrt(0.0883024619 * r + 0.2817188376 * g + 0.6299787005 * b);\n const A = 1.9779984951 * lp - 2.428592205 * mp + 0.4505937099 * sp;\n const B = 0.0259040371 * lp + 0.7827717662 * mp - 0.808675766 * sp;\n return {\n l: 0.2104542553 * lp + 0.793617785 * mp - 0.0040720468 * sp,\n c: Math.hypot(A, B),\n h: ((Math.atan2(B, A) * 180) / Math.PI + 360) % 360,\n };\n}\n\n/**\n * OKLCh back to a hex, clamped into sRGB.\n *\n * Clamping is a real loss and it is the right one here: a proposal that lands outside sRGB has to\n * become *some* hex to go in the file, and the author sees the swatch and the ratio for the colour\n * that will actually ship rather than for the one the maths wanted.\n */\nexport function hex({ l, c, h }: Oklch): string {\n const rad = (h * Math.PI) / 180;\n const A = c * Math.cos(rad);\n const B = c * Math.sin(rad);\n const lp = (l + 0.3963377774 * A + 0.2158037573 * B) ** 3;\n const mp = (l - 0.1055613458 * A - 0.0638541728 * B) ** 3;\n const sp = (l - 0.0894841775 * A - 1.291485548 * B) ** 3;\n return `#${[\n toGamma(4.0767416621 * lp - 3.3077115913 * mp + 0.2309699292 * sp),\n toGamma(-1.2684380046 * lp + 2.6097574011 * mp - 0.3413193965 * sp),\n toGamma(-0.0041960863 * lp - 0.7034186147 * mp + 1.707614701 * sp),\n ]\n .map((v) => clamp(v).toString(16).padStart(2, \"0\"))\n .join(\"\")}`;\n}\n\nconst luminance = (hexValue: string): number => {\n const h = hexValue.trim().replace(/^#/, \"\");\n const [r = 0, g = 0, b = 0] = [0, 2, 4].map((i) =>\n toLinear(Number.parseInt(h.slice(i, i + 2), 16) / 255),\n );\n return 0.2126 * r + 0.7152 * g + 0.0722 * b;\n};\n\n/** The WCAG 2.x ratio between two opaque hexes, 1–21. Order does not matter. */\nexport function contrast(a: string, b: string): number {\n const [hi, lo] = [luminance(a), luminance(b)].sort((x, y) => y - x) as [number, number];\n return (hi + 0.05) / (lo + 0.05);\n}\n\n/** The floor {@link inkFor} will not knowingly return below — WCAG AA for body text. */\nexport const AA = 4.5;\n\n/**\n * The hue kept, the chroma at a fifth, the lightness pulled toward one end.\n *\n * `k` is how far. daisyUI's own value is 0.8 both ways, and this leans on `LIGHT_PULL` for the pale\n * branch instead — see the constant.\n */\nconst pull = (fill: Oklch, dark: boolean, k: number, chroma = 0.2): Oklch => ({\n l: dark ? fill.l * (1 - k) : fill.l + (1 - fill.l) * k,\n c: fill.c * chroma,\n h: fill.h,\n});\n\n/** daisyUI's, unchanged: a dark ink sits at a fifth of its fill's lightness. */\nconst DARK_PULL = 0.8;\n\n/**\n * A pale ink goes further than daisyUI takes it — 0.9 of the way to white rather than 0.8.\n *\n * Measured, not preferred. Against daisyUI's own fixture the two are indistinguishable — 21 of 23\n * within ΔE 8 either way, median 0.60 — because daisyUI's dark fills are few and it hand-picked\n * most of their inks anyway. Against the 112 fill/ink pairs the sixteen hand-written themes ship, 0.8\n * reproduces 84% and 0.9 reproduces **all of them**, and every one of 0.8's misses is the same\n * shape: a dark fill where the house wrote a near-white ink and daisyUI's constant stops at a\n * tinted grey.\n *\n * What it costs is real and small: on a synthetic sweep the pale branch now differs from daisyUI's\n * by ΔE 4.62 at the median where before it was identical. That is a just-noticeable step, paid on\n * fills nobody picked, to be exactly right on 112 that somebody did.\n *\n * Going all the way to white fits this house perfectly too and daisyUI barely at all (13 of 23),\n * which is what a constant overfitted to one corpus looks like. It was not taken.\n */\nconst LIGHT_PULL = 0.9;\n\n/**\n * The *other* ink a status colour needs: the one that reads on the page, not on the fill.\n *\n * Shark's convention, and it catches people out because the two look like the same token wearing\n * different suffixes. `--destructive-content` is what sits **on** `--destructive` — white text in a\n * filled button. `--destructive-foreground` is destructive text on `--background` — an error line\n * under an input, where the fill never appears. One is a contrast pair with the fill, the other is\n * a member of the fill's family that survives being read at body weight on the page.\n *\n * So it is not {@link inkFor}: pulling to the far end would give near-black, which is not red any\n * more. It is the fill walked *toward the page's own ink* until it reads, which keeps the hue.\n *\n * Sixty percent, and the number is measured rather than chosen: across the sixteen hand-written\n * themes it reproduces all sixty-four authored values at ΔE 4.6 in the worst case and about 3 at\n * the median. The per-token optima were 60, 60, 59 and 62, which is one number with rounding on it\n * rather than four. The thirteen themes imported from daisyUI author fifty-two more and they are\n * not evidence either way: `import-daisy.mjs` calls this function to write them.\n *\n * `surface` is the page the ink is read on, and it is optional because the mix does not need it —\n * only the *floor* does. Given one, the result is walked further toward `ground` until it clears\n * AA there, which is the same discipline {@link inkFor} applies to its own pair. Without it the\n * function answers what daisyUI's generator would have answered, and the caller measures.\n */\nexport function pageInk(fill: string, ground: string, surface?: string): string {\n const [a, b] = [oklch(fill), oklch(ground)];\n const rad = (v: Oklch) => (v.h * Math.PI) / 180;\n const at = (p: number) => {\n const A = a.c * Math.cos(rad(a)) * p + b.c * Math.cos(rad(b)) * (1 - p);\n const B = a.c * Math.sin(rad(a)) * p + b.c * Math.sin(rad(b)) * (1 - p);\n return hex({\n l: a.l * p + b.l * (1 - p),\n c: Math.hypot(A, B),\n h: ((Math.atan2(B, A) * 180) / Math.PI + 360) % 360,\n });\n };\n const measured = at(0.6);\n if (surface === undefined || contrast(measured, surface) >= AA) return measured;\n // Sixty percent is where the authored values sit, not a floor. Give it the surface the ink is\n // read on and it becomes one: walk further toward `ground` until the pair clears AA. Every value\n // the sixteen hand-written themes author already does, so this changes nothing for them. It\n // answers for the imported ones, and `lemonade` — imported since, and the reason this exists — is\n // the whole of it: all four of its page inks land at 3.85–3.93 and are walked back until they\n // clear.\n for (let p = 0.58; p >= 0; p -= 0.02) {\n const candidate = at(p);\n if (contrast(candidate, surface) >= AA) return candidate;\n }\n return at(0);\n}\n\n/** Walk one branch out to its end, giving up lightness first and chroma only when that runs out. */\nfunction reach(fill: string, colour: Oklch, dark: boolean): string {\n const start = dark ? DARK_PULL : LIGHT_PULL;\n const base = hex(pull(colour, dark, start));\n if (contrast(fill, base) >= AA) return base;\n for (let k = start; k <= 1.0001; k += 0.01) {\n const candidate = hex(pull(colour, dark, k));\n if (contrast(fill, candidate) >= AA) return candidate;\n }\n for (let chroma = 0.2; chroma >= -0.0001; chroma -= 0.02) {\n const candidate = hex(pull(colour, dark, 1, chroma));\n if (contrast(fill, candidate) >= AA) return candidate;\n }\n return hex(pull(colour, dark, 1, 0));\n}\n\n/**\n * The ink that belongs on `fill` — a hex, ready to be written into a theme.\n *\n * Returns daisyUI's own value wherever that clears AA, and the nearest thing to it that does\n * wherever it would not. See the module docblock for the measurements behind both halves.\n */\nexport function inkFor(fill: string): string {\n const colour = oklch(fill);\n const dark =\n contrast(fill, hex(pull(colour, true, DARK_PULL))) >=\n contrast(fill, hex(pull(colour, false, LIGHT_PULL)));\n const first = reach(fill, colour, dark);\n if (contrast(fill, first) >= AA) return first;\n // The preferred branch can cap out below AA where the other one, pushed, does not.\n const second = reach(fill, colour, !dark);\n return contrast(fill, second) > contrast(fill, first) ? second : first;\n}\n","import themeDataJson from \"../theme-data.json\";\nimport type { SectionPrefDecl } from \"./sections.js\";\n\n/**\n * The generated theme tables — the four non-colour axes — as a JS module.\n *\n * Consumers must read them through this export rather than importing\n * `@kanzo-tech/theme/theme-data.json` directly. A raw JSON subpath import is an ESM JSON\n * import at runtime, which Node rejects without `with { type: \"json\" }` — and Rollup strips\n * that attribute when bundling, so there is no way to make the direct import survive a build.\n * Bundling the data into this package's own JS entry is safe: it is the package that owns the\n * data, so the copy can never skew from the CSS generated alongside it.\n *\n * There is no derivation any more, so there are no tables for one to read: a theme is\n * `themes/<name>.css`, hand-written source, and `themes` below is the catalogue read off disk by\n * the generator so no second list can drift from it.\n */\nexport const themeData = themeDataJson;\nexport type ThemeData = typeof themeDataJson;\n\n/**\n * The themes this package ships, as data a picker can render.\n *\n * daisyUI keeps two registries — `themeOrder` (the ordered names) and `theme/object` (name → the\n * variable map) — precisely so a switcher can draw a theme without parsing its CSS. This is the\n * first: an entry carries what a control needs to *offer* a theme and nothing a page needs to\n * *paint* one, which is the theme's own stylesheet's job.\n *\n * **It replaces `paletteIndex`, and it is a flat list where that was a tree.** A palette used to\n * contain identities, so an entry had `children` and a picker had two levels. A brand is a theme\n * now, so `bank` and `bank-private` sit side by side and the second level is gone.\n *\n * Generated from the directory, not listed: `scripts/gen-theme.mjs` reads `themes/` and records\n * each file's own `color-scheme`. Adding a theme is adding a file.\n *\n * Read through this export rather than importing `@kanzo-tech/theme/theme-data.json`, for the\n * reason given on {@link themeData}: a raw JSON subpath import is an ESM JSON import at runtime, and\n * Rollup strips the attribute that would make it legal.\n */\nexport const themeIndex = themeDataJson.themes as ThemeIndexEntry[];\n\n/**\n * One theme, as a control sees it.\n *\n * **It carries no colours.** A theme travels in the page under its own `[data-theme]`, so a control\n * depicts one by *setting the attribute* and letting the cascade answer — which is also why a\n * preview is a `div` and not a strip of swatches. A handful of hexes could not depict a theme\n * anyway; on the default's own, two of the four a picker used to publish were the same value.\n *\n * `dark` is the theme's own `color-scheme`, and it is the whole of what \"a theme is one mode\"\n * means at this layer: it is a property of the theme, not a second axis crossed with it.\n */\nexport interface ThemeIndexEntry {\n name: string;\n dark: boolean;\n}\n\n/**\n * How many `--chart-N` custom properties the stylesheet declares. A 9th series folds into \"Other\" —\n * never cycle, or identity stops meaning anything. (`--chart-capacity` is declared beside them and\n * is not one of them; `boundary.test.ts` counts `--chart-N` only.)\n *\n * A fact about the SHEET, which is why it is here and not with the derivation that emits it: a\n * chart resolving `var(--chart-N)` off the cascade needs the count, and a chart runs in a browser\n * and is checked against the shipped theme files rather than trusted. The count is a compile-time constant\n * because a stylesheet cannot have a variable number of custom properties.\n *\n * How many of the slots carry a *real* category is the document's `capacity`, which can be lower;\n * it travels down the cascade as `--chart-capacity`, and past it `compile()` writes\n * `var(--muted-foreground)`.\n *\n * `packages/palette` declares the same number, because it is what emits the properties. Neither\n * copy is trusted: `boundary.test.ts` counts the declarations in the shipped `tokens.css` and\n * holds both against it.\n */\nexport const CHART_SLOTS = 8;\n\n/**\n * `@kanzo-tech/theme` — design tokens, the theme axis table, and the value types. No React.\n *\n * **Colour IS an axis now, and it is the same kind of axis as the rest.** A theme is one flat block\n * of CSS under `[data-theme=\"<name>\"]` — about fifty-five declarations somebody writes, pastes and\n * diffs — so applying one is writing an attribute, exactly like radius or density. It stopped being\n * special when it stopped being the output of a thirteen-stage derivation.\n *\n * Everything is driven by `data-*` attributes on `<html>`, and the values live in `themes.css`:\n * · `data-theme` — selects a whole theme: its colours, its shape knobs and its fonts.\n * · `data-radius` — the three radius knobs, as a user preference over the theme's own.\n * · `data-font` — sets `--font-sans` / `--font-heading`.\n * · `data-mono-font` — sets `--font-mono`.\n * · `data-font-size` — sets the root font-size (the rem density scale).\n *\n * **`data-theme` is `data-palette` and `data-identity` collapsed, and the authority question they\n * modelled has dissolved rather than been decided.** `data-palette` selected from a catalogue the\n * LIBRARY shipped, so a user picking Dracula could overrule a client's branding; `data-identity`\n * selected among brands the CLIENT authored, one level down. A tenant's brands are now themes\n * beside every other theme, so what a user may choose is what the tenant's policy admits — the\n * `pinned` / `hidden` chain that already governs every other section, rather than two attributes\n * with different pedigrees.\n *\n * Writing the attributes is `<KanzoThemeProvider>`'s job, from `@kanzo-tech/ui`. It sets them on\n * `document.documentElement` — see the AXES note below for why a wrapper element cannot work.\n *\n * Dark mode is not a token flip any more: a theme carries its own `color-scheme` and its own\n * colours, so `.dark` survives only as the selector for the `dark:` VARIANT at the call sites that\n * still ask for one.\n *\n * Requires `@kanzo-tech/ui/tailwind.css` (or the raw token/theme CSS) in the host's Tailwind build.\n */\n\n/**\n * A side of the compiled document. `compile()` always emits both blocks, so there are exactly two.\n *\n * **There is no `\"system\"`, and its absence is the design.** Following the OS is a real behaviour we\n * keep — without it the first visit has to guess, and guessing wrong flashes white at every\n * dark-mode user — but it is the state with *no* value, not a third value.\n *\n * That split is the reference systems', and they divide on which layer they are. The JS\n * theme-switching libraries make it a value: next-themes ships `defaultTheme = \"system\"` and appends\n * `\"system\"` to its `themes` array, MUI has `mode: \"light\" | \"dark\" | \"system\"`, Mantine `\"auto\"`.\n * The *token* layers do not: daisyUI writes `themes: light --default, dark --prefersdark`, where the\n * OS preference is a flag on a theme and `data-theme` overrides it; Tailwind has a media query or a\n * class; Radix Themes declines to model it and delegates to next-themes. And CSS itself has no third\n * keyword — `color-scheme: light dark` means \"the OS decides\", and an explicit side overrides.\n *\n * We are a token layer: a document with a `:root` block and a `.dark` block. `\"system\"` arrived here\n * as next-themes vocabulary for a mechanism we do not use, and `themeScript` never believed in it —\n * it has always resolved \"anything that is not an explicit side\" against `matchMedia`.\n *\n * A host next-themes IS still supported; `KanzoThemeProvider` translates its `\"system\"` to `null` in\n * one place, the way every other foreign vocabulary enters this system.\n */\nexport type Appearance = \"light\" | \"dark\";\n\n/**\n * The appearance PREFERENCE — an explicit side, or `\"\"` for \"ask the OS\".\n *\n * A value and not an absent key: the read-time whitelist is built from `Object.keys(DEFAULT_PREFS)`,\n * so a key missing from the default blob is dropped on every read. It also survives\n * `JSON.stringify` into both storage adapters, which an `undefined` would not.\n *\n * **`\"\"` and not `null`, which is what it was.** Unset is the same value here as everywhere else in\n * this package: a theme key stores `\"\"` for \"defer to the tenant\", and the write rule\n * removes an attribute at the default. Two spellings of one idea is what kept appearance out of the\n * declaration — a `SectionPrefDecl`'s values are strings — and therefore out of the one resolution\n * chain, which is the whole of what {@link CORE_PREFS} exists to end. Declared, \"follow the OS\" is\n * `{ value: \"\", label: \"System\" }`: a thing a control can offer, rather than something reachable\n * only through the panel's Reset button.\n */\nexport type AppearancePref = Appearance | \"\";\n\n/** Radius steps (`md` = 0.5rem default). */\nexport type KanzoRadius = \"none\" | \"xs\" | \"sm\" | \"md\" | \"lg\";\n\n/** Density (root font-size rem-scale); `default` omits the attribute. */\nexport type KanzoDensity = \"default\" | \"compact\" | \"comfortable\";\n\n/** Sans font key — host-extensible; the DS ships `system`/`geist`/`inter` stacks. */\nexport type KanzoFont = \"system\" | \"geist\" | \"inter\" | (string & {});\n\n/** Mono font key — host-extensible; the DS ships `system`/`geist-mono`/`jetbrains-mono`. */\nexport type KanzoMonoFont = \"system\" | \"geist-mono\" | \"jetbrains-mono\" | (string & {});\n\n/**\n * Theme key — host-extensible; `\"\"` means \"defer to the tenant's default\".\n *\n * This is `KanzoFont`'s case, not `KanzoRadius`': a value is a *host's* string, unknown when this\n * package is built. Where `KanzoFont` still names the three stacks the DS happens to ship, there is\n * nothing to union here — a tenant authors their own themes, so a literal union would be a list\n * that is wrong for every client.\n *\n * **It replaces `KanzoPalette`, `KanzoIdentity` and `KanzoIdentityMemory`, and the collapse is the\n * point.** Those were three types because a palette CONTAINED identities: a document was a two-mode\n * stylesheet, a brand was a partial block layered onto it, and a memory recorded which brand you\n * last wore inside each document so switching away and back returned you to it. A theme is one flat\n * block, so a brand is not inside anything — `bank` and `bank-private` are two themes — and there is\n * no containment left for a memory to remember.\n */\nexport type KanzoThemeName = string;\n\n/**\n * One published theme, as the runtime sees it — the contract between the catalogue and the panel.\n *\n * It carries no colours, and it has no `children`. A control depicts a theme by setting\n * `data-theme` on an element and letting the cascade paint it: the theme is already in the page, so\n * a depiction copied out of it is a second spelling that can only ever be the same colours or the\n * wrong ones.\n *\n * **`children` went with the containment it modelled.** A palette used to hold brands, so an option\n * held options and the panel flattened a tree into one list. There is no tree: a brand is a theme.\n */\nexport interface ThemeOption {\n value: string;\n label: string;\n}\n\n// ── The axis table — the single source of truth for how a preference reaches the DOM ────────\n//\n// This lives here, not in @kanzo-tech/ui, because three separate things must agree on it and they\n// are split across two packages: the React provider and the SSR pre-hydration script, both in\n// `@kanzo-tech/ui`, and `scripts/gen-theme.mjs` here, which decides which selectors exist in\n// themes.css at all. Only this package holds all three in view. When they drifted there was no type\n// error to catch it — miss the generator and the provider writes an attribute no CSS matches; miss\n// the script and the FOUC it exists to prevent comes back.\n\n/**\n * The user's preferences. Six, and only one of them is a colour.\n *\n * *Free* colour left this table entirely: `palette`, `base`, `accent`, `primary`, `baseTint`,\n * `scheme` and `schemeColors` were seven ways to express *part* of a palette at runtime, and a\n * document expresses all of it at once, before a byte is sent. `appearance` stays because it is\n * the one colour-adjacent thing a user genuinely chooses, and it selects between two blocks of one\n * document. `identity` is the same kind of choice one level up: between blocks the TENANT\n * published, and never between a value they did not.\n */\nexport interface ThemePrefs {\n appearance: AppearancePref;\n radius: KanzoRadius;\n font: KanzoFont;\n monoFont: KanzoMonoFont;\n density: KanzoDensity;\n /**\n * Which theme this user wears on each side — `{}` while they have chosen neither.\n *\n * A map rather than a string because the choice is per appearance, and with one-mode themes that\n * is not a refinement but the only shape available: a theme IS a side, so \"which theme\" without\n * \"on which side\" does not name a preference. It is also exactly what the per-appearance palette\n * proposal asked for, arrived at from the other\n * direction — that proposal invented a map over documents that each carried both modes, to say\n * something the two-mode document made awkward and the one-mode theme makes trivial.\n *\n * The ordering that makes it possible: the pre-hydration script resolves appearance before it\n * writes anything, so it can index this map. That is the thing to check first when touching it.\n */\n themeByAppearance: Partial<Record<Appearance, KanzoThemeName>>;\n /**\n * What the packages a host installed contribute, keyed by namespace then by preference.\n *\n * **One key, and that is what makes an absent package harmless.** The read-time whitelist is built\n * from `Object.keys(DEFAULT_PREFS)` and drops everything else, which is right for the retired\n * colour axes it was built for and exactly wrong for a contributed choice: a host that drops an\n * optional peer for one release would lose the user's stored value on the next write. Riding on a\n * single known key, an unrecognised namespace survives every read and save without the core\n * knowing it exists — the same opacity {@link LookDocument}'s `sections` already has, which is the\n * point: both halves of a section are stored the same way.\n */\n sections: Record<string, Record<string, string>>;\n}\n\n/**\n * Every key of `ThemePrefs`, with no exception for the ones whose default is empty.\n *\n * `PREF_KEYS` in the provider is `Object.keys(DEFAULT_PREFS)`, and the read-time whitelist built\n * from it drops anything not listed. A pref missing here therefore works for exactly one session\n * and is gone on the next read, silently and with no type error — the retired-key hygiene rule\n * turned on a live field.\n */\nexport const DEFAULT_PREFS: ThemePrefs = {\n // `\"\"`, not `\"system\"` and no longer `null`: the default is to have no side pinned, spelled the\n // way every other deferral in this table is — a theme key of `\"\"` defers to the tenant, this\n // defers to the OS. One spelling is what lets it be declared, and therefore resolved, like the rest.\n appearance: \"\",\n radius: \"md\",\n font: \"system\",\n monoFont: \"system\",\n density: \"default\",\n // Empty, and both sides fall through to the tenant's default — so a tenant shipping one theme per\n // side stores nothing and gets the `<html>` it always had. `\"\"` per side means the same thing:\n // defer to whichever theme the tenant made default.\n themeByAppearance: {},\n // Empty, and never seeded from the registered manifests: a section's default is what `resolvePref`\n // answers when nothing is stored, so writing it in here would turn every default into a *stored\n // choice* the first time the panel opened — and a default that has been stored can no longer move\n // when the section, or the tenant's policy, changes it.\n sections: {},\n};\n\nexport const STORAGE_KEY = \"kanzo_theme_prefs\";\n\n/**\n * The core's own preferences, declared — one entry per axis, in the shape a package contributes.\n *\n * **Generated, and that is the point.** `scripts/gen-theme.mjs` authors the values *and* the\n * declaration, so the option list a control offers is the table the CSS was emitted from rather than\n * a hand-copy beside it. `Preferences.tsx` held two such copies — `RADII` and `DENSITIES` — sitting\n * next to the generated tables they duplicated, and `KanzoThemeProvider` held a third of the font\n * stacks with a fallback string that had already drifted from the sheet's. Adding a font is now one\n * line in the generator: the panel grows a card and the docs table grows a row.\n *\n * The rule this installs, and it is the same one the colour half follows: **a configuration is\n * authored once, where its values live.** The declaration, the control, the default and the\n * attribute are resolved from it.\n *\n * `source` is the one field a contributed preference has no use for. It says WHO emits the selectors\n * the attribute matches: `\"themes\"` is our generator, so the drift guards in `index.test.ts` can\n * hold the declaration against the sheet; `\"document\"` is `compile()`, from something a TENANT\n * authored after this package was built, and asserting a generated table for it would fail for the\n * right feature.\n */\nexport type CorePrefDecl = SectionPrefDecl & { source?: \"themes\" | \"document\" };\n\n/**\n * Cast, because JSON is data and TypeScript reads it as widened literals — `kind: string` will not\n * narrow to the union however it is written. The check is therefore a runtime one, in\n * `index.test.ts`: every key is a key of `DEFAULT_PREFS`, every declaration is well-formed, and\n * `check:generated` regenerates the file and fails on a diff.\n */\nexport const CORE_PREFS = themeData.prefs as unknown as Readonly<Record<CorePrefKey, CorePrefDecl>>;\n\n/**\n * Every preference the core declares — which is every key of {@link ThemePrefs} except the two that\n * are not choices at all.\n *\n * Written as an exclusion rather than a list, so the two exceptions have to justify themselves:\n * `identityByPalette` is a *memory* (what this user last wore in each document, consulted only when\n * the palette changes), and `sections` is the opaque bag another package's preferences ride in. A\n * new axis appears here by appearing in `ThemePrefs`, and the generator has to answer for it.\n */\nexport type CorePrefKey = Exclude<keyof ThemePrefs, \"sections\">;\n\n/**\n * The namespace the core's own preferences answer to in a tenant's policy.\n *\n * **The core is a section like any other, and this is the whole of what that costs.** A policy is\n * keyed by namespace — `{ theme: { radius: { pinned: \"sm\" } }, graph: { look: { hidden: true } } }`\n * — so a client shipping *compact and square* uses the mechanism an optional package already uses,\n * and one chain answers for colour, geometry and a contributed choice alike.\n *\n * That is daisyUI's insight, in the mechanism this repo already had: their theme carries the\n * geometry (`--radius-box`, `--size-field`, `--depth`) in the same document as the colours, so a\n * tenant ships a coherent whole rather than a panel of unrelated knobs. Ours went half-way there\n * when a palette became a document; the half not taken was that radius, density and the fonts had no\n * document-level default at all — only a user could move them.\n */\nexport const CORE_NAMESPACE = \"theme\";\n\n/**\n * Each axis → its `<html>` attribute + default value (at the default the attribute is removed).\n *\n * A projection of {@link CORE_PREFS} and no longer a table of its own: three things must agree about\n * an axis — the React provider, the SSR pre-hydration script, and the generator that decides which\n * selectors exist at all — and they now agree because there is one place to disagree with.\n *\n * These attributes go on `<html>`, never a wrapper element. Ark overlays (Dialog, Popover,\n * Menu, Select, Tooltip, Toast…) portal to `document.body`, outside any wrapper, so tokens set\n * on a wrapper would not reach them. `density` additionally *must* be on the root: it sets the\n * root font-size and every size in the system is `rem`.\n */\nexport const AXES: {\n key: keyof ThemePrefs;\n attr: string;\n def: string;\n source: \"themes\" | \"document\";\n /** See {@link SectionPrefDecl}. The stored value is a map keyed by the resolved appearance. */\n byAppearance?: true;\n}[] = Object.entries(CORE_PREFS)\n // The rows with somewhere to write. `appearance` is declared beside these and is not one: it\n // writes a class. A contributed preference makes the same distinction with the same field.\n .filter(([, decl]) => Boolean(decl.attr))\n .map(([key, decl]) => ({\n key: key as keyof ThemePrefs,\n attr: decl.attr as string,\n def: decl.default,\n source: decl.source ?? \"themes\",\n byAppearance: decl.byAppearance,\n }));\n\n// ── Sections ────────────────────────────────────────────────────────────────────────────────────\n//\n// The mechanism only. The core exports the document shape, the fallback resolver and the validator,\n// and knows no section: a package contributes by USING a namespace, never by registering here.\n// That is what keeps `@kanzo-tech/theme` free of any reference to `@kanzo-tech/graph`.\nexport {\n fallbackChain,\n resolvePref,\n prefBoolean,\n prefNumber,\n prefOptions,\n resolveSectionToken,\n sectionOf,\n validatePrefs,\n validateSection,\n withSection,\n type LookDocument,\n type PrefOption,\n type PrefOptions,\n type PrefOrigin,\n type PrefSource,\n type PrefSources,\n type Problem,\n type ResolvedPref,\n type SectionManifest,\n type SectionPolicy,\n type SectionPrefDecl,\n type SectionPrefPolicy,\n type SectionTokenDecl,\n} from \"./sections.js\";\n\n// The density axis's own obligations — what admits it as a section rather than a preference.\nexport {\n check as checkDensity,\n OBLIGATIONS as DENSITY_OBLIGATIONS,\n type Check as DensityCheck,\n type Obligation as DensityObligation,\n} from \"./obligations.js\";\n\n// Authoring-time colour, and the only derivation in the package. It runs in a form while somebody\n// picks a fill, never in a page painting one: what ships is still a flat block of hex, and this is\n// how the boring half of it stops being typed by hand. See `ink.ts` for the measurements.\nexport {\n AA,\n contrast,\n hex,\n inkFor,\n oklch,\n pageInk,\n type Oklch,\n} from \"./ink.js\";\n"],"names":["prefOptions","decl","sources","list","prefBoolean","value","prefNumber","n","resolvePref","stored","policy","legal","v","options","o","offered","validatePrefs","manifest","problems","prefs","key","ok","expected","values","fallbackChain","token","parts","out","i","resolveSectionToken","declared","candidate","hit","bare","validateSection","prefix","parent","sectionOf","doc","namespace","withSection","tokens","DENSITIES","themeDataJson","px","minRoot","SMALLEST_CONTROL_REM","OBLIGATIONS","check","toLinear","c","toGamma","clamp","oklch","hex","h","r","g","b","lp","mp","sp","A","B","l","rad","luminance","hexValue","contrast","a","hi","lo","x","y","AA","pull","fill","dark","k","chroma","DARK_PULL","LIGHT_PULL","pageInk","ground","surface","at","p","measured","reach","colour","start","base","inkFor","first","second","themeData","themeIndex","CHART_SLOTS","DEFAULT_PREFS","STORAGE_KEY","CORE_PREFS","CORE_NAMESPACE","AXES"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/sections.ts","../src/obligations.ts","../src/ink.ts","../src/index.ts"],"sourcesContent":["/**\n * Sections: appearance vocabulary contributed by the package that owns it.\n *\n * The core never learns that a section exists. It stores sections **opaquely**, resolves a token by\n * hierarchical fallback, and validates against a manifest the owning package hands it. Contributing\n * is *using* a name, not registering a type here — which is what keeps the one-way door shut\n * structurally rather than by discipline: `@kanzo-tech/theme` gains no reference to\n * `@kanzo-tech/graph`, and `boundary.test.ts` keeps passing because there is nothing to import.\n *\n * ## Resolution is Neovim's, validation is not\n *\n * A dotted name falls back to its parent: `--graph-point-size-min` → `--point-size-min` → the\n * default the manifest declared. Neovim's highlight groups work exactly this way\n * (`@foo.bar.lang → @foo.bar → @foo`) and it is why a section costs the core nothing.\n *\n * **Neovim pays a price we do not: there, a typo degrades silently to the parent.** Here the\n * manifest *declares* its tokens, so resolution goes by fallback and validation goes against the\n * declaration. That combination — contribute *and* validate — is the part no surveyed system does:\n * VS Code's `contributes.colors` and Emacs contribute without validating the namespace, Vanilla\n * Extract validates completely and therefore cannot have optional sections at all, and StyleX\n * retracted that shape. This is unmapped ground, and `sections.test.ts` says what it cannot prove.\n *\n * ## A default may be derived, and that is what makes white-label flow\n *\n * VS Code spells derived defaults as imperative transforms (`darken`, `transparent`, `oneOf`). Ours\n * is a **CSS value**, which is the same idea with nothing between it and the browser:\n * `var(--primary)` points at what the theme publishes, and `color-mix(in oklab, var(--primary) 20%,\n * transparent)` derives from it. A bank changing its theme moves both — **without the core knowing\n * what a graph is**, and without anyone copying a hex.\n *\n * **It was an object with three shapes** — `{kind:\"step\"|\"alpha\", ramp, step}` and\n * `{kind:\"role\", token}` — declarative against the tenant's *ramps*. Two of the three named a\n * reference tier that no longer exists, and the third was `var()` wearing a wrapper. Collapsing them\n * removed a type that had to be declared structurally in two packages at once, so it also removed\n * the drift between the two declarations.\n *\n * That is why this is not the thing that was refused earlier. Minting a *role* in the core's\n * vocabulary for one consumer is how seventeen level-names happened. A section token lives in the\n * owner's namespace, is declared by the owner, and costs nothing to whoever never installs it.\n */\n\nexport interface SectionTokenDecl {\n /**\n * A CSS value. A literal, a `var()` at what the theme publishes, or a `color-mix()` derived from\n * one — and nothing needs to resolve it but the browser.\n */\n default: string;\n /** What the token is for, in one line. Shown by a panel that lists a section's vocabulary. */\n doc: string;\n}\n\n/** What every declaration carries, whatever its kind. */\ninterface PrefCommon {\n /** The value when nothing else answers. Always a string — see {@link SectionPrefDecl}. */\n default: string;\n /** What the preference does, in one line. Shown by a surface beside the control. */\n doc: string;\n /**\n * What a surface CALLS it. Falls back to the key, which is what every surface used to draw.\n *\n * The options carried labels and the preference did not, so a generic control had a key for a\n * legend: a dock offering the force coefficients announced `link-spring`. A name is authored where\n * the values are, like everything else here — and it is not the `doc`, which is a sentence about\n * what the thing does rather than the two words a control is titled with.\n */\n label?: string;\n /**\n * The stored value is a map keyed by the resolved appearance, not a plain string.\n *\n * One field on the declaration rather than a second table beside it. It is only expressible\n * because appearance resolves *first* — the pre-hydration script has to resolve it to write\n * `.dark` — so by the time anything reads this, the side to index by is known.\n */\n byAppearance?: true;\n /**\n * The attribute to write on `<html>` — **only where CSS has to react.**\n *\n * Most contributed preferences have none. The graph's look is read by JS and pushed into a\n * renderer's config; an editor's font size would want one. Leaving it out is what keeps the\n * pre-hydration script from growing by a line for every optional package a host installs, which is\n * the cost the four core axes pay for having attributes at all.\n */\n attr?: string;\n}\n\n/** One thing a `choice` may offer. `value` is what is stored; `label` is what a person reads. */\nexport interface PrefOption {\n value: string;\n label: string;\n}\n\n/**\n * Where a choice's options come from, when an author cannot list them.\n *\n * The one thing a TENANT publishes that nobody can know at authoring time: the themes they wrote.\n * Closed on purpose — a source is something the provider already receives and can hand to a\n * resolver, not a hook for a package to fetch from.\n *\n * It was two, `\"palettes\"` and `\"identities\"`, while a palette contained brands and a control had to\n * ask which level it was filling. A brand is a theme, so there is one level and one list.\n */\nexport type PrefSource = \"themes\";\n\n/**\n * A list, or the name of the list's owner.\n *\n * This is the same move {@link SectionTokenDecl} already makes for colour — a **binding** instead of\n * a value — so the vocabulary stays one. It is what `theme` needs: its options are\n * a client's brands, and an author who typed them would be authoring the client's product.\n */\nexport type PrefOptions = readonly PrefOption[] | { from: PrefSource };\n\n/** What the host publishes, for the declarations that name a source. */\nexport type PrefSources = Partial<Record<PrefSource, readonly PrefOption[]>>;\n\n/**\n * The options a declaration offers — `null` when it names a source nobody has answered yet.\n *\n * `null` rather than `[]`, and the distinction is load-bearing: \"this tenant published nothing\",\n * \"the host has not wired the prop\" and \"the document is still being fetched\" are indistinguishable\n * from here, and every one of them must leave a stored value alone. An empty list would read as\n * *nothing is legal* and quietly reset a user's brand on the first render before the fetch lands —\n * which is the failure `useRetirement` guards with `options.length > 0` one layer up.\n */\nexport function prefOptions(\n decl: SectionPrefDecl,\n sources?: PrefSources,\n): readonly PrefOption[] | null {\n if (decl.kind !== \"choice\") return null;\n if (Array.isArray(decl.options)) return decl.options;\n const list = sources?.[(decl.options as { from: PrefSource }).from];\n return list && list.length > 0 ? list : null;\n}\n\n/**\n * One preference a section offers its user — the half of a manifest the person on the screen decides.\n *\n * **Three kinds, and the set is closed.** A section may offer a value it has measured; it may not\n * offer a text field, and there is deliberately no `custom` escape. That is the same line the colour\n * layer holds one level up — choose among what somebody validated, never author — and it is what\n * lets any surface render any section without knowing which package wrote it.\n *\n * The three arrived together with their call sites rather than ahead of them. `choice` shipped\n * alone and covered the graph's look; the eleven controls it could not express are what asked for\n * the other two — three toggles and two scalars in the graph's Display, six coefficients in its\n * simulation dock, every one of them hand-rolled with its own wiring.\n *\n * **\"Follow the system\" is an option and not a fourth kind.** It is the option whose value is `\"\"`,\n * and the convention is already load-bearing here: `identity` and `palette` default to `\"\"`, and the\n * write rule removes the attribute at the default — which is exactly what *the OS decides* means in\n * CSS, where there is no third keyword either. Declared as `{ value: \"\", label: \"System\" }` it is a\n * thing a control can offer, so getting back to it stops being the panel's `Reset` button's private\n * power.\n *\n * **A value is a string in all three**, and that is a decision rather than an oversight. One storage\n * shape means an unrecognised namespace rides through a write untouched without the core parsing\n * it; and a value that can be written to a `data-*` attribute needs no second spelling on its way\n * out. A renderer parses what its own kind means — `\"true\"`, `\"0.42\"` — and the parsing is one\n * place, {@link prefNumber} and {@link prefBoolean}.\n */\nexport type SectionPrefDecl =\n /** Pick one of a closed list — or of a list only the tenant can write. */\n | (PrefCommon & {\n kind: \"choice\";\n /** Everything a control may offer, in order. Order is the section's, and a surface keeps it. */\n options: PrefOptions;\n })\n /** On or off. Stored as `\"true\"` / `\"false\"`. */\n | (PrefCommon & { kind: \"toggle\" })\n /**\n * A number within declared bounds.\n *\n * The bounds are the section's own claim about what it will honour, so a stored value outside them\n * is declined the same way a retired option is: a slider that used to run to 5 and now stops at 3\n * must not paint 5 because storage remembers it.\n */\n | (PrefCommon & { kind: \"range\"; min: number; max: number; step: number });\n\n/** Read a `toggle`'s value. Anything that is not exactly `\"true\"` is off. */\nexport const prefBoolean = (value: string): boolean => value === \"true\";\n\n/** Read a `range`'s value. `NaN` never escapes: a corrupt string answers the declared minimum. */\nexport function prefNumber(value: string, decl: { min: number }): number {\n const n = Number.parseFloat(value);\n return Number.isFinite(n) ? n : decl.min;\n}\n\n/**\n * What a tenant may say about a section's choice — **selection, never authorship.**\n *\n * A document may fix a choice or withhold it; it may not invent a value for it. The options are the\n * ones the owning package declared and measured, and a policy picks among them or removes the\n * control. That is the same line the colour half holds — a user chooses among colours somebody\n * validated and never authors one — and it is what stops this field becoming the runtime palette\n * authoring the retired axes exist to prevent.\n */\nexport interface SectionPrefPolicy {\n /** Fixed to this value. No control is offered and a stored preference does not apply. */\n pinned?: string;\n /** Not offered. The default applies, and a stored preference is kept but does not apply. */\n hidden?: boolean;\n /** Offered, but starting somewhere other than the manifest says. */\n default?: string;\n}\n\n/**\n * What a package publishes to contribute a section. Reached by subpath, never imported here.\n *\n * **One manifest carries both halves**, and they are both optional. A package may contribute tokens\n * without preferences (a vocabulary a document decides), preferences without tokens (a choice that\n * paints nothing of its own), or both. Two exports would let a namespace and a version drift apart\n * while describing the same section, and the namespace is the whole of the contract.\n */\nexport interface SectionManifest {\n /** The namespace segment. `\"graph\"` produces `--graph-*`. */\n namespace: string;\n version: number;\n /** Keys are token names **without** the namespace: `\"marquee\"`, `\"point-size-min\"`. */\n tokens?: Readonly<Record<string, SectionTokenDecl>>;\n /** Keys are preference names, unqualified: `\"look\"`. */\n prefs?: Readonly<Record<string, SectionPrefDecl>>;\n}\n\n/** A tenant's policy for one section, keyed by preference name. */\nexport type SectionPolicy = Readonly<Record<string, SectionPrefPolicy>>;\n\n/** Where a resolved preference came from — a panel says so, and a test asserts on it. */\nexport type PrefOrigin = \"pinned\" | \"stored\" | \"policy\" | \"default\";\n\nexport interface ResolvedPref {\n value: string;\n via: PrefOrigin;\n /** Whether a control should be drawn at all. `false` for pinned and for withheld. */\n offered: boolean;\n}\n\n/**\n * One chain, most specific first — the sibling of {@link resolveSectionToken}.\n *\n * Writing it as a chain is not decoration: the token half already answers *the first member that\n * answers, and says which one it used*, and a preference resolved by some other order would be a\n * second mechanism wearing the first one's vocabulary.\n *\n * A stored value that is not in `options` is ignored rather than applied. A section's options are\n * the values it has measured; a stale one from a version the package no longer ships is exactly the\n * case where falling back beats honouring what storage happens to hold.\n */\nexport function resolvePref(\n decl: SectionPrefDecl,\n stored: string | undefined,\n policy?: SectionPrefPolicy,\n /** What the tenant published, for a choice whose options name a source rather than listing them. */\n sources?: PrefSources,\n): ResolvedPref {\n const legal = (v: string | undefined): v is string => {\n // `typeof` rather than a check against `undefined`, though the parameter says `string`: this\n // reads a blob out of a browser's storage, and a corrupt one holds whatever it holds. Without\n // it, an axis whose options a tenant owns — where there is no list to judge against — writes\n // `data-identity=\"[object Object]\"`, which is inert, survives in devtools, and is evidence of a\n // bug we chose not to have. The provider used to carry this test beside its write loop.\n if (typeof v !== \"string\") return false;\n // One gate per kind, and every kind has one. A preference with no notion of an illegal value\n // would let storage outlive the declaration that gave it meaning — which is the version-skew\n // case this whole chain exists to survive.\n if (decl.kind === \"choice\") {\n const options = prefOptions(decl, sources);\n // A source nobody has answered yet cannot judge anything, so it judges nothing: the stored\n // value stands and the retirement notice — which is the surface that knows how to *tell* a\n // user their brand is gone — decides later. See {@link prefOptions}.\n return options === null || options.some((o) => o.value === v);\n }\n if (decl.kind === \"toggle\") return v === \"true\" || v === \"false\";\n const n = Number.parseFloat(v);\n return Number.isFinite(n) && n >= decl.min && n <= decl.max;\n };\n\n if (legal(policy?.pinned)) return { value: policy.pinned, via: \"pinned\", offered: false };\n const offered = !policy?.hidden;\n if (offered && legal(stored)) return { value: stored, via: \"stored\", offered };\n if (legal(policy?.default)) return { value: policy.default, via: \"policy\", offered };\n return { value: decl.default, via: \"default\", offered };\n}\n\n/**\n * Check a section's stored preferences against the manifest that owns it.\n *\n * The preference sibling of {@link validateSection}, and the same argument: a name the manifest\n * never declared is reported rather than resolving to something plausible. What differs is that a\n * preference also has a closed set of legal *values*, so a stored value outside `options` is\n * reported too — `resolvePref` already refuses to apply it, and this is what says so out loud.\n */\nexport function validatePrefs(\n manifest: SectionManifest,\n stored: Readonly<Record<string, string>>,\n /** As {@link resolvePref}'s. A choice whose source is unanswered is not judged, and not reported. */\n sources?: PrefSources,\n): Problem[] {\n const problems: Problem[] = [];\n const prefs = manifest.prefs ?? {};\n for (const [key, value] of Object.entries(stored)) {\n const decl = prefs[key];\n if (!decl) {\n problems.push({\n token: key,\n kind: \"unknown\",\n detail: `\"${manifest.namespace}\" declares no preference \"${key}\"`,\n });\n continue;\n }\n // A switch rather than a ternary chain: the range arm needs `decl` narrowed to reach `min`, and\n // `kind === \"choice\" ? … : kind === \"toggle\" ? … : …` does not narrow the last branch.\n let ok: boolean;\n let expected: string;\n switch (decl.kind) {\n case \"choice\": {\n const options = prefOptions(decl, sources);\n if (options === null) continue;\n const values = options.map((o) => o.value);\n ok = values.includes(value);\n expected = values.join(\", \");\n break;\n }\n case \"toggle\": {\n ok = value === \"true\" || value === \"false\";\n expected = \"true, false\";\n break;\n }\n default: {\n const n = Number.parseFloat(value);\n ok = Number.isFinite(n) && n >= decl.min && n <= decl.max;\n expected = `${decl.min}–${decl.max}`;\n }\n }\n if (!ok) {\n problems.push({\n token: key,\n kind: \"shadowed\",\n detail:\n `\"${value}\" is not a legal ${manifest.namespace}.${key} (${expected}). ` +\n \"It resolves to the default instead.\",\n });\n }\n }\n return problems;\n}\n\n/** A stored appearance document. `sections` is opaque: the core never parses a section's payload. */\nexport interface LookDocument {\n id: string;\n label: string;\n version: number;\n /**\n * Keyed by namespace. **Unknown sections are legal and are preserved**, which is a product\n * requirement rather than politeness: a client signs their document before they buy the graph,\n * and the section a later package adds must survive every load and save in between.\n */\n sections: Readonly<Record<string, Readonly<Record<string, string>>>>;\n}\n\n/**\n * The fallback chain for a dotted token, most specific first.\n *\n * `--graph-point-size-min` → `--point-size-min`. One segment is dropped at a time from the left,\n * because the namespace is the qualifier: a section asks for its own value and inherits the shared\n * one when it has none of its own.\n */\nexport function fallbackChain(token: string): string[] {\n const bare = token.replace(/^--/, \"\");\n const parts = bare.split(\"-\");\n const out: string[] = [];\n for (let i = 0; i < parts.length; i++) out.push(`--${parts.slice(i).join(\"-\")}`);\n return out;\n}\n\n/**\n * Resolve a section token against a document, then against the manifest's default.\n *\n * `declared` is what the cascade or the document already provides, keyed by full token name. The\n * first member of the chain it answers wins; if none does, the manifest's default is returned —\n * and a default is returned as written, because it is already a CSS value: the browser is what\n * resolves it, on the element that uses it.\n */\nexport function resolveSectionToken(\n token: string,\n declared: Readonly<Record<string, string>>,\n manifest: SectionManifest,\n): { value: string; via: string } | null {\n for (const candidate of fallbackChain(token)) {\n const hit = declared[candidate];\n if (hit !== undefined) return { value: hit, via: candidate };\n }\n const bare = token.replace(new RegExp(`^--${manifest.namespace}-`), \"\");\n const decl = (manifest.tokens ?? {})[bare];\n return decl ? { value: decl.default, via: `${manifest.namespace}:default` } : null;\n}\n\nexport interface Problem {\n token: string;\n /** `unknown` — not declared by the manifest. `shadowed` — would silently fall back to a parent. */\n kind: \"unknown\" | \"shadowed\";\n detail: string;\n}\n\n/**\n * Check a section's stored payload against the manifest that owns it.\n *\n * This is the half Neovim does not have, and it is why a typo cannot degrade in silence: a name the\n * manifest never declared is reported rather than quietly resolving to whatever parent happens to\n * exist. Validation is **per section**, so a document carrying a section for an absent package is\n * simply not validated — it is not an error, and `sections.test.ts` pins that.\n */\nexport function validateSection(\n manifest: SectionManifest,\n stored: Readonly<Record<string, string>>,\n): Problem[] {\n const problems: Problem[] = [];\n for (const token of Object.keys(stored)) {\n const prefix = `--${manifest.namespace}-`;\n if (!token.startsWith(prefix)) {\n problems.push({\n token,\n kind: \"unknown\",\n detail: `not in the \"${manifest.namespace}\" namespace — a section may only declare its own`,\n });\n continue;\n }\n const bare = token.slice(prefix.length);\n if (!(bare in (manifest.tokens ?? {}))) {\n const parent = fallbackChain(token)[1];\n problems.push({\n token,\n kind: \"shadowed\",\n detail:\n `\"${manifest.namespace}\" declares no \"${bare}\". Unvalidated, this would fall back to ` +\n `${parent ?? \"nothing\"} and paint something plausible — which is the silent-typo failure ` +\n `this check exists to prevent.`,\n });\n }\n }\n return problems;\n}\n\n/** Read a section's payload. Absent, or a package you do not have, answers an empty record. */\nexport function sectionOf(doc: LookDocument, namespace: string): Readonly<Record<string, string>> {\n return doc.sections[namespace] ?? {};\n}\n\n/**\n * Write a section back, preserving every other section byte for byte.\n *\n * The preservation is the point: a host that never installed `@kanzo-tech/graph` still round-trips\n * a document containing a graph section, because the core treats the payload as opaque data rather\n * than as something to parse into a type it knows.\n */\nexport function withSection(\n doc: LookDocument,\n namespace: string,\n tokens: Readonly<Record<string, string>>,\n): LookDocument {\n return { ...doc, sections: { ...doc.sections, [namespace]: tokens } };\n}\n","import themeDataJson from \"../theme-data.json\" with { type: \"json\" };\n\n/**\n * What the density axis owes — the third section's obligations, in the graph section's shape.\n *\n * Density sets the root font-size the whole `rem` scale resolves against, so it is a **multiplier\n * on every size the component layer declares**. That is what makes it a section rather than a\n * preference: it has a bar it can break.\n *\n * ## Whose obligation is it\n *\n * The reading this was written from: the base height belongs to `@kanzo-tech/ui`, the multiplier to\n * density, and WCAG measures the *product* — so the obligation sits with whoever can break it.\n *\n * **Measured, that reading needs one correction, and the correction matters.** The eleven genuine\n * 2.5.8 failures found in the showcases fail at the **default** density too (20.0 px tall, centres\n * 22.0 px apart) and clear only at comfortable. Density did not move a passing target to failing;\n * it made an already-failing one worse. So density is not the *owner* of the bar — it is a\n * multiplier on a bar **both** layers can break, and the component that declared a 1.25rem row\n * broke it first. The obligation is stated here because this is where the multiplier lives and\n * where the axis can be graded; the fix for a specific failure may well belong in `ui`.\n */\nexport interface Obligation {\n id: string;\n subject: string;\n measured: number | null;\n threshold: number | null;\n holds: \">=\" | \"<=\" | \"<\";\n unit: string;\n against: string;\n reason: string;\n}\n\n/** Root font-size per density, read from the generated table rather than re-typed. */\nconst DENSITIES: Record<string, string> = themeDataJson.densities;\n\nconst px = (v: string) => Number.parseFloat(v);\n\n/** The smallest root font-size the axis offers. Derived, so a new density step is graded too. */\nconst minRoot = Math.min(...Object.values(DENSITIES).map(px));\n\n/**\n * The library's smallest interactive height, in `rem`, at the smallest density.\n *\n * `Button size=\"xs\"` is `h-6` — 1.5rem. Written here as the rem figure and multiplied by the axis,\n * because the axis is what this file can grade; the 1.5 is `@kanzo-tech/ui`'s to change and a test\n * there is what would catch it moving.\n */\nconst SMALLEST_CONTROL_REM = 1.5;\n\nexport const OBLIGATIONS: readonly Obligation[] = [\n {\n id: \"target-size\",\n subject: \"the smallest interactive control, at the smallest density\",\n measured: Number((SMALLEST_CONTROL_REM * minRoot).toFixed(2)),\n threshold: 24,\n holds: \">=\",\n unit: \"CSS px\",\n against: \"WCAG 2.5.8 Target Size (Minimum), AA\",\n reason:\n \"Every size in the library is `rem`, so the density axis multiplies every target. The \" +\n \"smallest control is `Button size=\\\"xs\\\"` at 1.5rem, which is exactly 24px at the default \" +\n \"root and 21px at compact. Measured over the app-shell, workspace and metadata-form \" +\n \"showcases at compact, **no `size=\\\"xs\\\"` button failed**: every undersized target was saved \" +\n \"by 2.5.8's spacing exception. The eleven genuine failures were a hand-rolled 1.25rem list \" +\n \"row, and they fail at the default density too — see the docblock above on ownership.\",\n },\n {\n id: \"x-height\",\n subject: \"the smallest type size the scale emits, at the smallest density\",\n measured: null,\n threshold: null,\n holds: \">=\",\n unit: \"CSS px\",\n against: \"not established — WCAG sets no minimum type size, and this repo has set none either\",\n reason:\n \"Real and not gradeable, and saying so beats inventing a bar. `--kanzo-font-size-xs` is \" +\n \"0.625rem, which is 10px at the default root and 8.75px at compact. What would close it: a \" +\n \"house floor with a reason. The honest anchor already exists in the source — the IDE tier \" +\n \"justifies `--kanzo-font-size-base` as 13px, 'below Tailwind text-xs', which means the scale \" +\n \"was designed against a floor nobody wrote down. Writing that floor down is the work.\",\n },\n];\n\nexport interface Check extends Obligation {\n measured: number;\n threshold: number;\n ok: boolean;\n}\n\n/**\n * Grade every obligation carrying a number.\n *\n * Ungradeable rows are skipped rather than defaulted to passing, for the same reason the graph's\n * are: a bar nobody established cannot be cleared, and reporting one green is how a guard comes to\n * test a corpus of zero.\n */\nexport function check(): Check[] {\n return OBLIGATIONS.filter(\n (o): o is Obligation & { measured: number; threshold: number } =>\n o.measured !== null && o.threshold !== null,\n ).map((o) => ({\n ...o,\n ok:\n o.holds === \">=\"\n ? o.measured >= o.threshold\n : o.holds === \"<=\"\n ? o.measured <= o.threshold\n : o.measured < o.threshold,\n }));\n}\n","// The ink a fill carries, and the maths under it.\n//\n// A theme is a flat block of hex and stays one — nothing here runs in a browser painting a page.\n// This is authoring-time: it answers \"you picked this fill, what text goes on it?\" once, in a form,\n// and the value it returns is written into the document as a literal. `a-theme-is-one-flat-block`\n// is the rule this serves, not the one it bends: the derivation that decision cut was a\n// multi-second categorical search shipped beside a stylesheet, and what replaced it was nobody\n// computing the boring half at all — fifty-four hand-typed declarations per theme.\n//\n// ## The rule is daisyUI's, recovered from its output\n//\n// daisyUI has no derivation either: zero `oklch(from …)`, zero `color-mix` in 5.7.20, and\n// `var(--color-primary-content)` is used bare 136 times with no fallback. But its 35 themes were\n// clearly generated, and the formula reads straight off them. Of 280 fill/ink pairs, 156 carry the\n// fill's hue to four decimal places, and within those:\n//\n// · fill light, ink dark → L × 0.2 (97/109 within ±0.5pp) C × 0.2 (101/109 within ±0.005)\n// · fill dark, ink light → L = 0.8 + 0.2·L (exact on 31/47)\n//\n// One sentence: **carry the hue, cut the chroma to a fifth, and pull the lightness 80% of the way\n// to the nearer end.** The 47 pairs that miss are pure-black fills where a person chose pure white,\n// and the 124 that do not share the hue at all were adjusted by hand afterwards.\n//\n// ## Where we do not follow it\n//\n// daisyUI picks the branch on a lightness threshold — measured, its ink goes dark from L≥58.9 and\n// light up to L≤56.9. Choosing instead by *which branch actually contrasts more* flips a little\n// earlier (53–55% depending on chroma, so the two rules disagree over a narrow band) and it is the\n// better question to ask, because the band they disagree over is exactly where the threshold picks\n// the unreadable one.\n//\n// So {@link inkFor} takes daisyUI's value on the branch that contrasts more, and treats 4.5:1 as a\n// floor rather than a hope: if that value does not clear AA it pushes the lightness on to the end,\n// then lets the chroma go, then tries the other branch. Measured over 9,178 in-gamut fills spanning\n// L, C and hue:\n//\n// | | below AA 4.5:1 | worst |\n// |---|---|---|\n// | daisyUI's own 280 shipped pairs | 40 (14.3%) | 3.04:1 |\n// | the formula alone | 1,041 (11.3%) | 3.16:1 |\n// | {@link inkFor} | **0** | **4.50:1** |\n//\n// The second departure is `LIGHT_PULL`, and it has its own note. Between the two, the distance from\n// the plain formula is worth stating twice because the two numbers say different things: over\n// daisyUI's own shipped pairs the median difference is ΔE 0.60 and 21 of 23 land within 8, so on\n// the colours daisyUI actually chose these are the same rule. Over the synthetic sweep — which is\n// mostly fills nobody would pick — 4,414 of 9,178 come back byte-identical and the median\n// difference is ΔE 4.62, a just-noticeable step, concentrated on the pale branch.\n//\n// ## What this cannot prove\n//\n// **That the pair looks good.** AA is a floor on legibility, not a judgement about a palette, and a\n// pair can clear 4.5:1 and still be ugly. The studio shows the number beside the swatch for that\n// reason: the rule proposes, the author disposes, and what ships is whatever hex is in the file.\n//\n// **Anything about the fill's own contrast** against the surface it sits on. That is a different\n// pair and `status.test.ts` is what measures it, over the themes that actually ship.\n\n/** A colour in OKLCh. `l` is 0–1 (not a percentage), `h` is degrees. */\nexport interface Oklch {\n l: number;\n c: number;\n h: number;\n}\n\nconst toLinear = (c: number) => (c <= 0.04045 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4);\nconst toGamma = (c: number) => (c <= 0.0031308 ? 12.92 * c : 1.055 * c ** (1 / 2.4) - 0.055);\nconst clamp = (v: number) => Math.min(255, Math.max(0, Math.round(v * 255)));\n\n/** A hex as OKLCh. */\nexport function oklch(hex: string): Oklch {\n const h = hex.trim().replace(/^#/, \"\");\n const [r = 0, g = 0, b = 0] = [0, 2, 4].map((i) =>\n toLinear(Number.parseInt(h.slice(i, i + 2), 16) / 255),\n );\n const lp = Math.cbrt(0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b);\n const mp = Math.cbrt(0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b);\n const sp = Math.cbrt(0.0883024619 * r + 0.2817188376 * g + 0.6299787005 * b);\n const A = 1.9779984951 * lp - 2.428592205 * mp + 0.4505937099 * sp;\n const B = 0.0259040371 * lp + 0.7827717662 * mp - 0.808675766 * sp;\n return {\n l: 0.2104542553 * lp + 0.793617785 * mp - 0.0040720468 * sp,\n c: Math.hypot(A, B),\n h: ((Math.atan2(B, A) * 180) / Math.PI + 360) % 360,\n };\n}\n\n/**\n * OKLCh back to a hex, clamped into sRGB.\n *\n * Clamping is a real loss and it is the right one here: a proposal that lands outside sRGB has to\n * become *some* hex to go in the file, and the author sees the swatch and the ratio for the colour\n * that will actually ship rather than for the one the maths wanted.\n */\nexport function hex({ l, c, h }: Oklch): string {\n const rad = (h * Math.PI) / 180;\n const A = c * Math.cos(rad);\n const B = c * Math.sin(rad);\n const lp = (l + 0.3963377774 * A + 0.2158037573 * B) ** 3;\n const mp = (l - 0.1055613458 * A - 0.0638541728 * B) ** 3;\n const sp = (l - 0.0894841775 * A - 1.291485548 * B) ** 3;\n return `#${[\n toGamma(4.0767416621 * lp - 3.3077115913 * mp + 0.2309699292 * sp),\n toGamma(-1.2684380046 * lp + 2.6097574011 * mp - 0.3413193965 * sp),\n toGamma(-0.0041960863 * lp - 0.7034186147 * mp + 1.707614701 * sp),\n ]\n .map((v) => clamp(v).toString(16).padStart(2, \"0\"))\n .join(\"\")}`;\n}\n\nconst luminance = (hexValue: string): number => {\n const h = hexValue.trim().replace(/^#/, \"\");\n const [r = 0, g = 0, b = 0] = [0, 2, 4].map((i) =>\n toLinear(Number.parseInt(h.slice(i, i + 2), 16) / 255),\n );\n return 0.2126 * r + 0.7152 * g + 0.0722 * b;\n};\n\n/** The WCAG 2.x ratio between two opaque hexes, 1–21. Order does not matter. */\nexport function contrast(a: string, b: string): number {\n const [hi, lo] = [luminance(a), luminance(b)].sort((x, y) => y - x) as [number, number];\n return (hi + 0.05) / (lo + 0.05);\n}\n\n/** The floor {@link inkFor} will not knowingly return below — WCAG AA for body text. */\nexport const AA = 4.5;\n\n/**\n * The hue kept, the chroma at a fifth, the lightness pulled toward one end.\n *\n * `k` is how far. daisyUI's own value is 0.8 both ways, and this leans on `LIGHT_PULL` for the pale\n * branch instead — see the constant.\n */\nconst pull = (fill: Oklch, dark: boolean, k: number, chroma = 0.2): Oklch => ({\n l: dark ? fill.l * (1 - k) : fill.l + (1 - fill.l) * k,\n c: fill.c * chroma,\n h: fill.h,\n});\n\n/** daisyUI's, unchanged: a dark ink sits at a fifth of its fill's lightness. */\nconst DARK_PULL = 0.8;\n\n/**\n * A pale ink goes further than daisyUI takes it — 0.9 of the way to white rather than 0.8.\n *\n * Measured, not preferred. Against daisyUI's own fixture the two are indistinguishable — 21 of 23\n * within ΔE 8 either way, median 0.60 — because daisyUI's dark fills are few and it hand-picked\n * most of their inks anyway. Against the 112 fill/ink pairs the sixteen hand-written themes ship, 0.8\n * reproduces 84% and 0.9 reproduces **all of them**, and every one of 0.8's misses is the same\n * shape: a dark fill where the house wrote a near-white ink and daisyUI's constant stops at a\n * tinted grey.\n *\n * What it costs is real and small: on a synthetic sweep the pale branch now differs from daisyUI's\n * by ΔE 4.62 at the median where before it was identical. That is a just-noticeable step, paid on\n * fills nobody picked, to be exactly right on 112 that somebody did.\n *\n * Going all the way to white fits this house perfectly too and daisyUI barely at all (13 of 23),\n * which is what a constant overfitted to one corpus looks like. It was not taken.\n */\nconst LIGHT_PULL = 0.9;\n\n/**\n * The *other* ink a status colour needs: the one that reads on the page, not on the fill.\n *\n * Shark's convention, and it catches people out because the two look like the same token wearing\n * different suffixes. `--destructive-content` is what sits **on** `--destructive` — white text in a\n * filled button. `--destructive-foreground` is destructive text on `--background` — an error line\n * under an input, where the fill never appears. One is a contrast pair with the fill, the other is\n * a member of the fill's family that survives being read at body weight on the page.\n *\n * So it is not {@link inkFor}: pulling to the far end would give near-black, which is not red any\n * more. It is the fill walked *toward the page's own ink* until it reads, which keeps the hue.\n *\n * Sixty percent, and the number is measured rather than chosen: across the sixteen hand-written\n * themes it reproduces all sixty-four authored values at ΔE 4.6 in the worst case and about 3 at\n * the median. The per-token optima were 60, 60, 59 and 62, which is one number with rounding on it\n * rather than four. The thirteen themes imported from daisyUI author fifty-two more and they are\n * not evidence either way: `import-daisy.mjs` calls this function to write them.\n *\n * `surface` is the page the ink is read on, and it is optional because the mix does not need it —\n * only the *floor* does. Given one, the result is walked further toward `ground` until it clears\n * AA there, which is the same discipline {@link inkFor} applies to its own pair. Without it the\n * function answers what daisyUI's generator would have answered, and the caller measures.\n */\nexport function pageInk(fill: string, ground: string, surface?: string): string {\n const [a, b] = [oklch(fill), oklch(ground)];\n const rad = (v: Oklch) => (v.h * Math.PI) / 180;\n const at = (p: number) => {\n const A = a.c * Math.cos(rad(a)) * p + b.c * Math.cos(rad(b)) * (1 - p);\n const B = a.c * Math.sin(rad(a)) * p + b.c * Math.sin(rad(b)) * (1 - p);\n return hex({\n l: a.l * p + b.l * (1 - p),\n c: Math.hypot(A, B),\n h: ((Math.atan2(B, A) * 180) / Math.PI + 360) % 360,\n });\n };\n const measured = at(0.6);\n if (surface === undefined || contrast(measured, surface) >= AA) return measured;\n // Sixty percent is where the authored values sit, not a floor. Give it the surface the ink is\n // read on and it becomes one: walk further toward `ground` until the pair clears AA. Every value\n // the sixteen hand-written themes author already does, so this changes nothing for them. It\n // answers for the imported ones, and `lemonade` — imported since, and the reason this exists — is\n // the whole of it: all four of its page inks land at 3.85–3.93 and are walked back until they\n // clear.\n for (let p = 0.58; p >= 0; p -= 0.02) {\n const candidate = at(p);\n if (contrast(candidate, surface) >= AA) return candidate;\n }\n return at(0);\n}\n\n/** Walk one branch out to its end, giving up lightness first and chroma only when that runs out. */\nfunction reach(fill: string, colour: Oklch, dark: boolean): string {\n const start = dark ? DARK_PULL : LIGHT_PULL;\n const base = hex(pull(colour, dark, start));\n if (contrast(fill, base) >= AA) return base;\n for (let k = start; k <= 1.0001; k += 0.01) {\n const candidate = hex(pull(colour, dark, k));\n if (contrast(fill, candidate) >= AA) return candidate;\n }\n for (let chroma = 0.2; chroma >= -0.0001; chroma -= 0.02) {\n const candidate = hex(pull(colour, dark, 1, chroma));\n if (contrast(fill, candidate) >= AA) return candidate;\n }\n return hex(pull(colour, dark, 1, 0));\n}\n\n/**\n * The ink that belongs on `fill` — a hex, ready to be written into a theme.\n *\n * Returns daisyUI's own value wherever that clears AA, and the nearest thing to it that does\n * wherever it would not. See the module docblock for the measurements behind both halves.\n */\nexport function inkFor(fill: string): string {\n const colour = oklch(fill);\n const dark =\n contrast(fill, hex(pull(colour, true, DARK_PULL))) >=\n contrast(fill, hex(pull(colour, false, LIGHT_PULL)));\n const first = reach(fill, colour, dark);\n if (contrast(fill, first) >= AA) return first;\n // The preferred branch can cap out below AA where the other one, pushed, does not.\n const second = reach(fill, colour, !dark);\n return contrast(fill, second) > contrast(fill, first) ? second : first;\n}\n","import themeDataJson from \"../theme-data.json\";\nimport type { SectionPrefDecl } from \"./sections.js\";\n\n/**\n * The generated theme tables — the four non-colour axes — as a JS module.\n *\n * Consumers must read them through this export rather than importing\n * `@kanzo-tech/theme/theme-data.json` directly. A raw JSON subpath import is an ESM JSON\n * import at runtime, which Node rejects without `with { type: \"json\" }` — and Rollup strips\n * that attribute when bundling, so there is no way to make the direct import survive a build.\n * Bundling the data into this package's own JS entry is safe: it is the package that owns the\n * data, so the copy can never skew from the CSS generated alongside it.\n *\n * There is no derivation any more, so there are no tables for one to read: a theme is\n * `themes/<name>.css`, hand-written source, and `themes` below is the catalogue read off disk by\n * the generator so no second list can drift from it.\n */\nexport const themeData = themeDataJson;\nexport type ThemeData = typeof themeDataJson;\n\n/**\n * The themes this package ships, as data a picker can render.\n *\n * daisyUI keeps two registries — `themeOrder` (the ordered names) and `theme/object` (name → the\n * variable map) — precisely so a switcher can draw a theme without parsing its CSS. This is the\n * first: an entry carries what a control needs to *offer* a theme and nothing a page needs to\n * *paint* one, which is the theme's own stylesheet's job.\n *\n * **It replaces `paletteIndex`, and it is a flat list where that was a tree.** A palette used to\n * contain identities, so an entry had `children` and a picker had two levels. A brand is a theme\n * now, so `bank` and `bank-private` sit side by side and the second level is gone.\n *\n * Generated from the directory, not listed: `scripts/gen-theme.mjs` reads `themes/` and records\n * each file's own `color-scheme`. Adding a theme is adding a file.\n *\n * Read through this export rather than importing `@kanzo-tech/theme/theme-data.json`, for the\n * reason given on {@link themeData}: a raw JSON subpath import is an ESM JSON import at runtime, and\n * Rollup strips the attribute that would make it legal.\n */\nexport const themeIndex = themeDataJson.themes as ThemeIndexEntry[];\n\n/**\n * One theme, as a control sees it.\n *\n * **It carries no colours.** A theme travels in the page under its own `[data-theme]`, so a control\n * depicts one by *setting the attribute* and letting the cascade answer — which is also why a\n * preview is a `div` and not a strip of swatches. A handful of hexes could not depict a theme\n * anyway; on the default's own, two of the four a picker used to publish were the same value.\n *\n * `dark` is the theme's own `color-scheme`, and it is the whole of what \"a theme is one mode\"\n * means at this layer: it is a property of the theme, not a second axis crossed with it.\n */\nexport interface ThemeIndexEntry {\n name: string;\n dark: boolean;\n}\n\n/**\n * How many `--chart-N` custom properties the stylesheet declares. A 9th series folds into \"Other\" —\n * never cycle, or identity stops meaning anything. (`--chart-capacity` is declared beside them and\n * is not one of them; `boundary.test.ts` counts `--chart-N` only.)\n *\n * A fact about the SHEET, which is why it is here and not with the derivation that emits it: a\n * chart resolving `var(--chart-N)` off the cascade needs the count, and a chart runs in a browser\n * and is checked against the shipped theme files rather than trusted. The count is a compile-time constant\n * because a stylesheet cannot have a variable number of custom properties.\n *\n * How many of the slots carry a *real* category is the document's `capacity`, which can be lower;\n * it travels down the cascade as `--chart-capacity`, and past it `compile()` writes\n * `var(--muted-foreground)`.\n *\n * `packages/palette` declares the same number, because it is what emits the properties. Neither\n * copy is trusted: `boundary.test.ts` counts the declarations in the shipped `tokens.css` and\n * holds both against it.\n */\nexport const CHART_SLOTS = 8;\n\n/**\n * `@kanzo-tech/theme` — design tokens, the theme axis table, and the value types. No React.\n *\n * **Colour IS an axis now, and it is the same kind of axis as the rest.** A theme is one flat block\n * of CSS under `[data-theme=\"<name>\"]` — about fifty-five declarations somebody writes, pastes and\n * diffs — so applying one is writing an attribute, exactly like radius or density. It stopped being\n * special when it stopped being the output of a thirteen-stage derivation.\n *\n * Everything is driven by `data-*` attributes on `<html>`, and the values live in `themes.css`:\n * · `data-theme` — selects a whole theme: its colours, its shape knobs and its fonts.\n * · `data-radius` — the three radius knobs, as a user preference over the theme's own.\n * · `data-font` — sets `--font-sans` / `--font-heading`.\n * · `data-mono-font` — sets `--font-mono`.\n * · `data-font-size` — sets the root font-size (the rem density scale).\n *\n * **`data-theme` is `data-palette` and `data-identity` collapsed, and the authority question they\n * modelled has dissolved rather than been decided.** `data-palette` selected from a catalogue the\n * LIBRARY shipped, so a user picking Dracula could overrule a client's branding; `data-identity`\n * selected among brands the CLIENT authored, one level down. A tenant's brands are now themes\n * beside every other theme, so what a user may choose is what the tenant's policy admits — the\n * `pinned` / `hidden` chain that already governs every other section, rather than two attributes\n * with different pedigrees.\n *\n * Writing the attributes is `<KanzoThemeProvider>`'s job, from `@kanzo-tech/ui`. It sets them on\n * `document.documentElement` — see the AXES note below for why a wrapper element cannot work.\n *\n * Dark mode is not a token flip any more: a theme carries its own `color-scheme` and its own\n * colours, so `.dark` survives only as the selector for the `dark:` VARIANT at the call sites that\n * still ask for one.\n *\n * Requires `@kanzo-tech/ui/tailwind.css` (or the raw token/theme CSS) in the host's Tailwind build.\n */\n\n/**\n * A side of the compiled document. `compile()` always emits both blocks, so there are exactly two.\n *\n * **There is no `\"system\"`, and its absence is the design.** Following the OS is a real behaviour we\n * keep — without it the first visit has to guess, and guessing wrong flashes white at every\n * dark-mode user — but it is the state with *no* value, not a third value.\n *\n * That split is the reference systems', and they divide on which layer they are. The JS\n * theme-switching libraries make it a value: next-themes ships `defaultTheme = \"system\"` and appends\n * `\"system\"` to its `themes` array, MUI has `mode: \"light\" | \"dark\" | \"system\"`, Mantine `\"auto\"`.\n * The *token* layers do not: daisyUI writes `themes: light --default, dark --prefersdark`, where the\n * OS preference is a flag on a theme and `data-theme` overrides it; Tailwind has a media query or a\n * class; Radix Themes declines to model it and delegates to next-themes. And CSS itself has no third\n * keyword — `color-scheme: light dark` means \"the OS decides\", and an explicit side overrides.\n *\n * We are a token layer: a document with a `:root` block and a `.dark` block. `\"system\"` arrived here\n * as next-themes vocabulary for a mechanism we do not use, and `themeScript` never believed in it —\n * it has always resolved \"anything that is not an explicit side\" against `matchMedia`.\n *\n * A host next-themes IS still supported; `KanzoThemeProvider` translates its `\"system\"` to `null` in\n * one place, the way every other foreign vocabulary enters this system.\n */\nexport type Appearance = \"light\" | \"dark\";\n\n/**\n * The appearance PREFERENCE — an explicit side, or `\"\"` for \"ask the OS\".\n *\n * A value and not an absent key: the read-time whitelist is built from `Object.keys(DEFAULT_PREFS)`,\n * so a key missing from the default blob is dropped on every read. It also survives\n * `JSON.stringify` into both storage adapters, which an `undefined` would not.\n *\n * **`\"\"` and not `null`, which is what it was.** Unset is the same value here as everywhere else in\n * this package: a theme key stores `\"\"` for \"defer to the tenant\", and the write rule\n * removes an attribute at the default. Two spellings of one idea is what kept appearance out of the\n * declaration — a `SectionPrefDecl`'s values are strings — and therefore out of the one resolution\n * chain, which is the whole of what {@link CORE_PREFS} exists to end. Declared, \"follow the OS\" is\n * `{ value: \"\", label: \"System\" }`: a thing a control can offer, rather than something reachable\n * only through the panel's Reset button.\n */\nexport type AppearancePref = Appearance | \"\";\n\n/** Radius steps (`md` = 0.5rem default). */\nexport type KanzoRadius = \"none\" | \"xs\" | \"sm\" | \"md\" | \"lg\";\n\n/** Density (root font-size rem-scale); `default` omits the attribute. */\nexport type KanzoDensity = \"default\" | \"compact\" | \"comfortable\";\n\n/** Sans font key — host-extensible; the DS ships `system`/`geist`/`inter` stacks. */\nexport type KanzoFont = \"system\" | \"geist\" | \"inter\" | (string & {});\n\n/** Mono font key — host-extensible; the DS ships `system`/`geist-mono`/`jetbrains-mono`. */\nexport type KanzoMonoFont = \"system\" | \"geist-mono\" | \"jetbrains-mono\" | (string & {});\n\n/**\n * Theme key — host-extensible; `\"\"` means \"defer to the tenant's default\".\n *\n * This is `KanzoFont`'s case, not `KanzoRadius`': a value is a *host's* string, unknown when this\n * package is built. Where `KanzoFont` still names the three stacks the DS happens to ship, there is\n * nothing to union here — a tenant authors their own themes, so a literal union would be a list\n * that is wrong for every client.\n *\n * **It replaces `KanzoPalette`, `KanzoIdentity` and `KanzoIdentityMemory`, and the collapse is the\n * point.** Those were three types because a palette CONTAINED identities: a document was a two-mode\n * stylesheet, a brand was a partial block layered onto it, and a memory recorded which brand you\n * last wore inside each document so switching away and back returned you to it. A theme is one flat\n * block, so a brand is not inside anything — `bank` and `bank-private` are two themes — and there is\n * no containment left for a memory to remember.\n */\nexport type KanzoThemeName = string;\n\n/**\n * One published theme, as the runtime sees it — the contract between the catalogue and the panel.\n *\n * It carries no colours, and it has no `children`. A control depicts a theme by setting\n * `data-theme` on an element and letting the cascade paint it: the theme is already in the page, so\n * a depiction copied out of it is a second spelling that can only ever be the same colours or the\n * wrong ones.\n *\n * **`children` went with the containment it modelled.** A palette used to hold brands, so an option\n * held options and the panel flattened a tree into one list. There is no tree: a brand is a theme.\n */\nexport interface ThemeOption {\n value: string;\n label: string;\n}\n\n// ── The axis table — the single source of truth for how a preference reaches the DOM ────────\n//\n// This lives here, not in @kanzo-tech/ui, because three separate things must agree on it and they\n// are split across two packages: the React provider and the SSR pre-hydration script, both in\n// `@kanzo-tech/ui`, and `scripts/gen-theme.mjs` here, which decides which selectors exist in\n// themes.css at all. Only this package holds all three in view. When they drifted there was no type\n// error to catch it — miss the generator and the provider writes an attribute no CSS matches; miss\n// the script and the FOUC it exists to prevent comes back.\n\n/**\n * The user's preferences. Six, and only one of them is a colour.\n *\n * *Free* colour left this table entirely: `palette`, `base`, `accent`, `primary`, `baseTint`,\n * `scheme` and `schemeColors` were seven ways to express *part* of a palette at runtime, and a\n * document expresses all of it at once, before a byte is sent. `appearance` stays because it is\n * the one colour-adjacent thing a user genuinely chooses, and it selects between two blocks of one\n * document. `identity` is the same kind of choice one level up: between blocks the TENANT\n * published, and never between a value they did not.\n */\nexport interface ThemePrefs {\n appearance: AppearancePref;\n radius: KanzoRadius;\n font: KanzoFont;\n monoFont: KanzoMonoFont;\n density: KanzoDensity;\n /**\n * Which theme this user wears on each side — `{}` while they have chosen neither.\n *\n * A map rather than a string because the choice is per appearance, and with one-mode themes that\n * is not a refinement but the only shape available: a theme IS a side, so \"which theme\" without\n * \"on which side\" does not name a preference. It is also exactly what the per-appearance palette\n * proposal asked for, arrived at from the other\n * direction — that proposal invented a map over documents that each carried both modes, to say\n * something the two-mode document made awkward and the one-mode theme makes trivial.\n *\n * The ordering that makes it possible: the pre-hydration script resolves appearance before it\n * writes anything, so it can index this map. That is the thing to check first when touching it.\n */\n themeByAppearance: Partial<Record<Appearance, KanzoThemeName>>;\n /**\n * What the packages a host installed contribute, keyed by namespace then by preference.\n *\n * **One key, and that is what makes an absent package harmless.** The read-time whitelist is built\n * from `Object.keys(DEFAULT_PREFS)` and drops everything else, which is right for the retired\n * colour axes it was built for and exactly wrong for a contributed choice: a host that drops an\n * optional peer for one release would lose the user's stored value on the next write. Riding on a\n * single known key, an unrecognised namespace survives every read and save without the core\n * knowing it exists — the same opacity {@link LookDocument}'s `sections` already has, which is the\n * point: both halves of a section are stored the same way.\n */\n sections: Record<string, Record<string, string>>;\n}\n\n/**\n * Every key of `ThemePrefs`, with no exception for the ones whose default is empty.\n *\n * `PREF_KEYS` in the provider is `Object.keys(DEFAULT_PREFS)`, and the read-time whitelist built\n * from it drops anything not listed. A pref missing here therefore works for exactly one session\n * and is gone on the next read, silently and with no type error — the retired-key hygiene rule\n * turned on a live field.\n */\nexport const DEFAULT_PREFS: ThemePrefs = {\n // `\"\"`, not `\"system\"` and no longer `null`: the default is to have no side pinned, spelled the\n // way every other deferral in this table is — a theme key of `\"\"` defers to the tenant, this\n // defers to the OS. One spelling is what lets it be declared, and therefore resolved, like the rest.\n appearance: \"\",\n radius: \"md\",\n font: \"system\",\n monoFont: \"system\",\n density: \"default\",\n // Empty, and both sides fall through to the tenant's default — so a tenant shipping one theme per\n // side stores nothing and gets the `<html>` it always had. `\"\"` per side means the same thing:\n // defer to whichever theme the tenant made default.\n themeByAppearance: {},\n // Empty, and never seeded from the registered manifests: a section's default is what `resolvePref`\n // answers when nothing is stored, so writing it in here would turn every default into a *stored\n // choice* the first time the panel opened — and a default that has been stored can no longer move\n // when the section, or the tenant's policy, changes it.\n sections: {},\n};\n\nexport const STORAGE_KEY = \"kanzo_theme_prefs\";\n\n/**\n * The core's own preferences, declared — one entry per axis, in the shape a package contributes.\n *\n * **Generated, and that is the point.** `scripts/gen-theme.mjs` authors the values *and* the\n * declaration, so the option list a control offers is the table the CSS was emitted from rather than\n * a hand-copy beside it. `Preferences.tsx` held two such copies — `RADII` and `DENSITIES` — sitting\n * next to the generated tables they duplicated, and `KanzoThemeProvider` held a third of the font\n * stacks with a fallback string that had already drifted from the sheet's. Adding a font is now one\n * line in the generator: the panel grows a card and the docs table grows a row.\n *\n * The rule this installs, and it is the same one the colour half follows: **a configuration is\n * authored once, where its values live.** The declaration, the control, the default and the\n * attribute are resolved from it.\n *\n * `source` is the one field a contributed preference has no use for. It says WHO emits the selectors\n * the attribute matches: `\"themes\"` is our generator, so the drift guards in `index.test.ts` can\n * hold the declaration against the sheet; `\"document\"` is `compile()`, from something a TENANT\n * authored after this package was built, and asserting a generated table for it would fail for the\n * right feature.\n */\nexport type CorePrefDecl = SectionPrefDecl & { source?: \"themes\" | \"document\" };\n\n/**\n * Cast, because JSON is data and TypeScript reads it as widened literals — `kind: string` will not\n * narrow to the union however it is written. The check is therefore a runtime one, in\n * `index.test.ts`: every key is a key of `DEFAULT_PREFS`, every declaration is well-formed, and\n * `check:generated` regenerates the file and fails on a diff.\n */\nexport const CORE_PREFS = themeData.prefs as unknown as Readonly<Record<CorePrefKey, CorePrefDecl>>;\n\n/**\n * Every preference the core declares — which is every key of {@link ThemePrefs} except the two that\n * are not choices at all.\n *\n * Written as an exclusion rather than a list, so the two exceptions have to justify themselves:\n * `identityByPalette` is a *memory* (what this user last wore in each document, consulted only when\n * the palette changes), and `sections` is the opaque bag another package's preferences ride in. A\n * new axis appears here by appearing in `ThemePrefs`, and the generator has to answer for it.\n */\nexport type CorePrefKey = Exclude<keyof ThemePrefs, \"sections\">;\n\n/**\n * The namespace the core's own preferences answer to in a tenant's policy.\n *\n * **The core is a section like any other, and this is the whole of what that costs.** A policy is\n * keyed by namespace — `{ theme: { radius: { pinned: \"sm\" } }, graph: { look: { hidden: true } } }`\n * — so a client shipping *compact and square* uses the mechanism an optional package already uses,\n * and one chain answers for colour, geometry and a contributed choice alike.\n *\n * That is daisyUI's insight, in the mechanism this repo already had: their theme carries the\n * geometry (`--radius-box`, `--size-field`, `--relief`) in the same document as the colours, so a\n * tenant ships a coherent whole rather than a panel of unrelated knobs. Ours went half-way there\n * when a palette became a document; the half not taken was that radius, density and the fonts had no\n * document-level default at all — only a user could move them.\n */\nexport const CORE_NAMESPACE = \"theme\";\n\n/**\n * Each axis → its `<html>` attribute + default value (at the default the attribute is removed).\n *\n * A projection of {@link CORE_PREFS} and no longer a table of its own: three things must agree about\n * an axis — the React provider, the SSR pre-hydration script, and the generator that decides which\n * selectors exist at all — and they now agree because there is one place to disagree with.\n *\n * These attributes go on `<html>`, never a wrapper element. Ark overlays (Dialog, Popover,\n * Menu, Select, Tooltip, Toast…) portal to `document.body`, outside any wrapper, so tokens set\n * on a wrapper would not reach them. `density` additionally *must* be on the root: it sets the\n * root font-size and every size in the system is `rem`.\n */\nexport const AXES: {\n key: keyof ThemePrefs;\n attr: string;\n def: string;\n source: \"themes\" | \"document\";\n /** See {@link SectionPrefDecl}. The stored value is a map keyed by the resolved appearance. */\n byAppearance?: true;\n}[] = Object.entries(CORE_PREFS)\n // The rows with somewhere to write. `appearance` is declared beside these and is not one: it\n // writes a class. A contributed preference makes the same distinction with the same field.\n .filter(([, decl]) => Boolean(decl.attr))\n .map(([key, decl]) => ({\n key: key as keyof ThemePrefs,\n attr: decl.attr as string,\n def: decl.default,\n source: decl.source ?? \"themes\",\n byAppearance: decl.byAppearance,\n }));\n\n// ── Sections ────────────────────────────────────────────────────────────────────────────────────\n//\n// The mechanism only. The core exports the document shape, the fallback resolver and the validator,\n// and knows no section: a package contributes by USING a namespace, never by registering here.\n// That is what keeps `@kanzo-tech/theme` free of any reference to `@kanzo-tech/graph`.\nexport {\n fallbackChain,\n resolvePref,\n prefBoolean,\n prefNumber,\n prefOptions,\n resolveSectionToken,\n sectionOf,\n validatePrefs,\n validateSection,\n withSection,\n type LookDocument,\n type PrefOption,\n type PrefOptions,\n type PrefOrigin,\n type PrefSource,\n type PrefSources,\n type Problem,\n type ResolvedPref,\n type SectionManifest,\n type SectionPolicy,\n type SectionPrefDecl,\n type SectionPrefPolicy,\n type SectionTokenDecl,\n} from \"./sections.js\";\n\n// The density axis's own obligations — what admits it as a section rather than a preference.\nexport {\n check as checkDensity,\n OBLIGATIONS as DENSITY_OBLIGATIONS,\n type Check as DensityCheck,\n type Obligation as DensityObligation,\n} from \"./obligations.js\";\n\n// Authoring-time colour, and the only derivation in the package. It runs in a form while somebody\n// picks a fill, never in a page painting one: what ships is still a flat block of hex, and this is\n// how the boring half of it stops being typed by hand. See `ink.ts` for the measurements.\nexport {\n AA,\n contrast,\n hex,\n inkFor,\n oklch,\n pageInk,\n type Oklch,\n} from \"./ink.js\";\n"],"names":["prefOptions","decl","sources","list","prefBoolean","value","prefNumber","n","resolvePref","stored","policy","legal","v","options","o","offered","validatePrefs","manifest","problems","prefs","key","ok","expected","values","fallbackChain","token","parts","out","i","resolveSectionToken","declared","candidate","hit","bare","validateSection","prefix","parent","sectionOf","doc","namespace","withSection","tokens","DENSITIES","themeDataJson","px","minRoot","SMALLEST_CONTROL_REM","OBLIGATIONS","check","toLinear","c","toGamma","clamp","oklch","hex","h","r","g","b","lp","mp","sp","A","B","l","rad","luminance","hexValue","contrast","a","hi","lo","x","y","AA","pull","fill","dark","k","chroma","DARK_PULL","LIGHT_PULL","pageInk","ground","surface","at","p","measured","reach","colour","start","base","inkFor","first","second","themeData","themeIndex","CHART_SLOTS","DEFAULT_PREFS","STORAGE_KEY","CORE_PREFS","CORE_NAMESPACE","AXES"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kanzo-tech/theme",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.1",
|
|
4
4
|
"description": "Kanzo design tokens — a catalogue of themes, each one flat block of CSS, plus the non-colour axes (radius / font / mono font / density) a user layers over them, driven by data-* attributes on <html>.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
package/themes/acid.css
CHANGED
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
--size-field: 0.25rem;
|
|
45
45
|
--size-selector: 0.25rem;
|
|
46
46
|
--stroke: 1px;
|
|
47
|
-
--
|
|
47
|
+
--relief: 1;
|
|
48
48
|
|
|
49
49
|
/* daisyUI has no categorical channel, so this theme authors no `--chart-*` and takes the eight
|
|
50
50
|
`tokens.css` publishes as its fallback. It does NOT declare `--chart-capacity: 0`: zero means
|
package/themes/bank-dark.css
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
/* bank-dark — a theme is one mode, one flat block
|
|
2
|
-
from the compiled document that shipped before the derivation was cut, so the colours are
|
|
3
|
-
unchanged. Edit this file by hand from here on: it is source now, not output. */
|
|
1
|
+
/* bank-dark — a theme is one mode, one flat block, written by hand. */
|
|
4
2
|
[data-theme="bank-dark"] {
|
|
5
3
|
color-scheme: dark;
|
|
6
4
|
|
|
@@ -25,7 +23,7 @@
|
|
|
25
23
|
--border: #31353b;
|
|
26
24
|
--ring: #3065b9;
|
|
27
25
|
|
|
28
|
-
/*
|
|
26
|
+
/* Optional: each falls back to a bridge default in tokens.css when left out */
|
|
29
27
|
--popover: #121417;
|
|
30
28
|
--input: #6b7280;
|
|
31
29
|
--field: #090a0c;
|
|
@@ -42,7 +40,7 @@
|
|
|
42
40
|
--size-field: 0.25rem;
|
|
43
41
|
--size-selector: 0.25rem;
|
|
44
42
|
--stroke: 1px;
|
|
45
|
-
--
|
|
43
|
+
--relief: 0;
|
|
46
44
|
|
|
47
45
|
/* Optional: categorical set and syntax */
|
|
48
46
|
--chart-capacity: 8;
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
/* bank-private-dark — a theme is one mode, one flat block
|
|
2
|
-
from the compiled document that shipped before the derivation was cut, so the colours are
|
|
3
|
-
unchanged. Edit this file by hand from here on: it is source now, not output. */
|
|
1
|
+
/* bank-private-dark — a theme is one mode, one flat block, written by hand. */
|
|
4
2
|
[data-theme="bank-private-dark"] {
|
|
5
3
|
color-scheme: dark;
|
|
6
4
|
|
|
@@ -25,7 +23,7 @@
|
|
|
25
23
|
--border: #31353b;
|
|
26
24
|
--ring: #a16207;
|
|
27
25
|
|
|
28
|
-
/*
|
|
26
|
+
/* Optional: each falls back to a bridge default in tokens.css when left out */
|
|
29
27
|
--popover: #121417;
|
|
30
28
|
--input: #6b7280;
|
|
31
29
|
--field: #090a0c;
|
|
@@ -42,7 +40,7 @@
|
|
|
42
40
|
--size-field: 0.25rem;
|
|
43
41
|
--size-selector: 0.25rem;
|
|
44
42
|
--stroke: 1px;
|
|
45
|
-
--
|
|
43
|
+
--relief: 0;
|
|
46
44
|
|
|
47
45
|
/* Optional: categorical set and syntax */
|
|
48
46
|
--chart-capacity: 7;
|
package/themes/bank-private.css
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
/* bank-private — a theme is one mode, one flat block
|
|
2
|
-
from the compiled document that shipped before the derivation was cut, so the colours are
|
|
3
|
-
unchanged. Edit this file by hand from here on: it is source now, not output. */
|
|
1
|
+
/* bank-private — a theme is one mode, one flat block, written by hand. */
|
|
4
2
|
[data-theme="bank-private"] {
|
|
5
3
|
color-scheme: light;
|
|
6
4
|
|
|
@@ -25,7 +23,7 @@
|
|
|
25
23
|
--border: #c4c8d0;
|
|
26
24
|
--ring: #a16207;
|
|
27
25
|
|
|
28
|
-
/*
|
|
26
|
+
/* Optional: each falls back to a bridge default in tokens.css when left out */
|
|
29
27
|
--input: #6b7280;
|
|
30
28
|
--field: #00000008;
|
|
31
29
|
--faint: #636977;
|
|
@@ -41,7 +39,7 @@
|
|
|
41
39
|
--size-field: 0.25rem;
|
|
42
40
|
--size-selector: 0.25rem;
|
|
43
41
|
--stroke: 1px;
|
|
44
|
-
--
|
|
42
|
+
--relief: 0;
|
|
45
43
|
|
|
46
44
|
/* Optional: categorical set and syntax */
|
|
47
45
|
--chart-capacity: 7;
|
package/themes/bank.css
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
/* bank — a theme is one mode, one flat block
|
|
2
|
-
from the compiled document that shipped before the derivation was cut, so the colours are
|
|
3
|
-
unchanged. Edit this file by hand from here on: it is source now, not output. */
|
|
1
|
+
/* bank — a theme is one mode, one flat block, written by hand. */
|
|
4
2
|
[data-theme="bank"] {
|
|
5
3
|
color-scheme: light;
|
|
6
4
|
|
|
@@ -25,7 +23,7 @@
|
|
|
25
23
|
--border: #c4c8d0;
|
|
26
24
|
--ring: #2b7fff;
|
|
27
25
|
|
|
28
|
-
/*
|
|
26
|
+
/* Optional: each falls back to a bridge default in tokens.css when left out */
|
|
29
27
|
--input: #6b7280;
|
|
30
28
|
--field: #00000008;
|
|
31
29
|
--faint: #636977;
|
|
@@ -41,7 +39,7 @@
|
|
|
41
39
|
--size-field: 0.25rem;
|
|
42
40
|
--size-selector: 0.25rem;
|
|
43
41
|
--stroke: 1px;
|
|
44
|
-
--
|
|
42
|
+
--relief: 0;
|
|
45
43
|
|
|
46
44
|
/* Optional: categorical set and syntax */
|
|
47
45
|
--chart-capacity: 8;
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
/* catppuccin-latte-dark — a theme is one mode, one flat block
|
|
2
|
-
from the compiled document that shipped before the derivation was cut, so the colours are
|
|
3
|
-
unchanged. Edit this file by hand from here on: it is source now, not output. */
|
|
1
|
+
/* catppuccin-latte-dark — a theme is one mode, one flat block, written by hand. */
|
|
4
2
|
[data-theme="catppuccin-latte-dark"] {
|
|
5
3
|
color-scheme: dark;
|
|
6
4
|
|
|
@@ -25,7 +23,7 @@
|
|
|
25
23
|
--border: #404249;
|
|
26
24
|
--ring: #8839ef;
|
|
27
25
|
|
|
28
|
-
/*
|
|
26
|
+
/* Optional: each falls back to a bridge default in tokens.css when left out */
|
|
29
27
|
--popover: #16171a;
|
|
30
28
|
--input: #70737d;
|
|
31
29
|
--field: #0a0a0c;
|
|
@@ -42,7 +40,7 @@
|
|
|
42
40
|
--size-field: 0.25rem;
|
|
43
41
|
--size-selector: 0.25rem;
|
|
44
42
|
--stroke: 1px;
|
|
45
|
-
--
|
|
43
|
+
--relief: 0;
|
|
46
44
|
|
|
47
45
|
/* Optional: categorical set and syntax */
|
|
48
46
|
--chart-capacity: 8;
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
/* catppuccin-latte — a theme is one mode, one flat block
|
|
2
|
-
from the compiled document that shipped before the derivation was cut, so the colours are
|
|
3
|
-
unchanged. Edit this file by hand from here on: it is source now, not output. */
|
|
1
|
+
/* catppuccin-latte — a theme is one mode, one flat block, written by hand. */
|
|
4
2
|
[data-theme="catppuccin-latte"] {
|
|
5
3
|
color-scheme: light;
|
|
6
4
|
|
|
@@ -25,7 +23,7 @@
|
|
|
25
23
|
--border: #d2d4dc;
|
|
26
24
|
--ring: #a578fa;
|
|
27
25
|
|
|
28
|
-
/*
|
|
26
|
+
/* Optional: each falls back to a bridge default in tokens.css when left out */
|
|
29
27
|
--input: #8d91a1;
|
|
30
28
|
--field: #00000005;
|
|
31
29
|
--faint: #505360;
|
|
@@ -41,7 +39,7 @@
|
|
|
41
39
|
--size-field: 0.25rem;
|
|
42
40
|
--size-selector: 0.25rem;
|
|
43
41
|
--stroke: 1px;
|
|
44
|
-
--
|
|
42
|
+
--relief: 0;
|
|
45
43
|
|
|
46
44
|
/* Optional: categorical set and syntax */
|
|
47
45
|
--chart-capacity: 8;
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
/* catppuccin-mocha-dark — a theme is one mode, one flat block
|
|
2
|
-
from the compiled document that shipped before the derivation was cut, so the colours are
|
|
3
|
-
unchanged. Edit this file by hand from here on: it is source now, not output. */
|
|
1
|
+
/* catppuccin-mocha-dark — a theme is one mode, one flat block, written by hand. */
|
|
4
2
|
[data-theme="catppuccin-mocha-dark"] {
|
|
5
3
|
color-scheme: dark;
|
|
6
4
|
|
|
@@ -25,7 +23,7 @@
|
|
|
25
23
|
--border: #32343e;
|
|
26
24
|
--ring: #7e679a;
|
|
27
25
|
|
|
28
|
-
/*
|
|
26
|
+
/* Optional: each falls back to a bridge default in tokens.css when left out */
|
|
29
27
|
--popover: #131419;
|
|
30
28
|
--input: #6c7086;
|
|
31
29
|
--field: #0a0a0c;
|
|
@@ -42,7 +40,7 @@
|
|
|
42
40
|
--size-field: 0.25rem;
|
|
43
41
|
--size-selector: 0.25rem;
|
|
44
42
|
--stroke: 1px;
|
|
45
|
-
--
|
|
43
|
+
--relief: 0;
|
|
46
44
|
|
|
47
45
|
/* Optional: categorical set and syntax */
|
|
48
46
|
--chart-capacity: 8;
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
/* catppuccin-mocha — a theme is one mode, one flat block
|
|
2
|
-
from the compiled document that shipped before the derivation was cut, so the colours are
|
|
3
|
-
unchanged. Edit this file by hand from here on: it is source now, not output. */
|
|
1
|
+
/* catppuccin-mocha — a theme is one mode, one flat block, written by hand. */
|
|
4
2
|
[data-theme="catppuccin-mocha"] {
|
|
5
3
|
color-scheme: light;
|
|
6
4
|
|
|
@@ -25,7 +23,7 @@
|
|
|
25
23
|
--border: #c5c7d3;
|
|
26
24
|
--ring: #a681d0;
|
|
27
25
|
|
|
28
|
-
/*
|
|
26
|
+
/* Optional: each falls back to a bridge default in tokens.css when left out */
|
|
29
27
|
--input: #6c7086;
|
|
30
28
|
--field: #00001c08;
|
|
31
29
|
--faint: #64677d;
|
|
@@ -41,7 +39,7 @@
|
|
|
41
39
|
--size-field: 0.25rem;
|
|
42
40
|
--size-selector: 0.25rem;
|
|
43
41
|
--stroke: 1px;
|
|
44
|
-
--
|
|
42
|
+
--relief: 0;
|
|
45
43
|
|
|
46
44
|
/* Optional: categorical set and syntax */
|
|
47
45
|
--chart-capacity: 8;
|
package/themes/cmyk.css
CHANGED
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
--size-field: 0.25rem;
|
|
45
45
|
--size-selector: 0.25rem;
|
|
46
46
|
--stroke: 1px;
|
|
47
|
-
--
|
|
47
|
+
--relief: 0;
|
|
48
48
|
|
|
49
49
|
/* daisyUI has no categorical channel, so this theme authors no `--chart-*` and takes the eight
|
|
50
50
|
`tokens.css` publishes as its fallback. It does NOT declare `--chart-capacity: 0`: zero means
|
package/themes/coffee.css
CHANGED
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
--size-field: 0.25rem;
|
|
45
45
|
--size-selector: 0.25rem;
|
|
46
46
|
--stroke: 1px;
|
|
47
|
-
--
|
|
47
|
+
--relief: 0;
|
|
48
48
|
|
|
49
49
|
/* daisyUI has no categorical channel, so this theme authors no `--chart-*` and takes the eight
|
|
50
50
|
`tokens.css` publishes as its fallback. It does NOT declare `--chart-capacity: 0`: zero means
|
package/themes/cyberpunk.css
CHANGED
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
--size-field: 0.25rem;
|
|
45
45
|
--size-selector: 0.25rem;
|
|
46
46
|
--stroke: 1px;
|
|
47
|
-
--
|
|
47
|
+
--relief: 0;
|
|
48
48
|
|
|
49
49
|
/* daisyUI has no categorical channel, so this theme authors no `--chart-*` and takes the eight
|
|
50
50
|
`tokens.css` publishes as its fallback. It does NOT declare `--chart-capacity: 0`: zero means
|
package/themes/dim.css
CHANGED
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
--size-field: 0.25rem;
|
|
45
45
|
--size-selector: 0.25rem;
|
|
46
46
|
--stroke: 1px;
|
|
47
|
-
--
|
|
47
|
+
--relief: 0;
|
|
48
48
|
|
|
49
49
|
/* daisyUI has no categorical channel, so this theme authors no `--chart-*` and takes the eight
|
|
50
50
|
`tokens.css` publishes as its fallback. It does NOT declare `--chart-capacity: 0`: zero means
|
package/themes/dracula-dark.css
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
/* dracula-dark — a theme is one mode, one flat block
|
|
2
|
-
from the compiled document that shipped before the derivation was cut, so the colours are
|
|
3
|
-
unchanged. Edit this file by hand from here on: it is source now, not output. */
|
|
1
|
+
/* dracula-dark — a theme is one mode, one flat block, written by hand. */
|
|
4
2
|
[data-theme="dracula-dark"] {
|
|
5
3
|
color-scheme: dark;
|
|
6
4
|
|
|
@@ -25,7 +23,7 @@
|
|
|
25
23
|
--border: #2d354c;
|
|
26
24
|
--ring: #a75182;
|
|
27
25
|
|
|
28
|
-
/*
|
|
26
|
+
/* Optional: each falls back to a bridge default in tokens.css when left out */
|
|
29
27
|
--popover: #10141f;
|
|
30
28
|
--input: #6272a4;
|
|
31
29
|
--field: #090a0e;
|
|
@@ -42,7 +40,7 @@
|
|
|
42
40
|
--size-field: 0.25rem;
|
|
43
41
|
--size-selector: 0.25rem;
|
|
44
42
|
--stroke: 1px;
|
|
45
|
-
--
|
|
43
|
+
--relief: 0;
|
|
46
44
|
|
|
47
45
|
/* Optional: categorical set and syntax */
|
|
48
46
|
--chart-capacity: 8;
|
package/themes/dracula.css
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
/* dracula — a theme is one mode, one flat block
|
|
2
|
-
from the compiled document that shipped before the derivation was cut, so the colours are
|
|
3
|
-
unchanged. Edit this file by hand from here on: it is source now, not output. */
|
|
1
|
+
/* dracula — a theme is one mode, one flat block, written by hand. */
|
|
4
2
|
[data-theme="dracula"] {
|
|
5
3
|
color-scheme: light;
|
|
6
4
|
|
|
@@ -25,7 +23,7 @@
|
|
|
25
23
|
--border: #bfc9e3;
|
|
26
24
|
--ring: #e562af;
|
|
27
25
|
|
|
28
|
-
/*
|
|
26
|
+
/* Optional: each falls back to a bridge default in tokens.css when left out */
|
|
29
27
|
--input: #6272a4;
|
|
30
28
|
--field: #0000df09;
|
|
31
29
|
--faint: #5a699a;
|
|
@@ -41,7 +39,7 @@
|
|
|
41
39
|
--size-field: 0.25rem;
|
|
42
40
|
--size-selector: 0.25rem;
|
|
43
41
|
--stroke: 1px;
|
|
44
|
-
--
|
|
42
|
+
--relief: 0;
|
|
45
43
|
|
|
46
44
|
/* Optional: categorical set and syntax */
|
|
47
45
|
--chart-capacity: 8;
|
package/themes/forest.css
CHANGED
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
--size-field: 0.25rem;
|
|
45
45
|
--size-selector: 0.25rem;
|
|
46
46
|
--stroke: 1px;
|
|
47
|
-
--
|
|
47
|
+
--relief: 0;
|
|
48
48
|
|
|
49
49
|
/* daisyUI has no categorical channel, so this theme authors no `--chart-*` and takes the eight
|
|
50
50
|
`tokens.css` publishes as its fallback. It does NOT declare `--chart-capacity: 0`: zero means
|
package/themes/kanzo-dark.css
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
/* kanzo-dark — a theme is one mode, one flat block
|
|
2
|
-
from the compiled document that shipped before the derivation was cut, so the colours are
|
|
3
|
-
unchanged. Edit this file by hand from here on: it is source now, not output. */
|
|
1
|
+
/* kanzo-dark — a theme is one mode, one flat block, written by hand. */
|
|
4
2
|
/* `:root.dark:not([data-theme])` for the same reason kanzo.css claims the bare unattributed
|
|
5
3
|
document, and for the half that file could not cover: a first-time visitor with the OS in dark
|
|
6
4
|
has an empty `localStorage`, so `themeByAppearance` resolves to its `""` default and no
|
|
@@ -34,7 +32,7 @@
|
|
|
34
32
|
--border: #353535;
|
|
35
33
|
--ring: #737373;
|
|
36
34
|
|
|
37
|
-
/*
|
|
35
|
+
/* Optional: each falls back to a bridge default in tokens.css when left out */
|
|
38
36
|
--popover: #141414;
|
|
39
37
|
--input: #737373;
|
|
40
38
|
--field: #0a0a0a;
|
|
@@ -51,7 +49,7 @@
|
|
|
51
49
|
--size-field: 0.25rem;
|
|
52
50
|
--size-selector: 0.25rem;
|
|
53
51
|
--stroke: 1px;
|
|
54
|
-
--
|
|
52
|
+
--relief: 0;
|
|
55
53
|
|
|
56
54
|
/* Optional: categorical set and syntax */
|
|
57
55
|
--chart-capacity: 8;
|
package/themes/kanzo.css
CHANGED
|
@@ -1,13 +1,10 @@
|
|
|
1
|
-
/* kanzo — a theme is one mode, one flat block
|
|
1
|
+
/* kanzo — a theme is one mode, one flat block, written by hand.
|
|
2
2
|
|
|
3
3
|
`:root:not([data-theme])` and NOT a bare `:root`: this is the default theme, so it binds to the
|
|
4
4
|
document that has not chosen one. A bare `:root` has the same specificity as `[data-theme="x"]`,
|
|
5
|
-
which makes source order the tie-breaker
|
|
6
|
-
`:root` here
|
|
7
|
-
|
|
8
|
-
cannot outrank a chosen one".
|
|
9
|
-
from the compiled document that shipped before the derivation was cut, so the colours are
|
|
10
|
-
unchanged. Edit this file by hand from here on: it is source now, not output. */
|
|
5
|
+
which makes source order the tie-breaker, and `themes.css` imports alphabetically, so a bare
|
|
6
|
+
`:root` here would repaint every theme sorting before "kanzo.css" with kanzo's colours. See
|
|
7
|
+
`themes.test.ts`, "binds the default theme so it cannot outrank a chosen one". */
|
|
11
8
|
:root:not([data-theme]), [data-theme="kanzo"] {
|
|
12
9
|
color-scheme: light;
|
|
13
10
|
|
|
@@ -32,7 +29,7 @@
|
|
|
32
29
|
--border: #c9c9c9;
|
|
33
30
|
--ring: #737373;
|
|
34
31
|
|
|
35
|
-
/*
|
|
32
|
+
/* Optional: each falls back to a bridge default in tokens.css when left out */
|
|
36
33
|
--input: #737373;
|
|
37
34
|
--field: #00000008;
|
|
38
35
|
--faint: #6a6a6a;
|
|
@@ -48,7 +45,7 @@
|
|
|
48
45
|
--size-field: 0.25rem;
|
|
49
46
|
--size-selector: 0.25rem;
|
|
50
47
|
--stroke: 1px;
|
|
51
|
-
--
|
|
48
|
+
--relief: 0;
|
|
52
49
|
|
|
53
50
|
/* Optional: categorical set and syntax */
|
|
54
51
|
--chart-capacity: 8;
|
package/themes/lemonade.css
CHANGED
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
--size-field: 0.25rem;
|
|
45
45
|
--size-selector: 0.25rem;
|
|
46
46
|
--stroke: 1px;
|
|
47
|
-
--
|
|
47
|
+
--relief: 0;
|
|
48
48
|
|
|
49
49
|
/* daisyUI has no categorical channel, so this theme authors no `--chart-*` and takes the eight
|
|
50
50
|
`tokens.css` publishes as its fallback. It does NOT declare `--chart-capacity: 0`: zero means
|
package/themes/lofi.css
CHANGED
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
--size-field: 0.25rem;
|
|
45
45
|
--size-selector: 0.25rem;
|
|
46
46
|
--stroke: 1px;
|
|
47
|
-
--
|
|
47
|
+
--relief: 0;
|
|
48
48
|
|
|
49
49
|
/* daisyUI has no categorical channel, so this theme authors no `--chart-*` and takes the eight
|
|
50
50
|
`tokens.css` publishes as its fallback. It does NOT declare `--chart-capacity: 0`: zero means
|
package/themes/luxury.css
CHANGED
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
--size-field: 0.25rem;
|
|
45
45
|
--size-selector: 0.25rem;
|
|
46
46
|
--stroke: 1px;
|
|
47
|
-
--
|
|
47
|
+
--relief: 1;
|
|
48
48
|
|
|
49
49
|
/* daisyUI has no categorical channel, so this theme authors no `--chart-*` and takes the eight
|
|
50
50
|
`tokens.css` publishes as its fallback. It does NOT declare `--chart-capacity: 0`: zero means
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
/* monochrome-dark — a theme is one mode, one flat block
|
|
2
|
-
from the compiled document that shipped before the derivation was cut, so the colours are
|
|
3
|
-
unchanged. Edit this file by hand from here on: it is source now, not output. */
|
|
1
|
+
/* monochrome-dark — a theme is one mode, one flat block, written by hand. */
|
|
4
2
|
[data-theme="monochrome-dark"] {
|
|
5
3
|
color-scheme: dark;
|
|
6
4
|
|
|
@@ -25,7 +23,7 @@
|
|
|
25
23
|
--border: #353535;
|
|
26
24
|
--ring: #737373;
|
|
27
25
|
|
|
28
|
-
/*
|
|
26
|
+
/* Optional: each falls back to a bridge default in tokens.css when left out */
|
|
29
27
|
--popover: #141414;
|
|
30
28
|
--input: #737373;
|
|
31
29
|
--field: #0a0a0a;
|
|
@@ -42,7 +40,7 @@
|
|
|
42
40
|
--size-field: 0.25rem;
|
|
43
41
|
--size-selector: 0.25rem;
|
|
44
42
|
--stroke: 1px;
|
|
45
|
-
--
|
|
43
|
+
--relief: 0;
|
|
46
44
|
|
|
47
45
|
/* Optional: categorical set and syntax */
|
|
48
46
|
--chart-capacity: 0;
|
package/themes/monochrome.css
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
/* monochrome — a theme is one mode, one flat block
|
|
2
|
-
from the compiled document that shipped before the derivation was cut, so the colours are
|
|
3
|
-
unchanged. Edit this file by hand from here on: it is source now, not output. */
|
|
1
|
+
/* monochrome — a theme is one mode, one flat block, written by hand. */
|
|
4
2
|
[data-theme="monochrome"] {
|
|
5
3
|
color-scheme: light;
|
|
6
4
|
|
|
@@ -25,7 +23,7 @@
|
|
|
25
23
|
--border: #c9c9c9;
|
|
26
24
|
--ring: #737373;
|
|
27
25
|
|
|
28
|
-
/*
|
|
26
|
+
/* Optional: each falls back to a bridge default in tokens.css when left out */
|
|
29
27
|
--input: #737373;
|
|
30
28
|
--field: #00000008;
|
|
31
29
|
--faint: #6a6a6a;
|
|
@@ -41,7 +39,7 @@
|
|
|
41
39
|
--size-field: 0.25rem;
|
|
42
40
|
--size-selector: 0.25rem;
|
|
43
41
|
--stroke: 1px;
|
|
44
|
-
--
|
|
42
|
+
--relief: 0;
|
|
45
43
|
|
|
46
44
|
/* Optional: categorical set and syntax */
|
|
47
45
|
--chart-capacity: 0;
|
package/themes/night.css
CHANGED
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
--size-field: 0.25rem;
|
|
45
45
|
--size-selector: 0.25rem;
|
|
46
46
|
--stroke: 1px;
|
|
47
|
-
--
|
|
47
|
+
--relief: 0;
|
|
48
48
|
|
|
49
49
|
/* daisyUI has no categorical channel, so this theme authors no `--chart-*` and takes the eight
|
|
50
50
|
`tokens.css` publishes as its fallback. It does NOT declare `--chart-capacity: 0`: zero means
|
package/themes/nord-dark.css
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
/* nord-dark — a theme is one mode, one flat block
|
|
2
|
-
from the compiled document that shipped before the derivation was cut, so the colours are
|
|
3
|
-
unchanged. Edit this file by hand from here on: it is source now, not output. */
|
|
1
|
+
/* nord-dark — a theme is one mode, one flat block, written by hand. */
|
|
4
2
|
[data-theme="nord-dark"] {
|
|
5
3
|
color-scheme: dark;
|
|
6
4
|
|
|
@@ -25,7 +23,7 @@
|
|
|
25
23
|
--border: #282d36;
|
|
26
24
|
--ring: #567a84;
|
|
27
25
|
|
|
28
|
-
/*
|
|
26
|
+
/* Optional: each falls back to a bridge default in tokens.css when left out */
|
|
29
27
|
--popover: #101217;
|
|
30
28
|
--input: #545e73;
|
|
31
29
|
--field: #090a0c;
|
|
@@ -42,7 +40,7 @@
|
|
|
42
40
|
--size-field: 0.25rem;
|
|
43
41
|
--size-selector: 0.25rem;
|
|
44
42
|
--stroke: 1px;
|
|
45
|
-
--
|
|
43
|
+
--relief: 0;
|
|
46
44
|
|
|
47
45
|
/* Optional: categorical set and syntax */
|
|
48
46
|
--chart-capacity: 8;
|
package/themes/nord.css
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
/* nord — a theme is one mode, one flat block
|
|
2
|
-
from the compiled document that shipped before the derivation was cut, so the colours are
|
|
3
|
-
unchanged. Edit this file by hand from here on: it is source now, not output. */
|
|
1
|
+
/* nord — a theme is one mode, one flat block, written by hand. */
|
|
4
2
|
[data-theme="nord"] {
|
|
5
3
|
color-scheme: light;
|
|
6
4
|
|
|
@@ -25,7 +23,7 @@
|
|
|
25
23
|
--border: #b8bdc8;
|
|
26
24
|
--ring: #6299a9;
|
|
27
25
|
|
|
28
|
-
/*
|
|
26
|
+
/* Optional: each falls back to a bridge default in tokens.css when left out */
|
|
29
27
|
--input: #798191;
|
|
30
28
|
--field: #0000000a;
|
|
31
29
|
--faint: #444e61;
|
|
@@ -41,7 +39,7 @@
|
|
|
41
39
|
--size-field: 0.25rem;
|
|
42
40
|
--size-selector: 0.25rem;
|
|
43
41
|
--stroke: 1px;
|
|
44
|
-
--
|
|
42
|
+
--relief: 0;
|
|
45
43
|
|
|
46
44
|
/* Optional: categorical set and syntax */
|
|
47
45
|
--chart-capacity: 8;
|
package/themes/sunset.css
CHANGED
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
--size-field: 0.25rem;
|
|
45
45
|
--size-selector: 0.25rem;
|
|
46
46
|
--stroke: 1px;
|
|
47
|
-
--
|
|
47
|
+
--relief: 0;
|
|
48
48
|
|
|
49
49
|
/* daisyUI has no categorical channel, so this theme authors no `--chart-*` and takes the eight
|
|
50
50
|
`tokens.css` publishes as its fallback. It does NOT declare `--chart-capacity: 0`: zero means
|
package/themes/synthwave.css
CHANGED
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
--size-field: 0.25rem;
|
|
45
45
|
--size-selector: 0.25rem;
|
|
46
46
|
--stroke: 1px;
|
|
47
|
-
--
|
|
47
|
+
--relief: 0;
|
|
48
48
|
|
|
49
49
|
/* daisyUI has no categorical channel, so this theme authors no `--chart-*` and takes the eight
|
|
50
50
|
`tokens.css` publishes as its fallback. It does NOT declare `--chart-capacity: 0`: zero means
|
package/themes/wireframe.css
CHANGED
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
--size-field: 0.25rem;
|
|
45
45
|
--size-selector: 0.25rem;
|
|
46
46
|
--stroke: 1px;
|
|
47
|
-
--
|
|
47
|
+
--relief: 0;
|
|
48
48
|
|
|
49
49
|
/* daisyUI has no categorical channel, so this theme authors no `--chart-*` and takes the eight
|
|
50
50
|
`tokens.css` publishes as its fallback. It does NOT declare `--chart-capacity: 0`: zero means
|
package/tokens.css
CHANGED
|
@@ -105,7 +105,7 @@
|
|
|
105
105
|
--color-sidebar-primary: var(--primary);
|
|
106
106
|
--color-sidebar-primary-foreground: var(--primary-foreground);
|
|
107
107
|
--color-sidebar-accent: var(--accent);
|
|
108
|
-
--color-sidebar-accent-foreground: var(--accent-foreground);
|
|
108
|
+
--color-sidebar-accent-foreground: var(--accent-foreground, var(--foreground));
|
|
109
109
|
--color-sidebar-ring: var(--ring);
|
|
110
110
|
|
|
111
111
|
/* ── The categorical set ──────────────────────────────────────────────────────
|
|
@@ -279,7 +279,7 @@
|
|
|
279
279
|
utilities now answer to `--radius-field` — `rounded-lg` sits on a button far more often than
|
|
280
280
|
on a card. */
|
|
281
281
|
--stroke: 1px;
|
|
282
|
-
--
|
|
282
|
+
--relief: 0;
|
|
283
283
|
--size-field: 0.25rem;
|
|
284
284
|
--size-selector: 0.25rem;
|
|
285
285
|
|
|
@@ -297,9 +297,9 @@
|
|
|
297
297
|
3px — a 19px content in a 16px box, visible in `app-shell` and `job-studio` and in no test.
|
|
298
298
|
`theme-tokens.test.ts` is the guard that makes the next deletion fail out loud.
|
|
299
299
|
|
|
300
|
-
`CodeEditor`
|
|
301
|
-
12px rather than 13px and 11px. That is the same defect wearing a fallback,
|
|
302
|
-
|
|
300
|
+
`CodeEditor` read two of these with a literal fallback and so kept rendering — at 14px and
|
|
301
|
+
12px rather than 13px and 11px. That is the same defect wearing a fallback, so the fallbacks
|
|
302
|
+
are gone and the guard now rejects one on any name that is always declared. */
|
|
303
303
|
--kanzo-font-size-base: 0.8125rem; /* 13px — IDE base (below Tailwind text-xs) */
|
|
304
304
|
--kanzo-font-size-small: 0.6875rem; /* 11px */
|
|
305
305
|
--kanzo-font-size-xs: 0.625rem; /* 10px — `obligations.ts` grades this one */
|