trykeyline 0.0.0-stage → 0.1.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 (96) hide show
  1. package/.claude-plugin/marketplace.json +18 -0
  2. package/LICENSE +37 -0
  3. package/README.md +117 -2
  4. package/bin/keyline.mjs +280 -0
  5. package/package.json +38 -4
  6. package/plugins/keyline/.claude-plugin/plugin.json +14 -0
  7. package/plugins/keyline/skills/keyline/SKILL.md +57 -0
  8. package/plugins/keyline/skills/keyline/commands/audit.md +25 -0
  9. package/plugins/keyline/skills/keyline/commands/color.md +7 -0
  10. package/plugins/keyline/skills/keyline/commands/critique.md +43 -0
  11. package/plugins/keyline/skills/keyline/commands/depth.md +7 -0
  12. package/plugins/keyline/skills/keyline/commands/init.md +15 -0
  13. package/plugins/keyline/skills/keyline/commands/layout.md +7 -0
  14. package/plugins/keyline/skills/keyline/commands/motion.md +7 -0
  15. package/plugins/keyline/skills/keyline/commands/patterns.md +7 -0
  16. package/plugins/keyline/skills/keyline/commands/polish.md +19 -0
  17. package/plugins/keyline/skills/keyline/commands/refine.md +29 -0
  18. package/plugins/keyline/skills/keyline/commands/sync.md +17 -0
  19. package/plugins/keyline/skills/keyline/commands/type.md +7 -0
  20. package/plugins/keyline/skills/keyline/domains/color/deriving-a-ramp.md +144 -0
  21. package/plugins/keyline/skills/keyline/domains/color/guide.md +243 -0
  22. package/plugins/keyline/skills/keyline/domains/color/status-and-exceptions.md +174 -0
  23. package/plugins/keyline/skills/keyline/domains/color/theming-mechanics.md +121 -0
  24. package/plugins/keyline/skills/keyline/domains/depth/elevation-recipes.md +191 -0
  25. package/plugins/keyline/skills/keyline/domains/depth/fallbacks-and-performance.md +132 -0
  26. package/plugins/keyline/skills/keyline/domains/depth/guide.md +255 -0
  27. package/plugins/keyline/skills/keyline/domains/depth/translucent-materials.md +159 -0
  28. package/plugins/keyline/skills/keyline/domains/layout/forks-gating-and-disclosures.md +201 -0
  29. package/plugins/keyline/skills/keyline/domains/layout/guide.md +284 -0
  30. package/plugins/keyline/skills/keyline/domains/layout/measure-and-rhythm.md +193 -0
  31. package/plugins/keyline/skills/keyline/domains/layout/radius-lines-and-layers.md +122 -0
  32. package/plugins/keyline/skills/keyline/domains/motion/guards.md +162 -0
  33. package/plugins/keyline/skills/keyline/domains/motion/guide.md +234 -0
  34. package/plugins/keyline/skills/keyline/domains/motion/ladder-and-curves.md +139 -0
  35. package/plugins/keyline/skills/keyline/domains/motion/patterns.md +196 -0
  36. package/plugins/keyline/skills/keyline/domains/motion/performance.md +121 -0
  37. package/plugins/keyline/skills/keyline/domains/patterns/actions-and-forms.md +204 -0
  38. package/plugins/keyline/skills/keyline/domains/patterns/agents-and-live-states.md +111 -0
  39. package/plugins/keyline/skills/keyline/domains/patterns/collections-and-dashboards.md +131 -0
  40. package/plugins/keyline/skills/keyline/domains/patterns/copy.md +123 -0
  41. package/plugins/keyline/skills/keyline/domains/patterns/guide.md +216 -0
  42. package/plugins/keyline/skills/keyline/domains/patterns/iteration.md +78 -0
  43. package/plugins/keyline/skills/keyline/domains/type/display-tier.md +108 -0
  44. package/plugins/keyline/skills/keyline/domains/type/guide.md +251 -0
  45. package/plugins/keyline/skills/keyline/domains/type/ladders.md +169 -0
  46. package/plugins/keyline/skills/keyline/domains/type/numerals-and-loading.md +107 -0
  47. package/plugins/keyline/skills/keyline/domains/type/prose-and-emphasis.md +125 -0
  48. package/plugins/keyline/skills/keyline/domains/type/type-audit.md +90 -0
  49. package/plugins/keyline/skills/keyline/glass/color/assets/color-tokens.css +319 -0
  50. package/plugins/keyline/skills/keyline/glass/color/assets/theme.ts +119 -0
  51. package/plugins/keyline/skills/keyline/glass/color/guide.md +222 -0
  52. package/plugins/keyline/skills/keyline/glass/color/intent-ramp.md +136 -0
  53. package/plugins/keyline/skills/keyline/glass/color/theming.md +112 -0
  54. package/plugins/keyline/skills/keyline/glass/depth/assets/GlassPanel.tsx +171 -0
  55. package/plugins/keyline/skills/keyline/glass/depth/assets/glass.css +322 -0
  56. package/plugins/keyline/skills/keyline/glass/depth/blur-and-tint.md +50 -0
  57. package/plugins/keyline/skills/keyline/glass/depth/depth-cues.md +87 -0
  58. package/plugins/keyline/skills/keyline/glass/depth/guide.md +216 -0
  59. package/plugins/keyline/skills/keyline/glass/depth/performance-and-fallbacks.md +37 -0
  60. package/plugins/keyline/skills/keyline/glass/layout/adaptive-presentations.md +231 -0
  61. package/plugins/keyline/skills/keyline/glass/layout/assets/SectionNav.tsx +244 -0
  62. package/plugins/keyline/skills/keyline/glass/layout/assets/layout-tokens.css +199 -0
  63. package/plugins/keyline/skills/keyline/glass/layout/guide.md +258 -0
  64. package/plugins/keyline/skills/keyline/glass/layout/measure-and-rhythm.md +134 -0
  65. package/plugins/keyline/skills/keyline/glass/layout/radius-and-elevation.md +102 -0
  66. package/plugins/keyline/skills/keyline/glass/motion/assets/DropMenu.tsx +106 -0
  67. package/plugins/keyline/skills/keyline/glass/motion/assets/motion-presets.ts +210 -0
  68. package/plugins/keyline/skills/keyline/glass/motion/assets/motion-tokens.css +176 -0
  69. package/plugins/keyline/skills/keyline/glass/motion/assets/useReveal.ts +65 -0
  70. package/plugins/keyline/skills/keyline/glass/motion/assets/useScrollTick.ts +183 -0
  71. package/plugins/keyline/skills/keyline/glass/motion/easing-and-duration.md +108 -0
  72. package/plugins/keyline/skills/keyline/glass/motion/guide.md +286 -0
  73. package/plugins/keyline/skills/keyline/glass/motion/reduced-motion.md +81 -0
  74. package/plugins/keyline/skills/keyline/glass/motion/scroll-performance.md +58 -0
  75. package/plugins/keyline/skills/keyline/glass/motion/shared-element.md +161 -0
  76. package/plugins/keyline/skills/keyline/glass/motion/sound.md +192 -0
  77. package/plugins/keyline/skills/keyline/glass/motion/springs.md +51 -0
  78. package/plugins/keyline/skills/keyline/glass/type/assets/prose.tsx +268 -0
  79. package/plugins/keyline/skills/keyline/glass/type/assets/type-tokens.css +414 -0
  80. package/plugins/keyline/skills/keyline/glass/type/guide.md +222 -0
  81. package/plugins/keyline/skills/keyline/glass/type/long-form-prose.md +151 -0
  82. package/plugins/keyline/skills/keyline/glass/type/scale.md +69 -0
  83. package/plugins/keyline/skills/keyline/glass/type/weight-and-tracking.md +61 -0
  84. package/plugins/keyline/skills/keyline/scripts/check/css.mjs +112 -0
  85. package/plugins/keyline/skills/keyline/scripts/check/design.mjs +67 -0
  86. package/plugins/keyline/skills/keyline/scripts/check/files.mjs +44 -0
  87. package/plugins/keyline/skills/keyline/scripts/check/markup.mjs +149 -0
  88. package/plugins/keyline/skills/keyline/scripts/check/rules.mjs +584 -0
  89. package/plugins/keyline/skills/keyline/scripts/check.mjs +294 -0
  90. package/plugins/keyline/skills/keyline/scripts/contrast.mjs +153 -0
  91. package/plugins/keyline/skills/keyline/scripts/scan.mjs +340 -0
  92. package/plugins/keyline/skills/keyline/system/design-md.md +136 -0
  93. package/plugins/keyline/skills/keyline/system/detection.md +53 -0
  94. package/plugins/keyline/skills/keyline/system/guide.md +101 -0
  95. package/plugins/keyline/skills/keyline/system/presets.md +58 -0
  96. package/plugins/keyline/skills/keyline/system/roles.md +150 -0
@@ -0,0 +1,322 @@
1
+ /* ─────────────────────────────────────────────────────────────────────────
2
+ Glass materials — three tiers, with every fallback.
3
+
4
+ PAIRS WITH glass/color/assets/color-tokens.css, which declares the
5
+ --glass-*-bg tints and their dark-theme values. This file holds the
6
+ material classes; that file holds the tints. Both are needed.
7
+
8
+ Why the tints live there and not here: they change per theme AND get
9
+ replaced wholesale by the reduced-transparency fallback, so they have to be
10
+ runtime :root custom properties rather than build-time tokens. (One of them
11
+ is also a gradient, which no colour-token system accepts.)
12
+
13
+ Three tiers, not four. The reference implementation also defines a
14
+ `glass-menu` (16px / 1.8 / 72%) for "a menu over a known surface" — and
15
+ nothing uses it. Every menu turned out to need glass-panel's defensive
16
+ tint, either because its wrapper animates or because it opens over other
17
+ glass. Dead CSS in a design system is worse than absent CSS: the next
18
+ person assumes it's load-bearing.
19
+ ───────────────────────────────────────────────────────────────────────── */
20
+
21
+ @layer components {
22
+ /* ── Opaque baselines ──────────────────────────────────────────────────
23
+ What a browser without backdrop-filter gets. Progressive enhancement in
24
+ the correct direction: the opaque version is the DEFAULT and the glass is
25
+ layered on top. Invert it and the non-supporting path never gets tested.
26
+
27
+ Support is good (Firefox unflagged since 103), so this is insurance
28
+ rather than a live concern — but it's cheap insurance. */
29
+ .glass-nav {
30
+ background: var(--color-gray-100);
31
+ }
32
+ .glass-panel {
33
+ background: var(--color-gray-100);
34
+ }
35
+ .folder-glass {
36
+ background: var(--color-background-100);
37
+ }
38
+
39
+ /* ── The materials ─────────────────────────────────────────────────────
40
+ Test BOTH conditions — Safari shipped only the prefixed version for
41
+ years, and Safari is where glass matters most.
42
+
43
+ Only the standard `backdrop-filter` is written below, on purpose. Let the
44
+ build add `-webkit-backdrop-filter` (Lightning CSS in Tailwind v4, or
45
+ Autoprefixer). Lightning CSS keeps only the LAST of a hand-written pair,
46
+ so writing both, standard first, ships the prefixed one alone — and
47
+ Firefox, which only reads the standard property, loses every blur. With
48
+ no build step, write the prefixed line first and the standard one last. */
49
+ @supports (backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px)) {
50
+ /* Fixed chrome that arbitrary page content scrolls under — a paragraph, a
51
+ photograph, a full-bleed image wall. 20px because the less you know
52
+ about the backdrop, the more of it has to be suppressed so the
53
+ surface's own icons can be read.
54
+
55
+ saturate() is NOT decoration. Blur is a convolution, averaging colours
56
+ moves them toward grey, so a blurred backdrop is ALWAYS desaturated —
57
+ more so at larger radii. Without the boost you get the cloudy
58
+ "glassmorphism" wash. This one line is the difference between
59
+ translucent and dirty, and it costs nothing: same filter pass. */
60
+ .glass-nav {
61
+ background: var(--glass-nav-bg);
62
+ backdrop-filter: blur(20px) saturate(1.8);
63
+ }
64
+
65
+ /* Near-opaque (97%) on purpose, and for three reasons at once:
66
+
67
+ 1. Its wrapper ANIMATES. A transform on an ancestor weakens
68
+ backdrop-filter in its subtree — the blur samples the wrong region,
69
+ or nothing, for the duration. So the TINT carries the legibility and
70
+ the blur is a bonus once it settles. General rule: when you can't
71
+ rely on the blur, thicken the tint.
72
+
73
+ 2. It opens over OTHER GLASS (the nav dock). Two surfaces at the same
74
+ tint read as one object — the panel would look like the dock had
75
+ grown taller. The heavy tint buys tier separation.
76
+
77
+ 3. At 97% you can actually COMPUTE text contrast against the tint. At
78
+ 72% the effective background is whatever happens to be behind it,
79
+ and the ratio is not computable.
80
+
81
+ Where you have the choice, fix it structurally instead: animate the
82
+ wrapper, put the glass on a static child. */
83
+ .glass-panel {
84
+ background: var(--glass-panel-bg);
85
+ backdrop-filter: blur(24px) saturate(1.8);
86
+ }
87
+
88
+ /* A frosted pocket with cards peeking out behind it. 8px because here you
89
+ WANT the backdrop recognised — blur is softening, not obscuring. And
90
+ 1.4 rather than 1.8 because those cards are the subject; pushing their
91
+ saturation would make the pocket louder than its own contents.
92
+
93
+ Its tint is a gradient (brighter at the top, where the light hits)
94
+ rather than a flat fill — the same idea as a 1px inset catch light, at
95
+ a scale where a 1px line would be lost. */
96
+ .folder-glass {
97
+ background: var(--folder-glass-bg);
98
+ backdrop-filter: blur(8px) saturate(1.4);
99
+ }
100
+ }
101
+
102
+ /* ── prefers-reduced-transparency ──────────────────────────────────────
103
+ Two reasons users set this: blur is expensive on low-powered devices,
104
+ and translucent surfaces are harder to read. One response serves both —
105
+ replace the material with an opaque one.
106
+
107
+ BOTH halves are mandatory and each is useless alone:
108
+ · filter only → a 72% tint over live content with no blur to suppress
109
+ it. WORSE than the glass was; text overlaps text.
110
+ · tint only → you still pay the whole cost of the blur, which was
111
+ half the reason for the preference.
112
+
113
+ The tint half lives in color-tokens.css (it re-declares the :root
114
+ variables and must come after both theme blocks). This is the other half.
115
+
116
+ Useful side effect: with a solid tint, text contrast becomes real and
117
+ computable — for exactly the users most likely to need it. */
118
+ @media (prefers-reduced-transparency: reduce) {
119
+ .glass-nav,
120
+ .glass-panel,
121
+ .folder-glass {
122
+ backdrop-filter: none;
123
+ }
124
+ }
125
+
126
+ /* ── prefers-contrast: more ────────────────────────────────────────────
127
+ A translucent surface leans on its hairline to define its edge, and an
128
+ 8%-alpha hairline is the first thing to disappear under a contrast
129
+ preference. Promote it to primary text colour.
130
+
131
+ Note what is NOT changed: the surface itself. High contrast is about
132
+ edges and text, not about removing the material. Composes correctly with
133
+ reduced-transparency if the user has both — solid surface, strong
134
+ border. */
135
+ @media (prefers-contrast: more) {
136
+ .glass-nav,
137
+ .glass-panel,
138
+ .folder-glass {
139
+ border-color: var(--color-gray-1000);
140
+ }
141
+ }
142
+ }
143
+
144
+ /* ─────────────────────────────────────────────────────────────────────────
145
+ REFERENCE — glass is only half of it. A blurred, tinted rectangle is a
146
+ PATCH. Three non-transparency cues turn it into an object, and every glass
147
+ surface needs all three. See glass/depth/depth-cues.md.
148
+
149
+ 1 · A HAIRLINE, from the TRANSLUCENT ramp:
150
+
151
+ border-[0.5px] border-gray-alpha-400
152
+
153
+ Not solid grey — a solid border doesn't participate in what's behind
154
+ it, so over a photograph it reads as a pasted-on frame. And because
155
+ the alpha ramp inverts to white-based in dark mode, the border is
156
+ automatically lighter than its surface there and darker here.
157
+
158
+ 0.5px renders as a true sub-pixel hairline on retina and rounds UP to
159
+ 1px elsewhere — it degrades rather than disappears.
160
+
161
+ 2 · A LAYERED SHADOW. Four layers, one light source above:
162
+
163
+ inset 0 1px 0 rgba(255,255,255,0.6) top-edge catch light
164
+ 0 1px 1px rgba(0,0,0,0.02) contact
165
+ 0 8px 16px -4px rgba(0,0,0,0.04) near ambient
166
+ 0 24px 32px -8px rgba(0,0,0,0.06) far ambient
167
+
168
+ The inset highlight contributes more than any of the shadows and is
169
+ the one most often omitted — remove it and the surface looks printed
170
+ on rather than raised. Negative spread contracts before blurring, so
171
+ the shadow stays tucked under instead of haloing out. Opacities are
172
+ 2–6%: if you can identify the shadow as a shadow, it's too strong.
173
+
174
+ Dark mode is a DIFFERENT shadow, not a re-tint — shadows ~10x stronger
175
+ (4% black on #0b0b0b is mathematically invisible), inset highlight ~6x
176
+ weaker (0.6 white on dark reads as a glowing edge). Because the
177
+ structure changes, this can't be one token — it's a per-theme pair,
178
+ and it belongs in the .dock-shadow class at the foot of this file
179
+ rather than repeated inline at every use.
180
+
181
+ 3 · CONTINUOUS CORNERS:
182
+
183
+ *, *::before, *::after { corner-shape: superellipse(1.5); }
184
+
185
+ And nest radii by: INNER = OUTER − the padding between them.
186
+ Dock rounded-2xl (16) p-1 (4) → button rounded-[12px] → avatar 10px
187
+
188
+ ── Performance, briefly ──────────────────────────────────────────────
189
+ Cost scales with AREA and re-runs whenever anything behind it changes —
190
+ including every frame of every scroll. Keep surfaces small, keep them few,
191
+ UNMOUNT closed menus (a hidden backdrop-filter element still pays its
192
+ filter pass; opacity: 0 does not save you), never animate blur(), and
193
+ never put glass over video or a flat background.
194
+ ───────────────────────────────────────────────────────────────────────── */
195
+
196
+ /* ─────────────────────────────────────────────────────────────────────────
197
+ The elevation stack, as a class.
198
+
199
+ Every floating surface takes this alongside its material class:
200
+
201
+ <div class="glass-nav dock-shadow rounded-2xl border-[0.5px] …">
202
+
203
+ It is NOT a design token, because it isn't a colour — no token system
204
+ takes a four-layer box-shadow (see §6 of the Glass colour guide (`glass/color/guide.md`)).
205
+ And it can't be one value, because dark mode changes the structure and
206
+ not just the numbers. A class is the only place it can live, and putting
207
+ it in one is what stops a ~400-character string being copied into five
208
+ components with a dark twin each.
209
+ ───────────────────────────────────────────────────────────────────────── */
210
+
211
+ @layer components {
212
+ .dock-shadow {
213
+ box-shadow:
214
+ inset 0 1px 0 rgba(255, 255, 255, 0.6), /* top-edge catch light */
215
+ 0 1px 1px rgba(0, 0, 0, 0.02), /* contact */
216
+ 0 8px 16px -4px rgba(0, 0, 0, 0.04), /* near ambient */
217
+ 0 24px 32px -8px rgba(0, 0, 0, 0.06); /* far ambient */
218
+ }
219
+
220
+ /* Shadows ~10x stronger; the inset highlight ~6x weaker. */
221
+ :root[data-theme="dark"] .dock-shadow {
222
+ box-shadow:
223
+ inset 0 1px 0 rgba(255, 255, 255, 0.1),
224
+ 0 1px 1px rgba(0, 0, 0, 0.3),
225
+ 0 8px 16px -4px rgba(0, 0, 0, 0.5),
226
+ 0 24px 32px -8px rgba(0, 0, 0, 0.6);
227
+ }
228
+ }
229
+
230
+ /* ─────────────────────────────────────────────────────────────────────────
231
+ A gradient 1px stroke — no wrapper element, no mask.
232
+
233
+ Two background layers on one box: the fill clipped to the padding edge,
234
+ the gradient clipped to the border edge, with a transparent border to
235
+ show it through.
236
+
237
+ background-origin: border-box IS THE LOAD-BEARING LINE. Without it the
238
+ gradient is SIZED to the padding box while being clipped to the border
239
+ box, so the stroke gets the wrong end of the ramp on all four sides — a
240
+ one-word omission that produces a result which looks almost right.
241
+
242
+ AND THE STOPS ARE IN PIXELS, NOT PERCENTAGES. Percentages are relative to
243
+ the box, so the ramp rescales whenever the box resizes: a 46px closed bar
244
+ ran the entire sweep inside its own height, came out louder than the
245
+ 250px panel it opened into, and shimmered through the open animation as
246
+ the ramp re-fitted frame by frame. Anchored in pixels the top of the
247
+ stroke holds still and the colour simply grows downward.
248
+
249
+ Colours come from the Glass colour guide's --sv-* family, which is
250
+ that system's one chromatic exception. Delete this block if you don't
251
+ have such an element.
252
+ ───────────────────────────────────────────────────────────────────────── */
253
+
254
+ @layer components {
255
+ .sv-panel {
256
+ border: 1px solid transparent;
257
+ background-image:
258
+ linear-gradient(var(--sv-fill-top), var(--sv-fill-bottom)),
259
+ linear-gradient(
260
+ 180deg,
261
+ var(--sv-edge-top) 0px,
262
+ var(--sv-edge-mid) 120px,
263
+ var(--sv-edge-bottom) 240px
264
+ );
265
+ background-origin: border-box;
266
+ background-clip: padding-box, border-box;
267
+
268
+ /* Four shadows, not one. A single centred blur reads as a drop shadow
269
+ that happens to be blue; the two HORIZONTAL casts are what make it
270
+ read as light coming off the panel. Same negative spread as the
271
+ elevation stack, so the glow stays tucked rather than haloing. */
272
+ box-shadow: var(--sv-glow);
273
+ transition: box-shadow 220ms var(--ease-geist);
274
+ }
275
+
276
+ /* Lifts on hover and STAYS lifted while open, so the colour reports the
277
+ same state the chevron does. */
278
+ .sv-panel:hover,
279
+ .sv-panel[data-open="true"] {
280
+ box-shadow: var(--sv-glow-lift);
281
+ }
282
+
283
+ @media (prefers-reduced-motion: reduce) {
284
+ .sv-panel {
285
+ transition: none;
286
+ }
287
+ }
288
+
289
+ /* One pass of light across the label as it opens — never a loop. A
290
+ repeating shimmer claims the thing is still working, which it isn't.
291
+ Drop the class on animationend so the label goes back to painted text:
292
+ background-clip: text with color: transparent is a text-selection and
293
+ forced-colours hazard to leave in place. */
294
+ .sv-sheen {
295
+ background-image: linear-gradient(
296
+ 100deg,
297
+ var(--sv-accent) 40%,
298
+ var(--sv-sheen-hi) 50%,
299
+ var(--sv-accent) 60%
300
+ );
301
+ background-size: 250% 100%;
302
+ -webkit-background-clip: text;
303
+ background-clip: text;
304
+ color: transparent;
305
+ animation: svSheen 700ms var(--ease-geist) both;
306
+ }
307
+ @keyframes svSheen {
308
+ from {
309
+ background-position: 140% 0;
310
+ }
311
+ to {
312
+ background-position: -40% 0;
313
+ }
314
+ }
315
+ @media (prefers-reduced-motion: reduce) {
316
+ .sv-sheen {
317
+ animation: none;
318
+ background-image: none;
319
+ color: var(--sv-accent);
320
+ }
321
+ }
322
+ }
@@ -0,0 +1,50 @@
1
+ # Blur, saturation, and tint
2
+
3
+ Glass's values for depth laws 7–10; why blur greys, what the tint does and how to composite text are in `domains/depth/translucent-materials.md`.
4
+
5
+ ## Saturation
6
+
7
+ | Saturate | When |
8
+ |---|---|
9
+ | 1.4 | Only *softening* a backdrop you want recognised |
10
+ | 1.8 | *Suppressing* a backdrop (the default) |
11
+ | >2.0 | Almost never — colours look radioactive |
12
+ | 1.0 | Never; this is just omitting it |
13
+
14
+ The reference implementation uses 1.4 on the folder pocket, deliberately: the cards behind it are the subject, and pushing their saturation would make the pocket louder than its contents. The three chrome surfaces use 1.8. Roughly, the more you blur, the more saturation you need: 8px / 1.4 and 20px / 1.8 both come out "colour preserved".
15
+
16
+ ## The tint values
17
+
18
+ ```css
19
+ :root {
20
+ --glass-nav-bg: rgba(242, 242, 242, 0.72);
21
+ --glass-panel-bg: rgba(243, 243, 243, 0.97);
22
+ --folder-glass-bg: linear-gradient(to bottom, rgba(255,255,255,0.55), rgba(255,255,255,0.34));
23
+ }
24
+
25
+ :root[data-theme="dark"] {
26
+ --glass-nav-bg: rgba(26, 26, 26, 0.72);
27
+ --glass-panel-bg: rgba(22, 22, 22, 0.97);
28
+ --folder-glass-bg: linear-gradient(to bottom, rgba(48,48,48,0.5), rgba(30,30,30,0.34));
29
+ }
30
+ ```
31
+
32
+ The three tint rules in `domains/depth/translucent-materials.md`, at these values:
33
+
34
+ - **The nav tint is `#f2f2f2`, not white**, matching `gray-100`, the "raised surface" token.
35
+ - **The dark tints are lighter than the dark page**: `#1a1a1a` and `#161616` against `#0b0b0b`.
36
+ - **The pocket is a gradient**, brighter at the top, and **its dark gradient is grey-based**, not white-based. Its inset highlight is also brighter than the chrome's (`rgba(255,255,255,0.8)` vs `0.6`): a bigger, more decorative surface gets more pronounced light modelling.
37
+
38
+ They live in `:root`, not `@theme`, because the reduced-transparency fallback replaces them and one is a gradient (colour law 10; `glass/color/guide.md`).
39
+
40
+ ## The 97% panel, in full
41
+
42
+ The stated reason in the reference implementation:
43
+
44
+ > Its own animating transform weakens `backdrop-filter`, so the tint carries the opacity rather than the blur.
45
+
46
+ Two more reasons. **The settings box opens directly above the nav dock**, glass over glass: at 72% each it would look like the dock had grown taller, so the heavy tint buys tier separation as well as legibility (depth law 11). And at 97% the backdrop contributes ~3%, so you can *compute* text contrast against the tint, which you cannot at 72%.
47
+
48
+ ## Text over glass, worked
49
+
50
+ A 72% `#f2f2f2` tint composites to about `#f5f5f5` over a white page and around `#b8b8b8` over a dark photograph. Text at `gray-800` (`#7d7d7d`) clears AA on the first and fails on the second, and the same pixel does both as the page scrolls — hence §7 of the guide. The reduced-transparency fallback helps as a side effect, swapping in a solid token so contrast is real for the users most likely to need it.
@@ -0,0 +1,87 @@
1
+ # Depth cues
2
+
3
+ Glass's edges, shadows and tiers, for depth laws 1–5; the bundle and the shadow derivation in general are in `domains/depth/elevation-recipes.md`. Corners and the nesting rule are layout's: `glass/layout/radius-and-elevation.md`.
4
+
5
+ ## 1. The hairline
6
+
7
+ Every glass surface here pairs blur with a border:
8
+
9
+ ```tsx
10
+ className="glass-nav rounded-2xl border-[0.5px] border-gray-alpha-400 p-1"
11
+ ```
12
+
13
+ **Why the alpha ramp.** Because it inverts to white-based in dark mode (the Glass colour guide, `glass/color/guide.md`), the border is automatically lighter than its surface in dark mode and darker in light — "one step more contrast than the surface" in both directions.
14
+
15
+ **Why 0.5px.** On a small floating dock, 1px reads as heavy; 0.5px reads as an edge.
16
+
17
+ **The exception.** The decorative folder pocket uses a solid `border-white/60` with `dark:border-white/[0.08]`. A literal white works because it's a *decorative* material over content it controls, and the bright edge is part of the effect. The dark value drops to `0.08` from `0.6` because on a dark surface a bright edge reads as a glowing line, not a catch light.
18
+
19
+ Vertical dividers inside the dock are layout's (`glass/layout/radius-and-elevation.md`).
20
+
21
+ ## 2. The shadow stack
22
+
23
+ `.dock-shadow` is in §5 of the guide; depth law 4 gives each layer's job. In dark mode:
24
+
25
+ ```
26
+ inset 0 1px 0 rgba(255,255,255,0.10), /* highlight: 0.6 → 0.10 */
27
+ 0 1px 1px rgba(0,0,0,0.3), /* shadows: 0.02 → 0.3 */
28
+ 0 8px 16px -4px rgba(0,0,0,0.5), /* 0.04 → 0.5 */
29
+ 0 24px 32px -8px rgba(0,0,0,0.6) /* 0.06 → 0.6 */
30
+ ```
31
+
32
+ A 4%-black shadow on a `#0b0b0b` surface is invisible; dark mode needs 30–60% black before a shadow registers. It's also why the dark page is `#0b0b0b`, not `#000000` (depth law 5).
33
+
34
+ Because the structure changes and not just the values, elevation **cannot be a single token**. It needs a per-theme pair — the one place raw `rgba()` outside the token file is acceptable — and it belongs in a **CSS class**, not a JS constant:
35
+
36
+ ```tsx
37
+ <div className="glass-nav dock-shadow rounded-2xl border-[0.5px] border-gray-alpha-400 p-1">
38
+ ```
39
+
40
+ The reference implementation started with an exported constant and moved to the class:
41
+
42
+ - **It's ~400 characters of arbitrary-value syntax either way**, but in a class it's four readable lines you can compare against the four in the dark block.
43
+ - **A shared constant is still imported and interpolated at every site**, so a `grep` for the shadow finds five hits rather than one definition.
44
+ - **The escaping is a hazard** (`domains/depth/elevation-recipes.md`, one definition per level).
45
+
46
+ ## 3. Elevation tiers
47
+
48
+ **More layers as the element floats higher and persists longer** (depth law 4).
49
+
50
+ | Tier | Recipe | Radius | For |
51
+ |---|---|---|---|
52
+ | **Chrome** | 4 layers (inset + contact + 2 ambient) | 16px | Fixed docks, floating menus, sheets |
53
+ | **Pocket** | `inset 0 1px 0 rgba(255,255,255,0.8)` + `0 10px 28px -12px rgba(0,0,0,0.22)` | 16px | A large decorative material |
54
+ | **Panel** | `0 8px 24px -8px rgba(0,0,0,0.12)` / dark `0.6` | 12px | Hover cards |
55
+ | **Card** | `0 4px 12px -4px rgba(0,0,0,0.22)` + `ring-1 ring-black/[0.06]` / dark `ring-white/[0.08]` | 9px | Cards in a stack |
56
+ | **Transient** | `0 4px 10px -4px rgba(0,0,0,0.3)` | 6px | Tooltips |
57
+
58
+ **A tooltip's shadow is 30% black in *both* themes.** It sits on `bg-gray-1000`, near-black in light mode and near-white in dark, so the surface sets its requirement (depth law 4).
59
+
60
+ **The stacked card uses a `ring`, not a `border`.** A ring doesn't affect layout, so it can't shift a card positioned by transform mid-animation. `ring-black/[0.06] dark:ring-white/[0.08]` carries the alpha ramp's information in a utility that doesn't take a token.
61
+
62
+ ### A confirmation takes an existing tier
63
+
64
+ A destructive confirmation takes the panel the rest of the system floats (depth law 1), in the pattern patterns law 4 sets out. Its focus returns to the opener, held on open:
65
+
66
+ ```tsx
67
+ const opener = useRef<HTMLElement | null>(null);
68
+ const open = () => { opener.current = document.activeElement as HTMLElement; setOpen(true); };
69
+ const close = () => { setOpen(false); requestAnimationFrame(() => opener.current?.focus()); };
70
+ ```
71
+
72
+ ## 4. Stacking inside a glass surface
73
+
74
+ A sliding highlight must sit **behind** the icons but **in front of** the surface's own tint:
75
+
76
+ ```tsx
77
+ <ul className="glass-nav relative isolate flex items-center …">
78
+ <m.div className="pointer-events-none absolute left-0 top-0 -z-10 rounded-[12px] bg-highlight" />
79
+ {/* icons, in normal flow, above the bar */}
80
+ </ul>
81
+ ```
82
+
83
+ `-z-10` alone would drop the bar behind the container, tint included; `isolate` scopes it, so the bar goes behind the icons but not behind the glass (layout law 7).
84
+
85
+ `bg-highlight` is a token, white in light mode and `#2c2c2c` in dark: an elevated surface within an elevated surface, lighter than the glass in light mode and lighter again in dark, so it can't be a fixed colour.
86
+
87
+ The enclosing fixed strip carries **no transform**: it would weaken the `backdrop-filter` in its subtree *and* trap the mobile sheet's full-bleed overlay (depth law 9).
@@ -0,0 +1,216 @@
1
+ # Glass and Depth
2
+
3
+ > **Glass preset.** This is the glass material running on [modimihir.com](https://modimihir.com), with its real values, meant to be copied whole. Read it beside `domains/depth/guide.md`, which holds the laws; this guide holds glass's values, recipes and decisions for them, and says where glass differs. Under any other preset, `domains/depth/guide.md` leads and this guide is only a worked example.
4
+
5
+ Glass is the preset built around translucency, so it spends depth law 6's ceiling: the material in four places, all floating chrome — the nav dock, the settings drop-up, the mobile sheet and one decorative pocket. Everything else is opaque. The material is the five parts in `domains/depth/translucent-materials.md` plus continuous corners (`glass/layout/guide.md`, §4).
6
+
7
+ ## 1. Three tiers
8
+
9
+ Depth law 7, at glass's values:
10
+
11
+ | Tier | Blur | Saturate | Tint | Backdrop | Used for |
12
+ |---|---|---|---|---|---|
13
+ | `folder-glass` | **8px** | 1.4 | gradient, 55%→34% | Content you *want* recognised | A frosted pocket, cards peeking behind |
14
+ | `glass-nav` | **20px** | 1.8 | 72% | Arbitrary page content | Fixed chrome that content scrolls under |
15
+ | `glass-panel` | **24px** | 1.8 | **97%** | Anything, including other glass | A floating menu that must stay legible |
16
+
17
+ The pocket's cards should stay recognisable: 8px softens rather than obscures, and 1.4 saturation keeps the pocket quieter than its contents. Paragraphs, photographs and image walls scroll under fixed chrome; 20px leaves less to compete with its icons. The panel, least in control of its backdrop, gets the most defensive 24px.
18
+
19
+ > **A finding from the reference implementation:** a fourth material, `glass-menu` (16px / 1.8 / 72%), for "a menu over a surface you already know", is dead CSS: every menu needed `glass-panel`'s defensive tint, because its wrapper animates or it opens over other glass. Copy three tiers (depth law 1).
20
+
21
+ ## 2. Saturation: 1.8 suppressing, 1.4 softening
22
+
23
+ Depth law 8:
24
+
25
+ ```css
26
+ backdrop-filter: blur(20px) saturate(1.8);
27
+ ```
28
+
29
+ **1.8 where you're suppressing the backdrop, 1.4 where you're only softening it.**
30
+
31
+ ## 3. The 97% panel
32
+
33
+ `glass-panel` pairs a 24px blur with **97% opacity** because its menus' wrappers *scale open*, and a transform on an ancestor weakens `backdrop-filter` beneath it (depth law 9): for the whole animation the tint does the work. Where you can, the animating wrapper and the glass surface are different elements:
34
+
35
+ ```tsx
36
+ <DropMenu open={open}> {/* animates scale + y */}
37
+ <ul className="glass-panel …"> {/* carries the blur, never transformed */}
38
+ ```
39
+
40
+ ## 4. Glass over glass
41
+
42
+ Depth law 11, on the site: the settings box (`glass-panel`) opens right above the dock (`glass-nav`). At 72% the drop-up would look like the dock growing taller; at 97% it's a separate panel.
43
+
44
+ ## 5. Edge and shadow
45
+
46
+ ### The hairline
47
+
48
+ ```tsx
49
+ className="glass-nav rounded-2xl border-[0.5px] border-gray-alpha-400 p-1"
50
+ ```
51
+
52
+ Depth law 3's translucent edge is the alpha ramp (`glass/color/guide.md`): `gray-alpha-400` sits *on* whatever's there, white page or photograph, where a solid `gray-400` reads as a pasted-on frame.
53
+
54
+ ### The shadow stack
55
+
56
+ Four layers, one light source above (depth law 4), in **one class**, not inline:
57
+
58
+ ```css
59
+ .dock-shadow {
60
+ box-shadow:
61
+ inset 0 1px 0 rgba(255, 255, 255, 0.6), /* top-edge catch light */
62
+ 0 1px 1px rgba(0, 0, 0, 0.02), /* contact */
63
+ 0 8px 16px -4px rgba(0, 0, 0, 0.04), /* near ambient */
64
+ 0 24px 32px -8px rgba(0, 0, 0, 0.06); /* far ambient */
65
+ }
66
+ :root[data-theme="dark"] .dock-shadow {
67
+ box-shadow:
68
+ inset 0 1px 0 rgba(255, 255, 255, 0.1),
69
+ 0 1px 1px rgba(0, 0, 0, 0.3),
70
+ 0 8px 16px -4px rgba(0, 0, 0, 0.5),
71
+ 0 24px 32px -8px rgba(0, 0, 0, 0.6);
72
+ }
73
+ ```
74
+
75
+ Opacities are 2–6% in light. Dark is a different shadow, not a re-tint (depth law 5): shadows ~10× stronger (4% black on `#0b0b0b` is invisible), highlight ~6× weaker (0.6 white on dark is a glowing edge, not a catch light).
76
+
77
+ **Extracted the moment it appeared twice.** This ~400-character string was once inline in five places here, each with a dark twin. Not being a colour, it *can't* be a design token (colour law 10), so a class is its only home.
78
+
79
+ → Every elevation tier, the pocket's edge, and stacking inside a glass surface: `glass/depth/depth-cues.md`
80
+
81
+ ## 6. A gradient edge, without a wrapper or a mask
82
+
83
+ Depth law 13, on the one chromatic panel, whose edge runs deep navy to pale blue:
84
+
85
+ ```css
86
+ .sv-panel {
87
+ border: 1px solid transparent;
88
+ background-image:
89
+ linear-gradient(var(--sv-fill-top), var(--sv-fill-bottom)), /* the fill */
90
+ linear-gradient(180deg, /* the stroke */
91
+ var(--sv-edge-top) 0px,
92
+ var(--sv-edge-mid) 120px,
93
+ var(--sv-edge-bottom) 240px);
94
+ background-origin: border-box;
95
+ background-clip: padding-box, border-box;
96
+ }
97
+ ```
98
+
99
+ **The stops are in pixels** because the panel opens from a 46px bar to 250px. With percentages it ran the whole navy-to-pale sweep inside 46px, *louder* than the panel it became, and shimmered as the ramp re-fitted each frame; at `0px / 120px / 240px` the top holds still and the colour grows downward.
100
+
101
+ ### The glow
102
+
103
+ Depth law 14:
104
+
105
+ ```css
106
+ box-shadow:
107
+ 0 1px 2px rgba(22, 39, 107, 0.05), /* contact */
108
+ -10px 0 20px -18px rgba(47, 90, 196, 0.38), /* left */
109
+ 10px 0 20px -18px rgba(47, 90, 196, 0.38), /* right */
110
+ 0 10px 24px -16px rgba(47, 90, 196, 0.3); /* below */
111
+ ```
112
+
113
+ It lifts on hover **and stays lifted while open** (`.sv-panel:hover, .sv-panel[data-open="true"]`), so the colour reports the chevron's state. Transition at 220ms, off under `prefers-reduced-motion`.
114
+
115
+ ### The sheen
116
+
117
+ A label shimmers once as the panel opens:
118
+
119
+ ```css
120
+ .sv-sheen {
121
+ background-image: linear-gradient(100deg,
122
+ var(--sv-accent) 40%, var(--sv-sheen-hi) 50%, var(--sv-accent) 60%);
123
+ background-size: 250% 100%;
124
+ background-clip: text;
125
+ color: transparent;
126
+ animation: svSheen 700ms var(--ease-geist) both;
127
+ }
128
+ ```
129
+
130
+ The class drops on `animationend`; under reduced motion, plain accent colour.
131
+
132
+ ## 7. Text over glass
133
+
134
+ **Contrast on a translucent surface can't be computed**: the effective background is whatever is behind it. Glass's answers to depth law 10, in order:
135
+
136
+ 1. **Raise the tint until it's effectively opaque behind text.** Hence `glass-panel` at 97%, where contrast against the tint *is* computable.
137
+ 2. **Only high-contrast text on glass:** icons at `quiet`→`gray-1000` (not `gray-600`, 2.38:1 on white) and 13–14px rows at `gray-900`/`gray-1000`. Nothing muted, nothing small.
138
+ 3. **No reading copy on glass.** No paragraph on the site sits on a translucent surface.
139
+
140
+ → The tint values and a worked composite: `glass/depth/blur-and-tint.md`
141
+
142
+ ## 8. Fallbacks
143
+
144
+ Depth law 15, at glass's tokens.
145
+
146
+ ### `prefers-reduced-transparency`
147
+
148
+ ```css
149
+ /* AFTER both theme blocks — it re-declares the same properties. */
150
+ @media (prefers-reduced-transparency: reduce) {
151
+ :root, :root[data-theme="dark"] {
152
+ --glass-nav-bg: var(--color-gray-100);
153
+ --glass-panel-bg: var(--color-gray-100);
154
+ --folder-glass-bg: var(--color-background-100);
155
+ }
156
+ .glass-nav, .glass-panel, .folder-glass {
157
+ backdrop-filter: none;
158
+ }
159
+ }
160
+ ```
161
+
162
+ ### `prefers-contrast: more`
163
+
164
+ ```css
165
+ @media (prefers-contrast: more) {
166
+ .glass-nav, .glass-panel, .folder-glass { border-color: var(--color-gray-1000); }
167
+ }
168
+ ```
169
+
170
+ ### `@supports` — the gap in the reference implementation
171
+
172
+ The reference implementation has **no `@supports` guard**, so without `backdrop-filter` it gets a bare 72% tint: legible, but weak. `glass/depth/assets/glass.css` ships with the guard:
173
+
174
+ ```css
175
+ .glass-nav { background: var(--color-gray-100); } /* opaque baseline */
176
+
177
+ @supports (backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px)) {
178
+ .glass-nav {
179
+ background: var(--glass-nav-bg);
180
+ backdrop-filter: blur(20px) saturate(1.8);
181
+ }
182
+ }
183
+ ```
184
+
185
+ **Don't hand-write `-webkit-backdrop-filter`.** Tailwind v4's Lightning CSS adds it and keeps only the last of a hand-written pair, so the site ships the prefix alone and Firefox loses every blur (`glass/depth/performance-and-fallbacks.md`).
186
+
187
+ ## 9. Performance
188
+
189
+ Depth law 16, within glass's budget: three or four surfaces at most, each small — a dock a few hundred pixels wide, not a full-viewport overlay — and closed menus unmount.
190
+
191
+ → What keeps it affordable here, and the prefix bug in detail: `glass/depth/performance-and-fallbacks.md`
192
+
193
+ ## 10. Confirmations
194
+
195
+ A destructive confirmation takes `glass-panel` with the same hairline and shadow as every other floating panel (depth law 1; patterns law 4). The action carries the red (`glass/color/guide.md`). → The focus-return code: `glass/depth/depth-cues.md`
196
+
197
+ ## Assets
198
+
199
+ - `glass/depth/assets/glass.css` — the three materials, all fallbacks, the missing `@supports` guard, `.dock-shadow`, the gradient-stroke panel. Pairs with `glass/color/assets/color-tokens.css` (tints, `--sv-*` stops).
200
+ - `glass/depth/assets/GlassPanel.tsx` — wrapper/surface split, hairline and stacking, as a component.
201
+
202
+ ## Checklist
203
+
204
+ The domain checklist applies in full; these are glass's values for it.
205
+
206
+ - [ ] Three tiers only: `folder-glass` 8px / 1.4, `glass-nav` 20px / 1.8 / 72%, `glass-panel` 24px / 1.8 / 97%.
207
+ - [ ] Glass in the four places only: dock, settings drop-up, mobile sheet, pocket.
208
+ - [ ] Anything that animates or opens over glass is `glass-panel`, its wrapper apart from the surface.
209
+ - [ ] Edges `border-[0.5px] border-gray-alpha-400`; the pocket alone takes `border-white/60`.
210
+ - [ ] `.dock-shadow` as one class with its dark twin beside it.
211
+ - [ ] Gradient strokes stopped at `0px / 120px / 240px`; the glow holds while open; the sheen runs once.
212
+ - [ ] Icons at `quiet`→`gray-1000` and rows at `gray-900`/`gray-1000` on glass; no reading copy.
213
+ - [ ] Reduced transparency swaps the tints to `gray-100` / `background-100` and drops the filter, after both theme blocks.
214
+ - [ ] `prefers-contrast: more` turns the edge `gray-1000`.
215
+ - [ ] `@supports` guard with the `gray-100` baseline; source has only `backdrop-filter`, compiled CSS both.
216
+ - [ ] Destructive confirmations take `glass-panel`; focus returns to the opener.