sheleg-design-skill 1.51.1 → 1.52.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -6,6 +6,97 @@ follow [SemVer](https://semver.org/spec/v2.0.0.html).
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [1.52.0] - 2026-08-27
10
+
11
+ ### The front door is the designs
12
+
13
+ Opening the published catalogue used to mean reading three tiles and clicking one before
14
+ seeing a single pack. It now opens **on the packs**, behind a tab strip of three real
15
+ screens: **Designs** (the front door, and it is the gallery), **Audit** and **Method** —
16
+ the prose that used to occupy the index. `packs.html` stays as a canonical alias for the
17
+ URL the gallery lived at, because two URLs serving one gallery is duplicate content.
18
+
19
+ The tabs are links to pages rather than panels toggled in one document, and that is the
20
+ SEO decision, not a shortcut: one URL per screen, every screen fully in the HTML, nothing
21
+ behind a click a crawler has to execute. `rel="prefetch"` buys the tab feel back.
22
+
23
+ ### What the SEO pass changed
24
+
25
+ - **Structured data, with one publisher everybody points at.** A `CollectionPage` whose
26
+ `mainEntity` is an `ItemList` of all thirty-six packs, each with its own anchor
27
+ (`/#pack-<name>`), so an answer engine can lift the list without running the filter JS;
28
+ a `SoftwareApplication` on Method; `BreadcrumbList` on both sub-pages; and an
29
+ `Organization` node referenced by `@id` from every page. The family site shipped the
30
+ opposite of that for two releases — a publisher node nothing pointed at.
31
+ - **A `404.html`**, a three-URL sitemap that lists only indexable screens, `llms.txt`
32
+ rewritten for the new shape, and `max-image-preview:large`, `color-scheme`,
33
+ `theme-color`, `og:site_name` and `og:locale` on every page.
34
+ - **Two defects the pass found in the old pages:** `method.html` was emitting its
35
+ canonical and description **twice** (the shared `HEAD` and `page_meta` both fired), and
36
+ the published dark-field count was **wrong** — `gallery.py` could not parse
37
+ `oklch()`, so `briefing-room`'s near-black field read as light and the site, its
38
+ `llms.txt` and its gallery all said five dark packs where six ship.
39
+
40
+ ### `deskmate`, corrected by its own audit
41
+
42
+ A blind read of the pack (T34a, which also routed correctly and rejected `tenor` on the
43
+ quoted clause) found nine defects. The important one is a colour pairing no gate can see:
44
+ `--surface` stays white on the dusk surface while `--ink` inverts to white, so **a card
45
+ that painted its heading in `--ink` painted white on white** inside an inverted section.
46
+ The pack now ships `--on-surface` and `--on-surface-soft` — words on a card take those,
47
+ words on the field take `--ink` — and the kit consumes them.
48
+
49
+ Also fixed: the focus ring shipped derived-only, so a browser without `color-mix()` got
50
+ **no ring at all** (literal first now, derived inside `@supports`, the mechanism
51
+ `showroom` established); the slab's inset was 40px in the pack and 80px in the token
52
+ layer; the gutter ramp stated four steps and shipped two tokens; the measure stated in
53
+ prose (62ch/68ch) had no token; the entrance duration's two measurements (0.64s declared,
54
+ 0.7s computed) were never reconciled; "the smallest radius that ships" and "every control
55
+ is 56px" were both contradicted by the pack's own body; and the provenance promise was
56
+ broader than the layer delivers.
57
+
58
+ One finding was rejected: `deskmate` is absent from `MOTION_DOCTRINE.md`'s ceiling
59
+ passage, and that passage is a named selection whose home the gate declares to be the
60
+ pack's own Register.
61
+
62
+ ### `onionskin` gets the same focus-ring guard
63
+
64
+ The audit that read `deskmate` measured the whole library for the same shape: three
65
+ packs derive a focus ring, and only `showroom` guarded it. `onionskin`'s ring was
66
+ `color-mix()`-only, so a browser without `color-mix()` painted **no ring at all** — the
67
+ one degradation both packs say they may not ship. The literal now sits in `:root` and the
68
+ derived form in an `@supports` block; no computed value changed, and its kit's token
69
+ block was resynced with it.
70
+
71
+ ### A count nothing could check, and now can
72
+
73
+ `SURFACE_COMPOSITION.md` claimed `--accent` in **twenty-nine** of the packs at a true
74
+ **thirty-three**, and named two of the three packs that call the accent something else —
75
+ omitting `babylove` entirely, so a reader following that sentence would look for a token
76
+ that pack does not have. `validate_counted_claims` cannot read a count whose noun is
77
+ "packs that declare a token", so `validate_token_population_counts` now derives both that
78
+ figure and the `@role non-text:` population from the token layers, and refuses a pack
79
+ that declares neither an `--accent` nor an `@role accent:` marker. The same sweep found
80
+ `SKILL.md` still saying "the twenty-nine".
81
+
82
+ ### The scenario that produced all of this, and the half of it that did not run
83
+
84
+ `T34` is the behavioural scenario shipped with `deskmate`, and 1.52.0 is the first
85
+ release where one of these branches was actually run rather than filed. **`T34a`
86
+ passed**: it routed to the pack, quoted the deciding clause, rejected `tenor` on the
87
+ clause that rules it out, and stayed inside the bundle directory. It then did the half
88
+ these scenarios have always asked for and never exercised — read the chosen pack in full
89
+ and report defects with quotes — and returned nine, eight of which are fixed above.
90
+
91
+ **`T34b`, the negative branch, terminated on an account spend limit before reporting a
92
+ verdict, and is not counted as a result.** A positive-only pass cannot detect the failure
93
+ that branch exists to detect. `test/scenarios.md` records both outcomes.
94
+
95
+ ### Ratchets
96
+
97
+ `validate.py` 5047 → 5060, `validate_palette.py` 3013 → 3023, `computed_at_least`
98
+ 686 → 691. Thirteen of the first delta are the new gate.
99
+
9
100
  ## [1.51.1] - 2026-08-27
10
101
 
11
102
  ### `deskmate`, the thirty-sixth pack — a colleague, quoted
@@ -58,6 +58,13 @@
58
58
  rules and it bars, and an icon that must be READ takes --ink-soft instead. */
59
59
  --ink-faint: #9693a3;
60
60
  --on-ink: #ffffff; /* 17.36:1 on --ink */
61
+ /* THE RULE THIS PACK CANNOT BE READ WITHOUT: --ink is for words on --bg, and
62
+ --on-surface is for words on --surface. On the light field the two are the same
63
+ value and the distinction looks decorative. On the dusk surface it is the whole
64
+ difference: --surface stays white there, measured, so a card that paints its text
65
+ in --ink paints white on white. Every component that sits on a card takes these. */
66
+ --on-surface: #1a182b; /* 17.36:1 on --surface */
67
+ --on-surface-soft: #716f7e; /* 4.91:1 on --surface */
61
68
 
62
69
  /* ---- The accent: it writes, it fills, and it needed no correction ---- */
63
70
  /* MEASURED. #6e47ff is the reference's purple-500 and its only functional accent:
@@ -121,9 +128,18 @@
121
128
  /* MEASURED — `ring-3` is 3px, and the ring's alpha IS its specification: 50% behind
122
129
  a control, 40% behind a field. Composited over white #ffffff the ring is
123
130
  #b6a3ff at 2.17:1, which is why the control's own border moves colour in the
124
- same state. */
125
- --focus-ring: 0 0 0 3px color-mix(in srgb, var(--accent) 50%, transparent);
126
- --focus-ring-field: 0 0 0 3px color-mix(in srgb, var(--accent) 40%, transparent);
131
+ same state.
132
+
133
+ THE LITERAL IS HERE AND THE DERIVED FORM IS IN THE `@supports` BLOCK AT THE END OF
134
+ THIS FILE, and the order is not a style preference. A custom property accepts
135
+ almost any token sequence, so `--focus-ring: color-mix(…)` parses even where
136
+ `color-mix()` is unsupported and wins the cascade; the invalidity surfaces only
137
+ where it is substituted, as invalid-at-computed-value-time, and by then the literal
138
+ is gone and `var(--focus-ring)` resolves to `unset` — an INVISIBLE FOCUS RING, on
139
+ the one degradation this pack says it may not ship. `showroom` is the worked
140
+ example this copies. */
141
+ --focus-ring: 0 0 0 3px rgba(110, 71, 255, 0.5);
142
+ --focus-ring-field: 0 0 0 3px rgba(110, 71, 255, 0.4);
127
143
 
128
144
  /* ---- Radii: a pill for the hand, a slab for the eye ----------------- */
129
145
  /* MEASURED at 1440: 977 of 1,191 visible elements compute 0px, 52 take the 32px
@@ -160,14 +176,20 @@
160
176
  --space-28: 112px; /* MEASURED — the section's own vertical rhythm */
161
177
 
162
178
  --page-max: 90rem; /* 1440px — MEASURED */
163
- --gutter: 20px; /* MEASURED — and it steps 40, 56, 80 outward */
164
- --gutter-wide: 80px; /* MEASURED at 1440 */
179
+ /* MEASURED, and the ramp ships as four tokens rather than as a sentence: the page's
180
+ gutter steps 20 → 40 → 56 → 80 across the reference's own breakpoints, and a step a
181
+ consumer cannot reach through a variable is a step the pack did not ship. */
182
+ --gutter: 20px;
183
+ --gutter-2: 40px; /* from 40rem */
184
+ --gutter-3: 56px; /* from 48rem */
185
+ --gutter-wide: 80px; /* from 64rem, and the value at 1440 */
165
186
  --control-h: 56px; /* MEASURED — the button and the field are the same height */
166
187
  --control-h-sm: 40px; /* MEASURED — the navigation's own pair, and see --tap-min */
167
188
  --control-px: 40px; /* MEASURED — 24px on the small size */
168
189
  /* The navigation slab is not a control and does not take a control's height:
169
- MEASURED at 65px with a 30px bottom radius at 1440, and 57px at the narrow
170
- width, inset 80px from each side of the page and 12px below 40rem. */
190
+ MEASURED at 65px with a 30px bottom radius at 1440 and 57px at the narrow width,
191
+ inset 40px from each side of the viewport and 12px below 40rem. That inset is the
192
+ SLAB's, and it is not the page's gutter — the gutter is 80px at the same width. */
171
193
  --slab-h: 65px;
172
194
  --slab-h-sm: 57px;
173
195
  --r-slab: 30px;
@@ -204,6 +226,13 @@
204
226
  --t-display-mid: 4.5rem; /* 72px from 40rem */
205
227
  --t-display-sm: 4rem; /* 64px below 40rem */
206
228
 
229
+ /* The reference holds its lede in a grid column and sets `max-width: none` on both
230
+ the headline and the paragraph, so these two are a PACK DECISION rather than a
231
+ measurement: the measure the reference implies, stated in `ch` so it survives the
232
+ face substitution below. */
233
+ --measure-lede: 62ch;
234
+ --measure-body: 68ch;
235
+
207
236
  --lh-display: 1.1; /* MEASURED — 88px on 80px */
208
237
  --lh-body: 1.4; /* MEASURED — 22.4px on 16px */
209
238
  --lh-ui: 1.5;
@@ -230,7 +259,12 @@
230
259
  --dur-fast: 0.15s; /* a colour, a border or an opacity */
231
260
  --dur-base: 0.2s; /* MEASURED — 23 elements */
232
261
  --dur-slow: 0.3s; /* MEASURED — 9 elements, a disclosure opening */
233
- --dur-enter: 0.64s; /* MEASURED — the reference's own enter-duration token */
262
+ /* MEASURED, and the two halves of the measurement disagree by 60ms: the reference
263
+ DECLARES `--enter-duration` as .6s, .64s and .68s across three sections, while the
264
+ transition on the 19 entering elements COMPUTES 0.7s. The pack ships the declared
265
+ middle value, because that is the number the reference's own author chose and the
266
+ 0.7s is one section's instance of it. */
267
+ --dur-enter: 0.64s;
234
268
  --stagger: 60ms; /* MEASURED — its enter-delay steps 0, 40, 60, 80, 120ms */
235
269
  --enter-y: 16px; /* MEASURED — the entrance travels 8px or 16px, never further */
236
270
 
@@ -294,6 +328,10 @@
294
328
  a muted icon here anyway, because its role is set by the light block. */
295
329
  --ink-faint: #9693a3;
296
330
  --on-ink: #150079;
331
+ /* A card on this surface stays WHITE — that is the measurement — so these two do NOT
332
+ invert with the field. They are the light block's values, and that is the point. */
333
+ --on-surface: #1a182b; /* 17.36:1 on --surface */
334
+ --on-surface-soft: #716f7e; /* 4.91:1 on --surface */
297
335
 
298
336
  --accent: #d2c6ff; /* 10.14:1 on --bg — SELECTED, the reference's own on-dusk eyebrow */
299
337
  --on-accent: #150079; /* 10.14:1 on --accent */
@@ -307,8 +345,12 @@
307
345
  --hairline-quiet: color-mix(in srgb, #ffffff 15%, var(--bg));
308
346
 
309
347
  --focus-color: #bcaaff; /* 7.90:1 on --bg, over the 3:1 mark floor */
310
- --focus-ring: 0 0 0 3px color-mix(in srgb, #8b6cff 50%, transparent);
311
- --focus-ring-field: 0 0 0 3px color-mix(in srgb, #8b6cff 40%, transparent);
348
+ /* MEASURED — the reference's on-dusk focus outline is its purple-400, and it is a
349
+ token here so the ring below derives from something rather than from a loose hex.
350
+ Literal first, derived in the `@supports` block, for the reason stated above. */
351
+ --focus-ring-base: #8b6cff;
352
+ --focus-ring: 0 0 0 3px rgba(139, 108, 255, 0.5);
353
+ --focus-ring-field: 0 0 0 3px rgba(139, 108, 255, 0.4);
312
354
  --gradient-word-fallback: #d2c6ff; /* 10.14:1 on --bg */
313
355
 
314
356
  /* DERIVED, and remapped rather than inherited: the light set measures 2.6-4.1:1 on
@@ -343,6 +385,22 @@
343
385
  }
344
386
  }
345
387
 
388
+ /* THE LITERALS ABOVE ARE THE FALLBACK, AND THEY ONLY WORK FROM IN HERE. The derived
389
+ form tracks the accent — change `--accent` and the ring follows — which is what a
390
+ literal cannot do; the literal exists so that a browser without `color-mix()` still
391
+ paints a ring at all. Both fields get one. */
392
+ @supports (color: color-mix(in srgb, red, red)) {
393
+ :root {
394
+ --focus-ring: 0 0 0 3px color-mix(in srgb, var(--accent) 50%, transparent);
395
+ --focus-ring-field: 0 0 0 3px color-mix(in srgb, var(--accent) 40%, transparent);
396
+ }
397
+
398
+ [data-surface="dusk"] {
399
+ --focus-ring: 0 0 0 3px color-mix(in srgb, var(--focus-ring-base) 50%, transparent);
400
+ --focus-ring-field: 0 0 0 3px color-mix(in srgb, var(--focus-ring-base) 40%, transparent);
401
+ }
402
+ }
403
+
346
404
  /* Body copy here is weight 500 on 146 nodes, and an unset `font-weight` gives 400 —
347
405
  one step light across the whole page. The UA's own 700 for `strong` and `b` is a
348
406
  step the other way and lands on the display's heading weight, so emphasis is pinned
@@ -367,10 +425,15 @@ b {
367
425
  /* PAGE — the page's own gutter, on the element a consumer never nests: a
368
426
  container query here would ask a component's box how wide the page is. */
369
427
  @media (min-width: 40rem) {
370
- .dm-page { padding-inline: var(--space-10); }
428
+ .dm-page { padding-inline: var(--gutter-2); }
371
429
  }
372
- /* PAGE — same rule, the wide step. */
373
- @media (min-width: 80rem) {
430
+ /* PAGE — the middle step. */
431
+ @media (min-width: 48rem) {
432
+ .dm-page { padding-inline: var(--gutter-3); }
433
+ }
434
+
435
+ /* PAGE — the wide step, and the value measured at 1440. */
436
+ @media (min-width: 64rem) {
374
437
  .dm-page { padding-inline: var(--gutter-wide); }
375
438
  }
376
439
 
@@ -486,15 +549,15 @@ b {
486
549
  font-size: var(--t-h3);
487
550
  font-weight: var(--weight-strong);
488
551
  letter-spacing: var(--track-display);
489
- color: var(--ink);
552
+ color: var(--on-surface);
490
553
  }
491
554
 
492
555
  .dm-card__meta {
493
556
  font-size: var(--t-sm);
494
- color: var(--ink-soft);
557
+ color: var(--on-surface-soft);
495
558
  }
496
559
 
497
- .dm-card__body { color: var(--ink-soft); font-size: var(--t-body); line-height: var(--lh-body); }
560
+ .dm-card__body { color: var(--on-surface-soft); font-size: var(--t-body); line-height: var(--lh-body); max-width: var(--measure-body); }
498
561
 
499
562
  /* ---- Chip ------------------------------------------------------------ */
500
563
  .dm-chip {
@@ -508,7 +571,7 @@ b {
508
571
  background-color: var(--surface);
509
572
  font-size: var(--t-sm);
510
573
  font-weight: var(--weight-body);
511
- color: var(--ink);
574
+ color: var(--on-surface);
512
575
  transition: border-color var(--dur-fast) var(--ease);
513
576
  }
514
577
 
@@ -617,7 +680,7 @@ b {
617
680
  margin-bottom: var(--space-2);
618
681
  font-size: var(--t-body);
619
682
  font-weight: var(--weight-body);
620
- color: var(--ink);
683
+ color: var(--on-surface);
621
684
  }
622
685
 
623
686
  .dm-field__input {
@@ -632,11 +695,11 @@ b {
632
695
  font-family: var(--font-body);
633
696
  font-size: var(--t-body);
634
697
  font-weight: var(--weight-regular);
635
- color: var(--ink);
698
+ color: var(--on-surface);
636
699
  transition: border-color var(--dur-fast) var(--ease);
637
700
  }
638
701
 
639
- .dm-field__input::placeholder { color: var(--ink-soft); }
702
+ .dm-field__input::placeholder { color: var(--on-surface-soft); }
640
703
 
641
704
  .dm-field__input:focus-visible {
642
705
  outline: none;
@@ -665,7 +728,7 @@ b {
665
728
  font-size: var(--t-sm);
666
729
  }
667
730
 
668
- .dm-slab__brand { color: var(--ink); font-weight: var(--weight-strong); }
731
+ .dm-slab__brand { color: var(--on-surface); font-weight: var(--weight-strong); }
669
732
  .dm-slab__nav { display: flex; gap: var(--space-2); margin-inline-end: auto; }
670
733
 
671
734
  .dm-slab__nav a {
@@ -674,7 +737,7 @@ b {
674
737
  min-height: var(--tap-min);
675
738
  padding: var(--space-2) var(--space-3);
676
739
  border-radius: var(--r-pill);
677
- color: var(--ink);
740
+ color: var(--on-surface);
678
741
  text-decoration: none;
679
742
  transition: background-color var(--dur-fast) var(--ease);
680
743
  }
@@ -791,10 +854,10 @@ b {
791
854
  font-size: var(--t-h3);
792
855
  font-weight: var(--weight-strong);
793
856
  letter-spacing: var(--track-display);
794
- color: var(--ink);
857
+ color: var(--on-surface);
795
858
  }
796
859
 
797
- .dm-qcard__body { color: var(--ink-soft); font-size: var(--t-body); line-height: var(--lh-body); }
860
+ .dm-qcard__body { color: var(--on-surface-soft); font-size: var(--t-body); line-height: var(--lh-body); }
798
861
 
799
862
  /* The card's halves answer to the CARD's box, not to the screen: at 34rem of
800
863
  container the artefact sits beside the sentence instead of under it. */
@@ -812,8 +875,8 @@ b {
812
875
  text-align: center;
813
876
  }
814
877
 
815
- .dm-empty__title { margin: 0; font-size: var(--t-body); font-weight: var(--weight-body); color: var(--ink); }
816
- .dm-empty__detail { margin: var(--space-2) 0 0; font-size: var(--t-body); color: var(--ink-soft); }
878
+ .dm-empty__title { margin: 0; font-size: var(--t-body); font-weight: var(--weight-body); color: var(--on-surface); }
879
+ .dm-empty__detail { margin: var(--space-2) 0 0; font-size: var(--t-body); color: var(--on-surface-soft); }
817
880
  .dm-empty__action { margin-top: var(--space-6); }
818
881
 
819
882
  /* ---- Skeleton -------------------------------------------------------- */
@@ -108,8 +108,15 @@
108
108
  --shadow-hair: 0 1px 0 0 color-mix(in srgb, var(--ink) 6%, transparent); /* MEASURED */
109
109
  --shadow-lift: 0 32px 64px -24px color-mix(in srgb, var(--ink) 28%, transparent); /* MEASURED — once */
110
110
 
111
- --focus-ring: 0 0 0 4px color-mix(in srgb, var(--accent) 10%, transparent),
112
- 0 4px 10px -3px color-mix(in srgb, var(--accent) 45%, transparent); /* MEASURED */
111
+ /* MEASURED, and declared LITERAL FIRST with the derived form in the `@supports` block
112
+ at the end of this file. The order is the guard, not a style: a custom property
113
+ accepts almost any token sequence, so a `color-mix()` value parses even where
114
+ `color-mix()` is unsupported and wins the cascade; it fails only at substitution, by
115
+ which point the literal is gone and `var(--focus-ring)` resolves to `unset` — an
116
+ invisible focus ring. Added 1.52.0; `showroom` is the pack that established the
117
+ mechanism and `deskmate` carries the same guard. */
118
+ --focus-ring: 0 0 0 4px rgba(5, 98, 239, 0.1),
119
+ 0 4px 10px -3px rgba(5, 98, 239, 0.45);
113
120
  --focus-w: 2px;
114
121
  --focus-offset: 2px;
115
122
  --focus-color: var(--accent); /* 5.25:1 on --bg, over the 3:1 mark floor */
@@ -262,6 +269,16 @@
262
269
  }
263
270
  }
264
271
 
272
+ /* THE LITERALS ABOVE ARE THE FALLBACK, AND THEY ONLY WORK FROM IN HERE. The derived form
273
+ tracks `--accent`; the literal exists so a browser without `color-mix()` still paints a
274
+ ring at all. The dark band inherits this ring, and its lighter accent with it. */
275
+ @supports (color: color-mix(in srgb, red, red)) {
276
+ :root {
277
+ --focus-ring: 0 0 0 4px color-mix(in srgb, var(--accent) 10%, transparent),
278
+ 0 4px 10px -3px color-mix(in srgb, var(--accent) 45%, transparent);
279
+ }
280
+ }
281
+
265
282
  /* The pack bans bold, and a ban the UA overrides is not a ban: `strong` and `b` are 700
266
283
  by default in every browser. 500 is the heaviest weight this page spends at scale, so
267
284
  emphasis here is the medium weight — or, better, the mono. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sheleg-design-skill",
3
- "version": "1.51.1",
3
+ "version": "1.52.0",
4
4
  "description": "Design taste as an installable agent skill. Cinematic scroll-driven landing pages built on one scroll clock and layered degrade-to-calm motion, a motion doctrine that decides whether to animate before it decides how, three calibration dials, and thirty-six locked style packs with ready-made design tokens — instrument-console, editorial-luxury, workbench, briefing-room, atrium, orchard, outrank, babylove, patchbay, nameplate, field-notes, cyclorama, showroom, blueprint, prism, maquette, scoreboard, datasheet, manpage, pigeonhole, roster, ora, tenor, paperclip, ledger, router, daylight, notation, almanac, vitrine, proscenium, awning, bulletin, rimlight, onionskin and deskmate. Colour, slop and fork-reciprocity gates run as scripts, not opinions. Works with Cursor, Claude Code and any agent that reads a SKILL.md.",
5
5
  "bin": {
6
6
  "sheleg-design-skill": "bin/cli.js"
@@ -53,7 +53,7 @@
53
53
  "type": "git",
54
54
  "url": "git+https://github.com/ssheleg/sheleg-design-skill.git"
55
55
  },
56
- "homepage": "https://github.com/ssheleg/sheleg-design-skill#readme",
56
+ "homepage": "https://skills.sshlg.me/skills/sheleg-design/",
57
57
  "bugs": {
58
58
  "url": "https://github.com/ssheleg/sheleg-design-skill/issues"
59
59
  }
@@ -2,7 +2,7 @@
2
2
  "name": "sheleg-design",
3
3
  "displayName": "SHELEG Design",
4
4
  "description": "SHELEG Design methodology: cinematic scroll-driven landing pages (single scroll clock, layered degrade-to-calm motion, WebGL particle formations), a motion doctrine that decides whether to animate before it decides how, and thirty-six pluggable visual style packs — instrument-console (dark console), editorial-luxury (warm editorial), workbench (light/dark product UI for dashboards and tools), briefing-room (dark 16:9 deck), atrium (warm consumer health), orchard, outrank, babylove (friendly consumer biotech), patchbay (a dark live schematic: one mint-cyan, 8% hairlines and no shadow, and an architecture diagram whose cords carry SMIL particles), field-notes (warm paper for dev tools sold on auditability), cyclorama (a pastel field on a 32s cycle), showroom (the product as the exhibit), blueprint (a drawing sheet, zero radius), prism (one iridescent wash over mono body), maquette (cream axonometric models on a dark table), scoreboard (warm paper, pixel numerals, a dark ledger of results), datasheet (an off-white spec sheet whose live instrument goes dark when it detects the reader is hiding), manpage (a developer landing page set in the reader's own system monospace on cream paper, with coral label chips that are real headings and a dark code frame as the argument), pigeonhole (a white sorting wall whose nine pastel categories are a filing scheme rather than a mood, each a two-layer chip whose label word is mandatory), roster (a white field in a faint grid of squares whose argument is other people's marks — client logotypes in pill-labelled industry columns, an engine's wordmark inside the headline — where the proof is a name rather than a number), ora (a warm coal field with cream ink and no third hue — the accent is the inverted field — a serif carrying every human sentence over a monospace carrying every machine fact, a terminal surface cut below the page, and a six-step verdict ramp, for products whose output is a machine's verdict about the reader), tenor (warm paper with zero radius and zero shadow, one hairline weight, an orange that exists only on hover and on focus, a sans tracked negative against a mono tracked positive, display at a line-height below one in an eight-to-twelve-character measure, and product proof delivered as silent looping video, for products arguing that a new kind of thing must be managed like an existing organisation), paperclip (neutral coal with no functional colour at all — every control monochrome and elevation made of hairlines, with the whole chromatic budget spent on a curtain of gradient capsules and twelve gradient section badges that cannot be clicked — for products that ask a person to run something that runs itself: agent teams, autonomous back-office, schedulers and budget-governed compute), ledger (warm cream paper ruled by a hairline at 12% ink and no shadow on any card, radius 15 nested concentrically, an ink primary button and a terracotta accent that never fills a control — it labels, as a 10px monospace uppercase kicker — with a seal on every card stating how its number is known, for the console of a product that answers questions about data: AI analysts, BI surfaces, query workspaces), router (a near-white field with a trace of blue and white cards standing on it, hairline seams instead of shadows anywhere, body at 14px and weight 450, and a status triplet in which the colour you paint with is not the colour you write with, for product consoles and the landing pages that have to look like them), daylight (a cool near-white portal field with generous radii and one very large soft shadow spent on a single object per screen, for client portals), notation (a near-white page drawn entirely in hairlines with a light serif over a monospace, no bold anywhere and an ink primary, for developer products sold on restraint), almanac (oatmeal paper with 2px seams and no 1px anywhere, a display set below a line-height of one, and mono tags notched through drawn boxes, for pages that assert a category), vitrine (a white hairline field with a serif display, an ink primary and one framed record carrying the evidence, for the front door of a product sold on trust). Ships the sheleg-design skill, the architecture reference, the motion doctrine, the Figma and Claude Design bridges, AI-surface patterns, style packs with ready-made token CSS, and the /sheleg-design command, and awning (a white forecourt where the accent is black and no hue reaches the chrome at all, a pill whose radius is a declared component token, one variable grotesque at 420/550 with no 700, and a single three-layer shadow, for commerce and platform front doors), and proscenium (a white field carrying two cool acts and one deep indigo act at the middle, an electric violet filling a control that stays nearly square at 4px against cards at 16, one family at nine weights, and a framed product panel the fold cuts off, for product-led marketing front doors whose argument is a demonstration), and bulletin (warm cream paper cut by flat pastel bands, every card and control a 1px ink outline standing on a hard zero-blur ink offset it travels into when pressed, a display face at 800 inside controls and 700 in the headline, and no tracking at any size, for front doors whose argument is breadth), and nameplate (a cool near-white slab under a page that is square on 87% of its elements, where the one round shape is reserved for a white 1px-bordered pill carrying somebody else's publication name as type, one family with the body at weight 500, and two uppercase registers tracked 0.06em and 0.175em, for pages whose argument is that named third parties will vouch for you: press placement, trust marks, certification and review aggregation), and rimlight (a white field with a cool grey act separator and one near-black act, a grotesque for every sentence and a monospace for every piece of chrome, square on 84% of its elements and tracked negative at every size, whose only elevation is a sixteen-layer coloured light rig — six layers lit and ten held at alpha 0, thrown from below-left — for a studio's own front door and the pages that sell what it makes), and onionskin (a white technical sheet at 96.5% zero radius — the squarest page in the library — where two bases do all the work and everything quiet is one of them at an alpha: text dims through the ink, structure dims through a navy that is never a word, over a dot grid with dashed hairlines and three faces at an 11px working size, for developer and AI infrastructure whose front page is a working document), and deskmate (a warm beige working day lit from one source above the top edge, where a single four-stop ramp — peach, lilac, violet, deep navy — washes the field, fills a panel and fills one word of a heading, everything a hand touches is a pill at 56px and everything else a 32px slab, elevation is a field step with two shadows on a 10,211px page, and the set piece is a framed transcript whose quoted chat client keeps its own face, ink and status colours under a --quoted-* namespace, for products sold as a colleague rather than a tool: AI employees and chat-native agents).",
5
- "version": "1.51.1",
5
+ "version": "1.52.0",
6
6
  "author": {
7
7
  "name": "ssheleg",
8
8
  "url": "https://x.com/sshlg93"
@@ -3,7 +3,7 @@ name: sheleg-design
3
3
  description: Use when deciding how something LOOKS or MOVES — cinematic landing pages and hero sections, particle/WebGL backgrounds, scroll-linked or scrubbed motion, layers that drift, dashboards, admin or internal tools, mobile screens, chat or agent interfaces, tokens, themes, palettes, typography and the Figma border. Triggers include "design a landing" / "дизайн лендинга", "build a landing page" / "сделай лендинг", "scroll animation" / "скролл-анимация", "dashboard style" / "стиль дашборда", "design tokens, style pack" / "дизайн-токены", "light/dark theme" / "светлая/тёмная тема", "figma variables" / "переменные фигмы, фигма в код", "mobile screen" / "мобильный экран", "palette, colors" / "палитра, цвета", "typography, font" / "типографика, шрифт", "how it looks, make it prettier" / "выглядит, красиво, красивее", "visual reference" / "визуальные референсы", "investor deck" / "презентация". Not for structure, copy or backend behavior.
4
4
  license: MIT
5
5
  metadata:
6
- version: 1.51.1
6
+ version: 1.52.0
7
7
  ---
8
8
 
9
9
  # SHELEG Design
@@ -154,8 +154,8 @@ as a definition of done, in that order:
154
154
  before writing CSS for a cinematic page. A scene has planes; everything on one
155
155
  plane is the failure no amount of easing repairs.
156
156
  - **Charts — hand the pack to `dataviz`** (same file), before drawing a chart in
157
- any pack. Token names are not uniform across the twenty-nine — only `--bg` and
158
- `--ink` resolve everywhere — and an undefined custom property does not error,
157
+ any pack. Token names are not uniform across the thirty-six packs — only `--bg`
158
+ and `--ink` resolve in every one — and an undefined custom property does not error,
159
159
  it silently falls back. Guessing one is the quietest way to ship a wrong chart.
160
160
  - **Mobile surfaces** ([`MOBILE_SURFACES.md`](./MOBILE_SURFACES.md)), when the
161
161
  brief is a native app screen or a mobile-web view — not a desktop page whose
@@ -60,10 +60,13 @@ the runnable palette validation; a pack is the *parameter set* it consumes.
60
60
  **Read the chosen pack's own token names before you write a single `var()`.**
61
61
  This table is by *role*, not by token, because the names are not uniform across
62
62
  the thirty-six packs: only `--bg` and `--ink` resolve in every pack. The accent is
63
- `--accent` in twenty-nine of the thirty-six packs, `--brand` in `field-notes` and `--cta` in `orchard` (each
64
- declares `@role accent:` in its token layer). **A second marker joins it in 1.26.0:**
63
+ `--accent` in thirty-three of the thirty-six packs; the other three name it themselves —
64
+ `--brand` in `babylove` and `field-notes`, `--cta` in `orchard` — and each declares
65
+ `@role accent:` in its token layer, which is the marker to read rather than the name.
66
+ (`roster` also ships a `--cta`, beside its `--accent`: that one is a component slot, not
67
+ the accent role, and the missing `@role accent:` marker is what says so.) **A second marker joins it in 1.26.0:**
65
68
  a semantic colour that sits below WCAG AA on its own field declares
66
- `@role non-text:` with the reason and the ratio, because nineteen of the token layers
69
+ `@role non-text:` with the reason and the ratio, because twenty-three of the token layers
67
70
  carry such a colour and each said so in a different phrasing — "never text on the field",
68
71
  "a FILL and large-text colour", "a fill, not a text colour", "No coral word under 24px",
69
72
  "category marks" — which is exactly why nothing could check it.
@@ -52,8 +52,9 @@ appears zero times in 364,905 bytes and exactly one element is fixed — so
52
52
  `MOTION_INTENSITY` above **4** has nothing legal to buy here.
53
53
 
54
54
  **Not for:** dense operator chrome. The elevation model is a field step and a 1.25px
55
- hairline, the smallest radius that ships is 8.4px, and every control is 56px tall — a
56
- fourth nested panel has nothing to sit on and a toolbar of pills is a novelty item. That
55
+ hairline, the smallest radius in the ramp is 8.4px, and a control is 56px tall or 44px
56
+ at the small size — a fourth nested panel has nothing to sit on and a toolbar of pills
57
+ is a novelty item. That
57
58
  is `workbench`'s half, and `router`'s. Not for regulated, clinical or public-sector
58
59
  pages, where a peach-to-violet bleed under the fold reads as a consumer app. Not for
59
60
  anything sold on austerity: this page spends 38 gradient fills, and a pack that spends
@@ -94,8 +95,10 @@ ramp that hangs is this one.
94
95
 
95
96
  ## Palette
96
97
 
97
- Copy [`tokens/deskmate.css`](./tokens/deskmate.css) verbatim. Every value there carries
98
- its provenance — MEASURED, SELECTED or DERIVED — and its ratio.
98
+ Copy [`tokens/deskmate.css`](./tokens/deskmate.css) verbatim. Every **colour** there
99
+ carries its provenance — MEASURED, SELECTED or DERIVED — and its ratio. The geometry
100
+ ramps (spacing, radii, type sizes) carry a provenance note per band rather than per
101
+ value, because a band was measured as a band.
99
102
 
100
103
  | Role | Token | Value | On `--bg` |
101
104
  |---|---|---|---|
@@ -109,6 +112,16 @@ its provenance — MEASURED, SELECTED or DERIVED — and its ratio.
109
112
  | Eyebrow | `--accent-deep` | `#4e32b5` | 7.93:1 |
110
113
  | Warm stop | `--peach` | `#ffbb98` | 1.51:1 — declared non-text, a stop and a star |
111
114
  | Last stop | `--dusk` | `#150079` | 14.83:1 — and the dusk surface's own field |
115
+ | On a card | `--on-surface` | `#1a182b` | 17.36:1 on `--surface`, and it does NOT invert |
116
+ | On a card, quiet | `--on-surface-soft` | `#716f7e` | 4.91:1 on `--surface` |
117
+
118
+ **`--ink` is for words on `--bg`; `--on-surface` is for words on a card.** On the light
119
+ field the two are the same value and the distinction reads as decoration. On the dusk
120
+ surface it is the whole difference: `--surface` stays white there — measured on
121
+ `/business`, where a card inside an inverted section keeps its white fill — so a card
122
+ that paints its heading in `--ink` paints **white on white**. Every component that sits
123
+ on a card takes the `--on-surface` pair, and the four that sit on the field
124
+ (the display, the lede, a stat's figure, a section's own heading) take `--ink`.
112
125
 
113
126
  **One accent, and it needed no correction.** It writes and it fills: `#6e47ff` is
114
127
  4.84:1 on the field `#faf5f1`, and `#ffffff` on `#6e47ff` is 5.24:1. A single token
@@ -174,8 +187,8 @@ is quoting a different library.
174
187
 
175
188
  **A measure in `ch`, and the reference has none.** It holds its lede in a grid column
176
189
  rather than in a measure — `max-width: none` on both the headline and the paragraph — so
177
- this pack states the measure the reference implies: **62ch** for a lede, **68ch** for
178
- body copy, measured against Gellix at 16px. Where the two faces are substituted the
190
+ this pack states the measure the reference implies and ships it as
191
+ `--measure-lede` (62ch) and `--measure-body` (68ch), against Gellix at 16px. Where the two faces are substituted the
179
192
  column holds and the character count moves, which is the point of stating it in `ch`.
180
193
 
181
194
  **Both brand faces are licensed and the substitutes are a pack decision.** Ulm Grotesk
@@ -191,8 +204,11 @@ library's own rule.
191
204
  **Elevation is a field step.** 68 white fills sit on a beige field with 51 hairlines
192
205
  between them, and the entire 10,211px page carries **two** shadows, which is why the
193
206
  pack ships exactly two shadow tokens and no ramp: `--shadow-panel` is the 5% whisper
194
- under the signature frame (`0 3.863px 17.769px`) and `--glow-bloom` is the one purple
195
- bloom on the page (`0 0 16px 8px` of purple-900 at 8%). A card is white on beige; a
207
+ under the signature frame (`0 3.863px 17.769px`), and `--glow-bloom` is the single purple
208
+ bloom (`0 0 16px 8px` of purple-900 at 8%) that the reference spends **once on the whole
209
+ page**. The pack does not prescribe where — the census records one instance and not which
210
+ element carried it — so the rule is the count: one bloom per page, on the object the page
211
+ is about, and if you cannot name that object do not spend it. A card is white on beige; a
196
212
  panel inside a card is beige on white; the third step is a hairline and there is no
197
213
  fourth.
198
214
 
@@ -253,9 +269,10 @@ grid:
253
269
  - The **message half** takes `--gradient-dusk`, white text, a byline row (a name, a small
254
270
  `APP` badge, a time), and under it an illustration of the artefact produced — a file
255
271
  tile, a set of connected tool marks, a checklist. Radius `--r-card` on the top corners.
256
- - The **explanation half** is `--surface`, a 24px heading in `--ink` and a 16px paragraph
257
- in `--ink-soft`, with 24px/32px/32px of padding. Radius `--r-card` on the bottom
258
- corners.
272
+ - The **explanation half** is `--surface`, a 24px heading in `--on-surface` and a 16px
273
+ paragraph in `--on-surface-soft`, with 24px/32px/32px of padding. Radius `--r-card` on
274
+ the bottom corners. Those two tokens rather than `--ink`: a card keeps its white fill
275
+ inside a dusk section, so the ink that sits on it cannot invert with the field.
259
276
  - No border and no shadow on either half; the two fills are the separation.
260
277
  - The small tile — a chip, a logo, a status row — is `--surface` at `--r-chip` with a
261
278
  `--rule-w` hairline at the ink's 5% and 10px of padding.
@@ -299,8 +316,8 @@ would be the liveliest thing on it.
299
316
  **Empty states.** The reference's is a measured refusal, not an absence: a search on
300
317
  `/integrations` matching nothing collapses the grid to 1280×0 at 1440 and says nothing at
301
318
  all — no message, no count, no reset. The pack's answer is a `--surface` card at
302
- `--r-card` holding a 16px/500 line in `--ink`, one sentence in `--ink-soft`, and a
303
- secondary pill that clears the filter. An empty result is a state, and a state that
319
+ `--r-card` holding a 16px/500 line in `--on-surface`, one sentence in
320
+ `--on-surface-soft`, and a secondary pill that clears the filter. An empty result is a state, and a state that
304
321
  paints nothing is indistinguishable from a broken query.
305
322
 
306
323
  Every interactive element takes `--tap-min` (44px) as a height floor. This is a
@@ -312,8 +329,10 @@ the navigation toggle at 48×32 and the transcript's reaction pills at 42×24.
312
329
  A marketing opening: full width, not full height, and it ends in the set piece rather
313
330
  than beginning with it.
314
331
 
315
- - The page holds `--page-max` (90rem) with `--gutter` stepping 20 → 40 → 56 → 80px, and
316
- the hero's own top padding is 176px at 1440 because the navigation slab hangs into it.
332
+ - The page holds `--page-max` (90rem) and its gutter steps through four tokens —
333
+ `--gutter` 20px, `--gutter-2` 40px, `--gutter-3` 56px, `--gutter-wide` 80px — because a
334
+ step a consumer cannot reach through a variable is a step the pack did not ship. The
335
+ hero's own top padding is 176px at 1440, because the navigation slab hangs into it.
317
336
  - Field `--bg` with `--gradient-dawn` blooming from above the headline, and the slab's
318
337
  bottom corners rounded at `--r-section` where the field bleeds into `--gradient-fold`.
319
338
  - A two-column grid at `1.15fr / 1fr` with a 56px gap: **the argument on the left, the
@@ -346,8 +365,10 @@ Three widths, measured: 1440, 768 and the sub-640 branch read at 500.
346
365
  collides its own ascenders at the awkward widths between the steps.
347
366
  - **The hero grid collapses to one column** and its gap goes 56px → 32px. The control pair
348
367
  goes full width at `--control-h`, which is how a 56px pill stays a 56px pill.
349
- - **Gutters step 80 → 56 → 40 → 20px** and the navigation slab's inset goes 40px → 12px,
350
- its height 65px → 57px. Section rhythm halves: 112px → 56px.
368
+ - **Gutters step 80 → 56 → 40 → 20px** through the four `--gutter*` tokens. The
369
+ navigation slab's own inset goes 40px → 12px and its height 65px → 57px — that inset is
370
+ the slab's and not the page's gutter, which is 80px at the same width. Section rhythm
371
+ halves: 112px → 56px.
351
372
  - **The frame's radius does not scale.** 47.319px at 1440 and at 500, with the same 15px
352
373
  inset — the concentric relationship is the object's identity and rescaling it makes the
353
374
  frame read as a different component.
@@ -437,7 +458,9 @@ cards made of the same material.
437
458
  a flicker.
438
459
  - **Focus-visible:** the control's own border takes `--focus-color` **and** the ring
439
460
  appears outside it. Both, always: the ring alone composites to `#b6a3ff` on white
440
- `#ffffff`, which is 2.17:1, and that is why the border moves in the same state.
461
+ `#ffffff`, which is 2.17:1, and that is why the border moves in the same state. The
462
+ ring ships as a **literal first** and derives from the accent inside an `@supports`
463
+ block — see Gotchas, because getting that order wrong removes the ring entirely.
441
464
  - **Disclosure row:** 62px tall at `--r-root`, transparent until focus, opening over
442
465
  `--dur-slow`. The row's chevron rotates; the row does not.
443
466
  - **Section reveal:** opacity and `--enter-y` on `--ease-out` over `--dur-enter`, with
@@ -519,3 +542,23 @@ nodes, `#1264a3` on `#e8f5fa` for a mention at 5.59:1, `#007c58` for an approval
519
542
  for a decline rule. They ship under `--quoted-*` so a status dot cannot reach them by
520
543
  accident. A page that lifts them into its own palette has adopted somebody else's design
521
544
  system one token at a time.
545
+
546
+ **A card does not invert with the field, and painting its text in `--ink` is the trap.**
547
+ `--surface` is white on both blocks — measured, because a chat-client card inside an
548
+ inverted section is still a white card — while `--ink` inverts to `#ffffff` on the dusk
549
+ surface. A card whose heading takes `--ink` therefore renders white on white inside a
550
+ dusk section — `#ffffff` on `#ffffff` is 1.00:1 — and no gate can see it: both tokens are
551
+ legal, both clear their own field, and the pairing only exists at render time. That is what `--on-surface` and
552
+ `--on-surface-soft` are for, and it is why they do not invert. Found by reading this
553
+ pack's own kit on a dusk section, which is the second defect that render caught after the
554
+ secondary control painting white on white — the same mechanism, one component along.
555
+
556
+ **The focus ring is declared literal-first, and the order is the guard.** A custom
557
+ property accepts almost any token sequence, so `--focus-ring: color-mix(…)` parses even
558
+ where `color-mix()` is unsupported, wins the cascade, and fails only at substitution — by
559
+ which point the literal is gone and `var(--focus-ring)` resolves to `unset`. An invisible
560
+ focus ring is the one degradation this pack may not ship, so the literal sits in `:root`
561
+ and the derived form sits in an `@supports (color: color-mix(in srgb, red, red))` block at
562
+ the end of the layer. `showroom` is the pack that learned this first; this one copies its
563
+ mechanism rather than its syntax, because the derived form here is a mix rather than a
564
+ relative colour.
@@ -58,6 +58,13 @@
58
58
  rules and it bars, and an icon that must be READ takes --ink-soft instead. */
59
59
  --ink-faint: #9693a3;
60
60
  --on-ink: #ffffff; /* 17.36:1 on --ink */
61
+ /* THE RULE THIS PACK CANNOT BE READ WITHOUT: --ink is for words on --bg, and
62
+ --on-surface is for words on --surface. On the light field the two are the same
63
+ value and the distinction looks decorative. On the dusk surface it is the whole
64
+ difference: --surface stays white there, measured, so a card that paints its text
65
+ in --ink paints white on white. Every component that sits on a card takes these. */
66
+ --on-surface: #1a182b; /* 17.36:1 on --surface */
67
+ --on-surface-soft: #716f7e; /* 4.91:1 on --surface */
61
68
 
62
69
  /* ---- The accent: it writes, it fills, and it needed no correction ---- */
63
70
  /* MEASURED. #6e47ff is the reference's purple-500 and its only functional accent:
@@ -121,9 +128,18 @@
121
128
  /* MEASURED — `ring-3` is 3px, and the ring's alpha IS its specification: 50% behind
122
129
  a control, 40% behind a field. Composited over white #ffffff the ring is
123
130
  #b6a3ff at 2.17:1, which is why the control's own border moves colour in the
124
- same state. */
125
- --focus-ring: 0 0 0 3px color-mix(in srgb, var(--accent) 50%, transparent);
126
- --focus-ring-field: 0 0 0 3px color-mix(in srgb, var(--accent) 40%, transparent);
131
+ same state.
132
+
133
+ THE LITERAL IS HERE AND THE DERIVED FORM IS IN THE `@supports` BLOCK AT THE END OF
134
+ THIS FILE, and the order is not a style preference. A custom property accepts
135
+ almost any token sequence, so `--focus-ring: color-mix(…)` parses even where
136
+ `color-mix()` is unsupported and wins the cascade; the invalidity surfaces only
137
+ where it is substituted, as invalid-at-computed-value-time, and by then the literal
138
+ is gone and `var(--focus-ring)` resolves to `unset` — an INVISIBLE FOCUS RING, on
139
+ the one degradation this pack says it may not ship. `showroom` is the worked
140
+ example this copies. */
141
+ --focus-ring: 0 0 0 3px rgba(110, 71, 255, 0.5);
142
+ --focus-ring-field: 0 0 0 3px rgba(110, 71, 255, 0.4);
127
143
 
128
144
  /* ---- Radii: a pill for the hand, a slab for the eye ----------------- */
129
145
  /* MEASURED at 1440: 977 of 1,191 visible elements compute 0px, 52 take the 32px
@@ -160,14 +176,20 @@
160
176
  --space-28: 112px; /* MEASURED — the section's own vertical rhythm */
161
177
 
162
178
  --page-max: 90rem; /* 1440px — MEASURED */
163
- --gutter: 20px; /* MEASURED — and it steps 40, 56, 80 outward */
164
- --gutter-wide: 80px; /* MEASURED at 1440 */
179
+ /* MEASURED, and the ramp ships as four tokens rather than as a sentence: the page's
180
+ gutter steps 20 → 40 → 56 → 80 across the reference's own breakpoints, and a step a
181
+ consumer cannot reach through a variable is a step the pack did not ship. */
182
+ --gutter: 20px;
183
+ --gutter-2: 40px; /* from 40rem */
184
+ --gutter-3: 56px; /* from 48rem */
185
+ --gutter-wide: 80px; /* from 64rem, and the value at 1440 */
165
186
  --control-h: 56px; /* MEASURED — the button and the field are the same height */
166
187
  --control-h-sm: 40px; /* MEASURED — the navigation's own pair, and see --tap-min */
167
188
  --control-px: 40px; /* MEASURED — 24px on the small size */
168
189
  /* The navigation slab is not a control and does not take a control's height:
169
- MEASURED at 65px with a 30px bottom radius at 1440, and 57px at the narrow
170
- width, inset 80px from each side of the page and 12px below 40rem. */
190
+ MEASURED at 65px with a 30px bottom radius at 1440 and 57px at the narrow width,
191
+ inset 40px from each side of the viewport and 12px below 40rem. That inset is the
192
+ SLAB's, and it is not the page's gutter — the gutter is 80px at the same width. */
171
193
  --slab-h: 65px;
172
194
  --slab-h-sm: 57px;
173
195
  --r-slab: 30px;
@@ -204,6 +226,13 @@
204
226
  --t-display-mid: 4.5rem; /* 72px from 40rem */
205
227
  --t-display-sm: 4rem; /* 64px below 40rem */
206
228
 
229
+ /* The reference holds its lede in a grid column and sets `max-width: none` on both
230
+ the headline and the paragraph, so these two are a PACK DECISION rather than a
231
+ measurement: the measure the reference implies, stated in `ch` so it survives the
232
+ face substitution below. */
233
+ --measure-lede: 62ch;
234
+ --measure-body: 68ch;
235
+
207
236
  --lh-display: 1.1; /* MEASURED — 88px on 80px */
208
237
  --lh-body: 1.4; /* MEASURED — 22.4px on 16px */
209
238
  --lh-ui: 1.5;
@@ -230,7 +259,12 @@
230
259
  --dur-fast: 0.15s; /* a colour, a border or an opacity */
231
260
  --dur-base: 0.2s; /* MEASURED — 23 elements */
232
261
  --dur-slow: 0.3s; /* MEASURED — 9 elements, a disclosure opening */
233
- --dur-enter: 0.64s; /* MEASURED — the reference's own enter-duration token */
262
+ /* MEASURED, and the two halves of the measurement disagree by 60ms: the reference
263
+ DECLARES `--enter-duration` as .6s, .64s and .68s across three sections, while the
264
+ transition on the 19 entering elements COMPUTES 0.7s. The pack ships the declared
265
+ middle value, because that is the number the reference's own author chose and the
266
+ 0.7s is one section's instance of it. */
267
+ --dur-enter: 0.64s;
234
268
  --stagger: 60ms; /* MEASURED — its enter-delay steps 0, 40, 60, 80, 120ms */
235
269
  --enter-y: 16px; /* MEASURED — the entrance travels 8px or 16px, never further */
236
270
 
@@ -294,6 +328,10 @@
294
328
  a muted icon here anyway, because its role is set by the light block. */
295
329
  --ink-faint: #9693a3;
296
330
  --on-ink: #150079;
331
+ /* A card on this surface stays WHITE — that is the measurement — so these two do NOT
332
+ invert with the field. They are the light block's values, and that is the point. */
333
+ --on-surface: #1a182b; /* 17.36:1 on --surface */
334
+ --on-surface-soft: #716f7e; /* 4.91:1 on --surface */
297
335
 
298
336
  --accent: #d2c6ff; /* 10.14:1 on --bg — SELECTED, the reference's own on-dusk eyebrow */
299
337
  --on-accent: #150079; /* 10.14:1 on --accent */
@@ -307,8 +345,12 @@
307
345
  --hairline-quiet: color-mix(in srgb, #ffffff 15%, var(--bg));
308
346
 
309
347
  --focus-color: #bcaaff; /* 7.90:1 on --bg, over the 3:1 mark floor */
310
- --focus-ring: 0 0 0 3px color-mix(in srgb, #8b6cff 50%, transparent);
311
- --focus-ring-field: 0 0 0 3px color-mix(in srgb, #8b6cff 40%, transparent);
348
+ /* MEASURED — the reference's on-dusk focus outline is its purple-400, and it is a
349
+ token here so the ring below derives from something rather than from a loose hex.
350
+ Literal first, derived in the `@supports` block, for the reason stated above. */
351
+ --focus-ring-base: #8b6cff;
352
+ --focus-ring: 0 0 0 3px rgba(139, 108, 255, 0.5);
353
+ --focus-ring-field: 0 0 0 3px rgba(139, 108, 255, 0.4);
312
354
  --gradient-word-fallback: #d2c6ff; /* 10.14:1 on --bg */
313
355
 
314
356
  /* DERIVED, and remapped rather than inherited: the light set measures 2.6-4.1:1 on
@@ -343,6 +385,22 @@
343
385
  }
344
386
  }
345
387
 
388
+ /* THE LITERALS ABOVE ARE THE FALLBACK, AND THEY ONLY WORK FROM IN HERE. The derived
389
+ form tracks the accent — change `--accent` and the ring follows — which is what a
390
+ literal cannot do; the literal exists so that a browser without `color-mix()` still
391
+ paints a ring at all. Both fields get one. */
392
+ @supports (color: color-mix(in srgb, red, red)) {
393
+ :root {
394
+ --focus-ring: 0 0 0 3px color-mix(in srgb, var(--accent) 50%, transparent);
395
+ --focus-ring-field: 0 0 0 3px color-mix(in srgb, var(--accent) 40%, transparent);
396
+ }
397
+
398
+ [data-surface="dusk"] {
399
+ --focus-ring: 0 0 0 3px color-mix(in srgb, var(--focus-ring-base) 50%, transparent);
400
+ --focus-ring-field: 0 0 0 3px color-mix(in srgb, var(--focus-ring-base) 40%, transparent);
401
+ }
402
+ }
403
+
346
404
  /* Body copy here is weight 500 on 146 nodes, and an unset `font-weight` gives 400 —
347
405
  one step light across the whole page. The UA's own 700 for `strong` and `b` is a
348
406
  step the other way and lands on the display's heading weight, so emphasis is pinned
@@ -108,8 +108,15 @@
108
108
  --shadow-hair: 0 1px 0 0 color-mix(in srgb, var(--ink) 6%, transparent); /* MEASURED */
109
109
  --shadow-lift: 0 32px 64px -24px color-mix(in srgb, var(--ink) 28%, transparent); /* MEASURED — once */
110
110
 
111
- --focus-ring: 0 0 0 4px color-mix(in srgb, var(--accent) 10%, transparent),
112
- 0 4px 10px -3px color-mix(in srgb, var(--accent) 45%, transparent); /* MEASURED */
111
+ /* MEASURED, and declared LITERAL FIRST with the derived form in the `@supports` block
112
+ at the end of this file. The order is the guard, not a style: a custom property
113
+ accepts almost any token sequence, so a `color-mix()` value parses even where
114
+ `color-mix()` is unsupported and wins the cascade; it fails only at substitution, by
115
+ which point the literal is gone and `var(--focus-ring)` resolves to `unset` — an
116
+ invisible focus ring. Added 1.52.0; `showroom` is the pack that established the
117
+ mechanism and `deskmate` carries the same guard. */
118
+ --focus-ring: 0 0 0 4px rgba(5, 98, 239, 0.1),
119
+ 0 4px 10px -3px rgba(5, 98, 239, 0.45);
113
120
  --focus-w: 2px;
114
121
  --focus-offset: 2px;
115
122
  --focus-color: var(--accent); /* 5.25:1 on --bg, over the 3:1 mark floor */
@@ -262,6 +269,16 @@
262
269
  }
263
270
  }
264
271
 
272
+ /* THE LITERALS ABOVE ARE THE FALLBACK, AND THEY ONLY WORK FROM IN HERE. The derived form
273
+ tracks `--accent`; the literal exists so a browser without `color-mix()` still paints a
274
+ ring at all. The dark band inherits this ring, and its lighter accent with it. */
275
+ @supports (color: color-mix(in srgb, red, red)) {
276
+ :root {
277
+ --focus-ring: 0 0 0 4px color-mix(in srgb, var(--accent) 10%, transparent),
278
+ 0 4px 10px -3px color-mix(in srgb, var(--accent) 45%, transparent);
279
+ }
280
+ }
281
+
265
282
  /* The pack bans bold, and a ban the UA overrides is not a ban: `strong` and `b` are 700
266
283
  by default in every browser. 500 is the heaviest weight this page spends at scale, so
267
284
  emphasis here is the medium weight — or, better, the mono. */