@cogenta/theme-canonical 0.1.2 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/dist/src/render/blocks/collection-list.js +1 -1
  2. package/dist/src/render/blocks/collection-list.js.map +1 -1
  3. package/dist/src/render/blocks/cta.d.ts.map +1 -1
  4. package/dist/src/render/blocks/cta.js +3 -1
  5. package/dist/src/render/blocks/cta.js.map +1 -1
  6. package/dist/src/render/blocks/faq.d.ts.map +1 -1
  7. package/dist/src/render/blocks/faq.js +1 -1
  8. package/dist/src/render/blocks/faq.js.map +1 -1
  9. package/dist/src/render/blocks/feature-grid.js +1 -1
  10. package/dist/src/render/blocks/feature-grid.js.map +1 -1
  11. package/dist/src/render/blocks/hero.d.ts.map +1 -1
  12. package/dist/src/render/blocks/hero.js +5 -1
  13. package/dist/src/render/blocks/hero.js.map +1 -1
  14. package/dist/src/render/blocks/logos.d.ts.map +1 -1
  15. package/dist/src/render/blocks/logos.js +1 -1
  16. package/dist/src/render/blocks/logos.js.map +1 -1
  17. package/dist/src/render/blocks/media-figure.js +1 -1
  18. package/dist/src/render/blocks/media-figure.js.map +1 -1
  19. package/dist/src/render/blocks/quote.d.ts.map +1 -1
  20. package/dist/src/render/blocks/quote.js +4 -2
  21. package/dist/src/render/blocks/quote.js.map +1 -1
  22. package/dist/src/render/blocks/stats.d.ts.map +1 -1
  23. package/dist/src/render/blocks/stats.js +1 -1
  24. package/dist/src/render/blocks/stats.js.map +1 -1
  25. package/dist/src/render/render-block.d.ts.map +1 -1
  26. package/dist/src/render/render-block.js +34 -1
  27. package/dist/src/render/render-block.js.map +1 -1
  28. package/package.json +4 -3
  29. package/src/render/blocks/collection-list.ts +1 -1
  30. package/src/render/blocks/cta.ts +8 -2
  31. package/src/render/blocks/faq.ts +5 -1
  32. package/src/render/blocks/feature-grid.ts +1 -1
  33. package/src/render/blocks/hero.ts +7 -3
  34. package/src/render/blocks/logos.ts +5 -1
  35. package/src/render/blocks/media-figure.ts +1 -1
  36. package/src/render/blocks/quote.ts +5 -3
  37. package/src/render/blocks/stats.ts +5 -1
  38. package/src/render/render-block.ts +34 -1
  39. package/src/styles/base.css +341 -0
  40. package/src/styles/blocks.css +890 -0
  41. package/src/styles/theme.css +20 -601
  42. package/src/styles/tokens.css +339 -0
@@ -0,0 +1,339 @@
1
+ /*
2
+ * The canonical theme's design system.
3
+ *
4
+ * Contract D fixes a *closed* skin token set — seven colours, five type tokens,
5
+ * two spacing tokens, three radii, three motion tokens, two shadows — and
6
+ * refuses any skin that adds one. So this layer adds nothing to the skin: every
7
+ * value below is *derived* from `--cogenta-*` by composition (`calc`,
8
+ * `color-mix`, relative colour syntax). A skin swap rewrites the generated
9
+ * `--cogenta-*` sheet and this whole system follows, with no build step, which
10
+ * is what contract D's hot swap means in practice.
11
+ *
12
+ * Two rules hold everywhere in this file and the two that import it:
13
+ *
14
+ * 1. **No colour literal.** Not one hex, `rgb()`, `hsl()` or bare `oklch()`.
15
+ * Every colour is either a skin token or a function *of* a skin token, so
16
+ * there is no colour on the page a skin cannot change. `theme.css`'s own
17
+ * isolation test enforces this on the source.
18
+ * 2. **No second scale.** The type steps come from `--cogenta-font-size-*` and
19
+ * the spacing multiplier from `--cogenta-space-scale`, both emitted by
20
+ * `renderSkin` from the same tokens the validator checked. Re-deriving them
21
+ * here would let the theme's idea of "large" drift from the skin's.
22
+ */
23
+
24
+ :where(:root) {
25
+ /* Lets the browser paint native controls, scrollbars and form widgets in the
26
+ right scheme, and is what makes `light-dark()` below resolve at all. */
27
+ color-scheme: light dark;
28
+ }
29
+
30
+ /* An explicit choice wins over the OS preference. No script ships with this
31
+ theme to set it; the attribute is the hook a site (or the admin) can flip. */
32
+ :where(:root[data-theme="dark"]) {
33
+ color-scheme: dark;
34
+ }
35
+
36
+ :where(:root[data-theme="light"]) {
37
+ color-scheme: light;
38
+ }
39
+
40
+ /* --------------------------------------------------------------------------
41
+ Space — one multiplier, one unit, a scale of multiples
42
+ -------------------------------------------------------------------------- */
43
+
44
+ :where(:root) {
45
+ /* `renderSkin` turns the skin's `space.density` word into this number.
46
+ The fallback keeps the theme usable against a hand-written sheet. */
47
+ --cg-density: var(--cogenta-space-scale, 1);
48
+ --cg-unit: calc(var(--cogenta-space-unit) * var(--cg-density));
49
+
50
+ --cg-s1: calc(var(--cg-unit) * 1);
51
+ --cg-s2: calc(var(--cg-unit) * 2);
52
+ --cg-s3: calc(var(--cg-unit) * 3);
53
+ --cg-s4: calc(var(--cg-unit) * 4);
54
+ --cg-s5: calc(var(--cg-unit) * 5);
55
+ --cg-s6: calc(var(--cg-unit) * 6);
56
+ --cg-s8: calc(var(--cg-unit) * 8);
57
+ --cg-s10: calc(var(--cg-unit) * 10);
58
+ --cg-s12: calc(var(--cg-unit) * 12);
59
+ --cg-s16: calc(var(--cg-unit) * 16);
60
+ --cg-s20: calc(var(--cg-unit) * 20);
61
+ --cg-s24: calc(var(--cg-unit) * 24);
62
+
63
+ /* Named steps, so a block says what it means rather than picking a number. */
64
+ --cg-gutter: var(--cg-s5);
65
+ --cg-gap-tight: var(--cg-s2);
66
+ --cg-gap: var(--cg-s4);
67
+ --cg-gap-loose: var(--cg-s8);
68
+ --cg-section-y: var(--cg-s16);
69
+
70
+ --cg-measure: 68ch;
71
+ --cg-measure-narrow: 46ch;
72
+ --cg-page: 76rem;
73
+ --cg-page-narrow: 52rem;
74
+ }
75
+
76
+ /* `space.density` is a skin-wide word; these let one region override it — a
77
+ compact sidebar inside a spacious page. CSS still cannot branch on the
78
+ *value* of a custom property, so the attribute stays the mechanism. */
79
+ :where([data-density="compact"]) {
80
+ --cg-density: 0.75;
81
+ }
82
+
83
+ :where([data-density="comfortable"]) {
84
+ --cg-density: 1;
85
+ }
86
+
87
+ :where([data-density="spacious"]) {
88
+ --cg-density: 1.25;
89
+ }
90
+
91
+ /* --------------------------------------------------------------------------
92
+ Type — the skin's scale, named; plus the rhythm the scale does not carry
93
+ -------------------------------------------------------------------------- */
94
+
95
+ :where(:root) {
96
+ /* Seven steps, computed by `renderSkin` as `baseSize * scale^(n-2)`. The
97
+ fallbacks matter: a sheet from an older skin renderer would otherwise
98
+ leave every font size unset. */
99
+ --cg-text-xs: var(--cogenta-font-size-xs, 0.64rem);
100
+ --cg-text-sm: var(--cogenta-font-size-sm, 0.8rem);
101
+ --cg-text-md: var(--cogenta-font-size-md, var(--cogenta-font-base-size, 1rem));
102
+ --cg-text-lg: var(--cogenta-font-size-lg, 1.25rem);
103
+ --cg-text-xl: var(--cogenta-font-size-xl, 1.5625rem);
104
+ --cg-text-2xl: var(--cogenta-font-size-2xl, 1.9531rem);
105
+ --cg-text-3xl: var(--cogenta-font-size-3xl, 2.4414rem);
106
+
107
+ /* Unitless throughout: a line height inherited as a fixed pixel value is the
108
+ classic cause of clipped text at 200% zoom (WCAG 1.4.4). */
109
+ --cg-leading-tight: 1.06;
110
+ --cg-leading-snug: 1.2;
111
+ --cg-leading-normal: 1.5;
112
+ --cg-leading-relaxed: 1.7;
113
+
114
+ /* Display type needs negative tracking to stop looking loose; small caps
115
+ labels need positive. Both are ems, so they follow the font size. */
116
+ --cg-tracking-tight: -0.022em;
117
+ --cg-tracking-snug: -0.012em;
118
+ --cg-tracking-normal: 0em;
119
+ --cg-tracking-wide: 0.08em;
120
+
121
+ --cg-weight-regular: 400;
122
+ --cg-weight-medium: 500;
123
+ --cg-weight-semibold: 600;
124
+ --cg-weight-bold: 700;
125
+ }
126
+
127
+ /* --------------------------------------------------------------------------
128
+ Radius — the skin's three, plus the two ends they imply
129
+ -------------------------------------------------------------------------- */
130
+
131
+ :where(:root) {
132
+ --cg-radius-xs: calc(var(--cogenta-radius-sm) / 2);
133
+ --cg-radius-sm: var(--cogenta-radius-sm);
134
+ --cg-radius-md: var(--cogenta-radius-md);
135
+ --cg-radius-lg: var(--cogenta-radius-lg);
136
+ /* Panels and media want to round harder than a button does. Derived, so a
137
+ square skin (`radius.lg: 0`) stays square all the way up. */
138
+ --cg-radius-xl: calc(var(--cogenta-radius-lg) * 1.75);
139
+ --cg-radius-pill: 999rem;
140
+ }
141
+
142
+ /* --------------------------------------------------------------------------
143
+ Motion
144
+ -------------------------------------------------------------------------- */
145
+
146
+ :where(:root) {
147
+ --cg-duration-fast: calc(var(--cogenta-motion-duration) * 0.6);
148
+ --cg-duration: var(--cogenta-motion-duration);
149
+ --cg-duration-slow: calc(var(--cogenta-motion-duration) * 2.6);
150
+ --cg-ease: var(--cogenta-motion-easing);
151
+ }
152
+
153
+ /* --------------------------------------------------------------------------
154
+ Colour — one palette per scheme, both derived from the same seven tokens
155
+ --------------------------------------------------------------------------
156
+
157
+ The block below declares the **light** palette in plain terms. The scheme
158
+ aware values live in the `@supports` block after it, and only there.
159
+
160
+ Why the split rather than one `light-dark()` per token: a custom property
161
+ accepts any token sequence, so a browser that does not know `light-dark()`
162
+ still stores the declaration and only fails when the value is *used* — at
163
+ which point the property becomes `unset` and the page loses its colour. A
164
+ feature query is the only construct that actually falls back here, and it is
165
+ written with `currentColor` so it needs no colour literal of its own.
166
+
167
+ **The dark palette is designed, not inverted.** Five decisions separate it
168
+ from a mechanical swap of `bg` and `fg`, and each one is the opposite of what
169
+ inversion produces:
170
+
171
+ 1. *Elevation runs the other way.* In light, a raised surface is the same
172
+ paper with a shadow under it. In dark, a shadow on a dark ground conveys
173
+ nothing, so elevation is expressed as **lightness**: sunken < canvas <
174
+ surface < raised, each a step up in L. Shadows give way to a hairline
175
+ ring (`--cg-ring`).
176
+ 2. *The accent is lifted, not kept.* A mid-dark accent that reads well on
177
+ paper is muddy on ink, so the dark accent is pushed to L 0.74 — brighter
178
+ than the skin's own accent, which inversion would never do.
179
+ 3. *Therefore the accent's foreground flips to dark.* Light label text on a
180
+ lifted accent fails contrast; the dark palette derives an ink-dark
181
+ `accent-fg` from the accent's own hue. This is the clearest single
182
+ difference from inversion, which would have kept the skin's `accentFg`.
183
+ 4. *Text is never pure light.* Ink is L 0.945 with a trace of the accent's
184
+ chroma — off-white reduces the halation pure white causes on dark.
185
+ 5. *Borders become light overlays.* A darker line on a dark surface is
186
+ invisible; the dark border is a step up in lightness, not down.
187
+
188
+ The hue and chroma of every dark value come from a skin token, so a skin with
189
+ an orange accent gets a warm dark palette without touching this file. The
190
+ *lightness* targets are the theme's — which is exactly the split contract D
191
+ draws: the skin owns the colours, the theme owns what it does with them.
192
+
193
+ Every ratio these values produce is computed for real, from this file, by
194
+ `test/design-system.test.ts` — in both schemes, against the AA body
195
+ threshold. None of the numbers above is a comment to be trusted.
196
+
197
+ Known limit, recorded in `BLOCKERS.md`: the derivation assumes a light-first
198
+ skin (`bg` lighter than `fg`), which is what contract D's default and all
199
+ nine blueprints use. A skin that is already dark would need a second colour
200
+ group in the contract — a `theme@2.0` bump, not a change this theme can make.
201
+ -------------------------------------------------------------------------- */
202
+
203
+ :where(:root) {
204
+ --cg-canvas: var(--cogenta-color-bg);
205
+ --cg-surface: var(--cogenta-color-bg);
206
+ --cg-surface-raised: var(--cogenta-color-bg);
207
+ --cg-surface-sunken: var(--cogenta-color-muted);
208
+ --cg-surface-tinted: color-mix(in oklab, var(--cogenta-color-accent) 8%, var(--cogenta-color-bg));
209
+
210
+ --cg-ink: var(--cogenta-color-fg);
211
+ --cg-ink-muted: var(--cogenta-color-muted-fg);
212
+ /* Tertiary: timestamps, credits, the second line of a card. */
213
+ --cg-ink-subtle: color-mix(in oklab, var(--cogenta-color-muted-fg) 88%, var(--cogenta-color-bg));
214
+
215
+ --cg-accent: var(--cogenta-color-accent);
216
+ --cg-accent-fg: var(--cogenta-color-accent-fg);
217
+ --cg-accent-hover: color-mix(in oklab, var(--cogenta-color-accent) 86%, var(--cogenta-color-fg));
218
+ --cg-accent-soft: color-mix(in oklab, var(--cogenta-color-accent) 10%, var(--cogenta-color-bg));
219
+ --cg-accent-soft-fg: color-mix(
220
+ in oklab,
221
+ var(--cogenta-color-accent) 68%,
222
+ var(--cogenta-color-fg)
223
+ );
224
+
225
+ --cg-line: var(--cogenta-color-border);
226
+ --cg-line-soft: color-mix(in oklab, var(--cogenta-color-border) 55%, var(--cogenta-color-bg));
227
+ --cg-line-strong: color-mix(in oklab, var(--cogenta-color-border) 55%, var(--cogenta-color-fg));
228
+
229
+ /* Overlay ground, for a scrim over media. */
230
+ --cg-scrim: color-mix(in oklab, var(--cogenta-color-fg) 62%, transparent);
231
+ }
232
+
233
+ @supports (color: light-dark(currentColor, currentColor)) and
234
+ (color: oklch(from currentColor l c h)) {
235
+ :where(:root) {
236
+ --cg-canvas: light-dark(
237
+ var(--cogenta-color-bg),
238
+ oklch(from var(--cogenta-color-fg) 0.17 calc(c * 0.7) h)
239
+ );
240
+ --cg-surface: light-dark(
241
+ var(--cogenta-color-bg),
242
+ oklch(from var(--cogenta-color-fg) 0.215 calc(c * 0.62) h)
243
+ );
244
+ --cg-surface-raised: light-dark(
245
+ var(--cogenta-color-bg),
246
+ oklch(from var(--cogenta-color-fg) 0.26 calc(c * 0.55) h)
247
+ );
248
+ --cg-surface-sunken: light-dark(
249
+ var(--cogenta-color-muted),
250
+ oklch(from var(--cogenta-color-fg) 0.13 calc(c * 0.8) h)
251
+ );
252
+ --cg-surface-tinted: light-dark(
253
+ color-mix(in oklab, var(--cogenta-color-accent) 8%, var(--cogenta-color-bg)),
254
+ oklch(from var(--cogenta-color-accent) 0.255 calc(c * 0.5) h)
255
+ );
256
+
257
+ --cg-ink: light-dark(
258
+ var(--cogenta-color-fg),
259
+ oklch(from var(--cogenta-color-accent) 0.945 0.012 h)
260
+ );
261
+ --cg-ink-muted: light-dark(
262
+ var(--cogenta-color-muted-fg),
263
+ oklch(from var(--cogenta-color-accent) 0.78 0.018 h)
264
+ );
265
+ --cg-ink-subtle: light-dark(
266
+ color-mix(in oklab, var(--cogenta-color-muted-fg) 88%, var(--cogenta-color-bg)),
267
+ oklch(from var(--cogenta-color-accent) 0.68 0.016 h)
268
+ );
269
+
270
+ --cg-accent: light-dark(
271
+ var(--cogenta-color-accent),
272
+ oklch(from var(--cogenta-color-accent) 0.74 calc(c * 0.85) h)
273
+ );
274
+ --cg-accent-fg: light-dark(
275
+ var(--cogenta-color-accent-fg),
276
+ oklch(from var(--cogenta-color-accent) 0.18 calc(c * 0.55) h)
277
+ );
278
+ --cg-accent-hover: light-dark(
279
+ color-mix(in oklab, var(--cogenta-color-accent) 86%, var(--cogenta-color-fg)),
280
+ oklch(from var(--cogenta-color-accent) 0.82 calc(c * 0.8) h)
281
+ );
282
+ --cg-accent-soft: light-dark(
283
+ color-mix(in oklab, var(--cogenta-color-accent) 10%, var(--cogenta-color-bg)),
284
+ oklch(from var(--cogenta-color-accent) 0.3 calc(c * 0.5) h)
285
+ );
286
+ --cg-accent-soft-fg: light-dark(
287
+ color-mix(in oklab, var(--cogenta-color-accent) 68%, var(--cogenta-color-fg)),
288
+ oklch(from var(--cogenta-color-accent) 0.9 calc(c * 0.45) h)
289
+ );
290
+
291
+ --cg-line: light-dark(
292
+ var(--cogenta-color-border),
293
+ oklch(from var(--cogenta-color-accent) 0.33 calc(c * 0.22) h)
294
+ );
295
+ --cg-line-soft: light-dark(
296
+ color-mix(in oklab, var(--cogenta-color-border) 55%, var(--cogenta-color-bg)),
297
+ oklch(from var(--cogenta-color-accent) 0.26 calc(c * 0.2) h)
298
+ );
299
+ --cg-line-strong: light-dark(
300
+ color-mix(in oklab, var(--cogenta-color-border) 55%, var(--cogenta-color-fg)),
301
+ oklch(from var(--cogenta-color-accent) 0.58 calc(c * 0.25) h)
302
+ );
303
+ }
304
+ }
305
+
306
+ /* --------------------------------------------------------------------------
307
+ Elevation — a shadow in light, a lighter surface and a ring in dark
308
+ --------------------------------------------------------------------------
309
+
310
+ `light-dark()` takes colours only, so these three cannot use it: a shadow is
311
+ not a colour. They are the one place in this file where the dark values are
312
+ written under a media query and repeated for the explicit toggle.
313
+ -------------------------------------------------------------------------- */
314
+
315
+ :where(:root) {
316
+ /* Inset, so it never adds to the box's size and can sit in the same
317
+ `box-shadow` list as a real drop shadow. */
318
+ --cg-ring: inset 0 0 0 1px var(--cg-line);
319
+ --cg-ring-accent: inset 0 0 0 1px var(--cg-accent);
320
+
321
+ --cg-elevation-0: none;
322
+ --cg-elevation-1: var(--cogenta-shadow-sm);
323
+ --cg-elevation-2: var(--cogenta-shadow-md);
324
+ --cg-elevation-3: var(--cogenta-shadow-md), var(--cogenta-shadow-sm);
325
+ }
326
+
327
+ @media (prefers-color-scheme: dark) {
328
+ :where(:root:not([data-theme="light"])) {
329
+ --cg-elevation-1: var(--cg-ring);
330
+ --cg-elevation-2: var(--cg-ring);
331
+ --cg-elevation-3: var(--cg-ring), var(--cogenta-shadow-md);
332
+ }
333
+ }
334
+
335
+ :where(:root[data-theme="dark"]) {
336
+ --cg-elevation-1: var(--cg-ring);
337
+ --cg-elevation-2: var(--cg-ring);
338
+ --cg-elevation-3: var(--cg-ring), var(--cogenta-shadow-md);
339
+ }