trykeyline 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/bin/keyline.mjs +2 -1
- package/package.json +1 -1
- package/plugins/keyline/.claude-plugin/plugin.json +1 -1
- package/plugins/keyline/plugin.json +1 -1
- package/plugins/keyline/skills/keyline/domains/motion/guide.md +1 -0
- package/plugins/keyline/skills/keyline/domains/patterns/guide.md +1 -1
- package/plugins/keyline/skills/keyline/domains/type/guide.md +1 -1
- package/plugins/keyline/skills/keyline/domains/type/ladders.md +1 -1
- package/plugins/keyline/skills/keyline/glass/color/assets/color-tokens.css +34 -145
- package/plugins/keyline/skills/keyline/glass/color/assets/theme.ts +10 -70
- package/plugins/keyline/skills/keyline/glass/color/guide.md +1 -1
- package/plugins/keyline/skills/keyline/glass/color/intent-ramp.md +2 -34
- package/plugins/keyline/skills/keyline/glass/color/theming.md +4 -65
- package/plugins/keyline/skills/keyline/glass/depth/assets/GlassPanel.tsx +14 -121
- package/plugins/keyline/skills/keyline/glass/depth/assets/glass.css +29 -188
- package/plugins/keyline/skills/keyline/glass/depth/depth-cues.md +1 -16
- package/plugins/keyline/skills/keyline/glass/depth/performance-and-fallbacks.md +3 -11
- package/plugins/keyline/skills/keyline/glass/layout/adaptive-presentations.md +5 -91
- package/plugins/keyline/skills/keyline/glass/layout/assets/SectionNav.tsx +3 -6
- package/plugins/keyline/skills/keyline/glass/layout/assets/layout-tokens.css +11 -140
- package/plugins/keyline/skills/keyline/glass/layout/guide.md +2 -8
- package/plugins/keyline/skills/keyline/glass/layout/measure-and-rhythm.md +0 -2
- package/plugins/keyline/skills/keyline/glass/layout/radius-and-elevation.md +1 -1
- package/plugins/keyline/skills/keyline/glass/motion/assets/DropMenu.tsx +4 -6
- package/plugins/keyline/skills/keyline/glass/motion/assets/motion-presets.ts +10 -184
- package/plugins/keyline/skills/keyline/glass/motion/assets/motion-tokens.css +21 -105
- package/plugins/keyline/skills/keyline/glass/motion/easing-and-duration.md +2 -31
- package/plugins/keyline/skills/keyline/glass/motion/guide.md +3 -3
- package/plugins/keyline/skills/keyline/glass/motion/reduced-motion.md +1 -7
- package/plugins/keyline/skills/keyline/glass/motion/scroll-performance.md +3 -32
- package/plugins/keyline/skills/keyline/glass/motion/sound.md +10 -83
- package/plugins/keyline/skills/keyline/glass/type/assets/prose.tsx +8 -43
- package/plugins/keyline/skills/keyline/glass/type/assets/type-tokens.css +46 -85
- package/plugins/keyline/skills/keyline/glass/type/guide.md +3 -3
- package/plugins/keyline/skills/keyline/glass/type/long-form-prose.md +9 -74
- package/plugins/keyline/skills/keyline/glass/type/scale.md +2 -6
- package/plugins/keyline/skills/keyline/glass/type/weight-and-tracking.md +4 -20
- package/plugins/keyline/skills/keyline/scripts/check/css.mjs +5 -14
- package/plugins/keyline/skills/keyline/scripts/check/design.mjs +3 -1
- package/plugins/keyline/skills/keyline/scripts/check/files.mjs +70 -26
- package/plugins/keyline/skills/keyline/scripts/check/markup.mjs +3 -3
- package/plugins/keyline/skills/keyline/scripts/check/rules.mjs +9 -1
- package/plugins/keyline/skills/keyline/scripts/check.mjs +2 -2
- package/plugins/keyline/skills/keyline/scripts/contrast.mjs +2 -0
- package/plugins/keyline/skills/keyline/scripts/scan.mjs +29 -45
- package/plugins/keyline/skills/keyline/system/guide.md +1 -1
|
@@ -1,24 +1,16 @@
|
|
|
1
1
|
/* ─────────────────────────────────────────────────────────────────────────
|
|
2
2
|
Typographic system — tokens
|
|
3
|
-
|
|
4
|
-
Swap "Archivo" for your family; keep the ladder
|
|
5
|
-
|
|
6
|
-
The ladder tops out at 18px on purpose. See glass/type/scale.md — a low
|
|
7
|
-
ceiling is what forces every template onto one scale, and it's the single
|
|
8
|
-
most consequential choice in this system.
|
|
3
|
+
Tailwind v4 @theme tokens; outside Tailwind, put the same custom
|
|
4
|
+
properties on :root. Swap "Archivo" for your family; keep the ladder,
|
|
5
|
+
which tops out at 18px on purpose (glass/type/scale.md).
|
|
9
6
|
───────────────────────────────────────────────────────────────────────── */
|
|
10
7
|
|
|
11
8
|
@theme {
|
|
12
|
-
/* Self-hosted
|
|
13
|
-
|
|
14
|
-
system faces after it are belt and braces for a request that never lands.
|
|
15
|
-
A bare `display: swap` without metric matching is a reflow under the
|
|
16
|
-
reader's eye — see §7 of glass/type/guide.md. */
|
|
9
|
+
/* Self-hosted with a loader-generated metric-matched fallback (§7 of
|
|
10
|
+
glass/type/guide.md). */
|
|
17
11
|
--font-sans: var(--font-archivo), system-ui, -apple-system, "Helvetica Neue", sans-serif;
|
|
18
12
|
|
|
19
|
-
/* Code, commands and paths
|
|
20
|
-
a handful of monospace runs isn't worth the request. A second family
|
|
21
|
-
earns a download only if it's carrying reading copy. */
|
|
13
|
+
/* Code, commands and paths: a system stack on purpose. */
|
|
22
14
|
--font-mono: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, monospace;
|
|
23
15
|
|
|
24
16
|
/* ── Size ladder ──────────────────────────────────────────────────────
|
|
@@ -33,14 +25,10 @@
|
|
|
33
25
|
--text-micro: 12px; /* tags · micro-labels */
|
|
34
26
|
--text-tip: 11px; /* tooltips · initials */
|
|
35
27
|
--text-mark: 8.5px; /* lettermarks — recognised, not read */
|
|
36
|
-
--text-card: 7.5px; /* card lettering
|
|
37
|
-
|
|
38
|
-
/* ── Leading: three curves, one per job ───────────────────────────────
|
|
39
|
-
Display tightens as size grows. Reading opens as passages lengthen.
|
|
40
|
-
UI collapses to 1 so fixed-height boxes centre exactly.
|
|
28
|
+
--text-card: 7.5px; /* card lettering */
|
|
41
29
|
|
|
42
|
-
|
|
43
|
-
|
|
30
|
+
/* ── Leading: three curves, one per job (§3 of the guide). With a ladder
|
|
31
|
+
this short, leading is a role signal: lead / body / dek differ only here. */
|
|
44
32
|
--leading-page: 1.25;
|
|
45
33
|
--leading-title: 1.3; /* a detail-page title, which wraps to two lines */
|
|
46
34
|
--leading-heading: 1.2; /* a section heading, which doesn't */
|
|
@@ -52,9 +40,7 @@
|
|
|
52
40
|
--leading-tight: 1.4;
|
|
53
41
|
--leading-none: 1;
|
|
54
42
|
|
|
55
|
-
/* ── Tracking
|
|
56
|
-
Two values, 2px apart, because there are only two sizes above 14px.
|
|
57
|
-
A system tracking six steps has a scale doing too much work. */
|
|
43
|
+
/* ── Tracking (§4 of the guide) ───────────────────────────────────── */
|
|
58
44
|
--tracking-page: -0.02em; /* 18px */
|
|
59
45
|
--tracking-heading: -0.015em; /* 16px */
|
|
60
46
|
--tracking-card: -0.01em; /* 7.5px, set solid */
|
|
@@ -63,24 +49,11 @@
|
|
|
63
49
|
/* ── Weight: two, and only two ─────────────────────────────────────── */
|
|
64
50
|
--font-weight-normal: 400; /* all body, all prose, all quiet UI */
|
|
65
51
|
--font-weight-medium: 500; /* headings, <strong>, active state, marks */
|
|
66
|
-
/* The one exception
|
|
67
|
-
doesn't survive rasterisation, so the choice is 600 or nothing. One
|
|
68
|
-
instance of this in a codebase is the right number. */
|
|
52
|
+
/* The one exception: §2 of the guide. */
|
|
69
53
|
--font-weight-card: 600;
|
|
70
54
|
}
|
|
71
55
|
|
|
72
|
-
/* Text
|
|
73
|
-
reference them by name:
|
|
74
|
-
primary → --color-gray-1000 17.93:1
|
|
75
|
-
secondary → --color-gray-900 8.45:1
|
|
76
|
-
muted → --color-muted 6.48:1
|
|
77
|
-
quiet → --color-quiet 5.49:1
|
|
78
|
-
faint → --color-faint 4.61:1
|
|
79
|
-
|
|
80
|
-
Only the top two are ramp steps. muted/quiet/faint are their OWN tokens,
|
|
81
|
-
because a border needs 3:1 against its surface and text needs 4.5:1 — one
|
|
82
|
-
step can't serve both. Reusing mid-ramp greys (500-800) for text fails in
|
|
83
|
-
a light theme while looking perfectly fine in dark. */
|
|
56
|
+
/* Text tiers (primary → faint) and their contrast: glass/color/guide.md. */
|
|
84
57
|
|
|
85
58
|
@layer base {
|
|
86
59
|
body {
|
|
@@ -89,18 +62,14 @@
|
|
|
89
62
|
font-size: var(--text-copy);
|
|
90
63
|
line-height: var(--leading-body);
|
|
91
64
|
|
|
92
|
-
/* Tabular by default
|
|
93
|
-
column that should align — dates, timestamps, read times, clocks.
|
|
94
|
-
A live-updating number is unreadable without this. */
|
|
65
|
+
/* Tabular by default (§6 of the guide). */
|
|
95
66
|
font-variant-numeric: tabular-nums;
|
|
96
67
|
|
|
97
68
|
-webkit-font-smoothing: antialiased;
|
|
98
69
|
text-rendering: optimizeLegibility;
|
|
99
70
|
}
|
|
100
71
|
|
|
101
|
-
/* Emphasis
|
|
102
|
-
fights geometric letterforms, and a 700 bold blots open prose. Keep the
|
|
103
|
-
semantic tags — only the visual mapping changed. */
|
|
72
|
+
/* Emphasis maps onto weight and colour (§5 of the guide). */
|
|
104
73
|
em,
|
|
105
74
|
i {
|
|
106
75
|
font-style: normal;
|
|
@@ -115,11 +84,7 @@
|
|
|
115
84
|
/* ─────────────────────────────────────────────────────────────────────────
|
|
116
85
|
Role classes — optional. Use these, or apply the tokens inline through
|
|
117
86
|
Tailwind arbitrary values (text-[14px] leading-[1.65]) as the reference
|
|
118
|
-
implementation does.
|
|
119
|
-
|
|
120
|
-
Note how many of these share --text-copy. That's the system working: once
|
|
121
|
-
the ladder is short, a role is a (size, leading, weight, rule) tuple and
|
|
122
|
-
size is usually the part that doesn't vary.
|
|
87
|
+
implementation does.
|
|
123
88
|
───────────────────────────────────────────────────────────────────────── */
|
|
124
89
|
|
|
125
90
|
@layer components {
|
|
@@ -260,27 +225,26 @@
|
|
|
260
225
|
}
|
|
261
226
|
|
|
262
227
|
/* ─────────────────────────────────────────────────────────────────────────
|
|
263
|
-
Imported prose —
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
than letting it invent a second ladder.
|
|
228
|
+
Imported prose — rendered Markdown that can't carry utility classes,
|
|
229
|
+
styled by element in one scoped class on the ladder above (§8 of the
|
|
230
|
+
guide). `.skill-prose` is the class the reference site uses.
|
|
267
231
|
───────────────────────────────────────────────────────────────────────── */
|
|
268
232
|
|
|
269
233
|
@layer components {
|
|
270
|
-
.prose
|
|
234
|
+
.skill-prose {
|
|
271
235
|
font-size: var(--text-copy);
|
|
272
236
|
line-height: var(--leading-read);
|
|
273
237
|
color: var(--color-muted);
|
|
274
238
|
}
|
|
275
239
|
/* The document starts with whatever it starts with; a leading <h2> would
|
|
276
240
|
otherwise push 44px of nothing above itself. */
|
|
277
|
-
.prose
|
|
241
|
+
.skill-prose > :first-child {
|
|
278
242
|
margin-top: 0;
|
|
279
243
|
}
|
|
280
244
|
|
|
281
245
|
/* Three levels, not two — an imported document nests deeper than authored
|
|
282
246
|
prose, so h4 gets a real role instead of a browser default. */
|
|
283
|
-
.prose
|
|
247
|
+
.skill-prose h2 {
|
|
284
248
|
margin: 44px 0 12px;
|
|
285
249
|
font-size: var(--text-section);
|
|
286
250
|
font-weight: var(--font-weight-medium);
|
|
@@ -288,14 +252,14 @@
|
|
|
288
252
|
letter-spacing: var(--tracking-heading);
|
|
289
253
|
color: var(--color-gray-1000);
|
|
290
254
|
}
|
|
291
|
-
.prose
|
|
255
|
+
.skill-prose h3 {
|
|
292
256
|
margin: 32px 0 10px;
|
|
293
257
|
font-size: var(--text-copy);
|
|
294
258
|
font-weight: var(--font-weight-medium);
|
|
295
259
|
line-height: var(--leading-tight);
|
|
296
260
|
color: var(--color-gray-1000);
|
|
297
261
|
}
|
|
298
|
-
.prose
|
|
262
|
+
.skill-prose h4 {
|
|
299
263
|
margin: 24px 0 8px;
|
|
300
264
|
font-size: var(--text-dense);
|
|
301
265
|
font-weight: var(--font-weight-medium);
|
|
@@ -303,23 +267,22 @@
|
|
|
303
267
|
color: var(--color-gray-900);
|
|
304
268
|
}
|
|
305
269
|
|
|
306
|
-
.prose
|
|
307
|
-
.prose
|
|
308
|
-
.prose
|
|
309
|
-
.prose
|
|
270
|
+
.skill-prose p,
|
|
271
|
+
.skill-prose ul,
|
|
272
|
+
.skill-prose ol,
|
|
273
|
+
.skill-prose blockquote {
|
|
310
274
|
margin: 12px 0;
|
|
311
275
|
}
|
|
312
276
|
|
|
313
|
-
/*
|
|
314
|
-
|
|
315
|
-
.prose-imported ul {
|
|
277
|
+
/* The 3px dot of the hand-rolled bullets (glass/type/long-form-prose.md). */
|
|
278
|
+
.skill-prose ul {
|
|
316
279
|
list-style: none;
|
|
317
280
|
padding-left: 18px;
|
|
318
281
|
}
|
|
319
|
-
.prose
|
|
282
|
+
.skill-prose ul > li {
|
|
320
283
|
position: relative;
|
|
321
284
|
}
|
|
322
|
-
.prose
|
|
285
|
+
.skill-prose ul > li::before {
|
|
323
286
|
content: "";
|
|
324
287
|
position: absolute;
|
|
325
288
|
left: -14px;
|
|
@@ -329,22 +292,22 @@
|
|
|
329
292
|
border-radius: 999px;
|
|
330
293
|
background: var(--color-gray-500);
|
|
331
294
|
}
|
|
332
|
-
.prose
|
|
295
|
+
.skill-prose ol {
|
|
333
296
|
list-style: decimal;
|
|
334
297
|
padding-left: 18px;
|
|
335
298
|
}
|
|
336
299
|
/* Tabular, so a run past 9 doesn't shift its text. */
|
|
337
|
-
.prose
|
|
300
|
+
.skill-prose li::marker {
|
|
338
301
|
color: var(--color-faint);
|
|
339
302
|
font-variant-numeric: tabular-nums;
|
|
340
303
|
}
|
|
341
304
|
|
|
342
|
-
.prose
|
|
305
|
+
.skill-prose strong {
|
|
343
306
|
font-weight: var(--font-weight-medium);
|
|
344
307
|
color: var(--color-gray-1000);
|
|
345
308
|
}
|
|
346
309
|
|
|
347
|
-
.prose
|
|
310
|
+
.skill-prose code {
|
|
348
311
|
font-family: var(--font-mono);
|
|
349
312
|
font-size: 0.87em;
|
|
350
313
|
background: var(--color-gray-100);
|
|
@@ -353,10 +316,8 @@
|
|
|
353
316
|
color: var(--color-gray-1000);
|
|
354
317
|
}
|
|
355
318
|
|
|
356
|
-
/* Code blocks and tables
|
|
357
|
-
|
|
358
|
-
own box — the page body must never scroll sideways. */
|
|
359
|
-
.prose-imported pre {
|
|
319
|
+
/* Code blocks and tables scroll inside their own box, never the page. */
|
|
320
|
+
.skill-prose pre {
|
|
360
321
|
margin: 16px 0;
|
|
361
322
|
padding: 14px 16px;
|
|
362
323
|
background: var(--color-background-200);
|
|
@@ -364,7 +325,7 @@
|
|
|
364
325
|
border-radius: 12px;
|
|
365
326
|
overflow-x: auto;
|
|
366
327
|
}
|
|
367
|
-
.prose
|
|
328
|
+
.skill-prose pre code {
|
|
368
329
|
background: none;
|
|
369
330
|
padding: 0;
|
|
370
331
|
font-size: var(--text-code);
|
|
@@ -373,24 +334,24 @@
|
|
|
373
334
|
}
|
|
374
335
|
|
|
375
336
|
/* Wrap each <table> in a div with this class at render time. */
|
|
376
|
-
.prose
|
|
337
|
+
.skill-prose .table-scroll {
|
|
377
338
|
margin: 16px 0;
|
|
378
339
|
overflow-x: auto;
|
|
379
340
|
}
|
|
380
|
-
.prose
|
|
341
|
+
.skill-prose table {
|
|
381
342
|
width: 100%;
|
|
382
343
|
border-collapse: collapse;
|
|
383
344
|
font-size: var(--text-dense);
|
|
384
345
|
line-height: 1.5;
|
|
385
346
|
}
|
|
386
|
-
.prose
|
|
387
|
-
.prose
|
|
347
|
+
.skill-prose th,
|
|
348
|
+
.skill-prose td {
|
|
388
349
|
padding: 8px 12px 8px 0;
|
|
389
350
|
text-align: left;
|
|
390
351
|
vertical-align: top;
|
|
391
352
|
border-bottom: 1px solid var(--color-gray-alpha-300);
|
|
392
353
|
}
|
|
393
|
-
.prose
|
|
354
|
+
.skill-prose th {
|
|
394
355
|
font-weight: var(--font-weight-medium);
|
|
395
356
|
color: var(--color-gray-1000);
|
|
396
357
|
white-space: nowrap;
|
|
@@ -398,17 +359,17 @@
|
|
|
398
359
|
|
|
399
360
|
/* Imported Markdown can't take the authored pull quote's 3px rounded bar
|
|
400
361
|
(glass/type/assets/prose.tsx), so it keeps a plain 2px rule. */
|
|
401
|
-
.prose
|
|
362
|
+
.skill-prose blockquote {
|
|
402
363
|
padding-left: 15px;
|
|
403
364
|
border-left: 2px solid var(--color-gray-alpha-300);
|
|
404
365
|
color: var(--color-faint);
|
|
405
366
|
}
|
|
406
|
-
.prose
|
|
367
|
+
.skill-prose hr {
|
|
407
368
|
margin: 32px 0;
|
|
408
369
|
border: 0;
|
|
409
370
|
border-top: 1px solid var(--color-gray-alpha-300);
|
|
410
371
|
}
|
|
411
|
-
.prose
|
|
372
|
+
.skill-prose img {
|
|
412
373
|
max-width: 100%;
|
|
413
374
|
height: auto;
|
|
414
375
|
border-radius: 12px;
|
|
@@ -33,7 +33,7 @@ Each role gets a size in steps of 1–2px, as densely as the roles demand (domai
|
|
|
33
33
|
|
|
34
34
|
Text tiers (`primary` → `quiet`) come from the Glass colour guide (`glass/color/guide.md`). Only the top two are grey-ramp steps (`gray-1000`, `gray-900`); `muted` / `quiet` / `faint` are separate tokens picked for contrast (colour law 2).
|
|
35
35
|
|
|
36
|
-
**Eighteen is the ceiling, and it's lower than you think.**
|
|
36
|
+
**Eighteen is the ceiling, and it's lower than you think.** The site runs on **two sizes**, 16 for headings and 14 for everything read; the detail templates that once opened two steps above every other page came down to those (domain law 2; the deleted sizes are in `glass/type/scale.md`). One element likewise takes one role on every page, and sibling controls match (domain law 2).
|
|
37
37
|
|
|
38
38
|
### When size stops doing the work
|
|
39
39
|
|
|
@@ -50,7 +50,7 @@ At one size, the 17px keyline that once shouted over a 16px heading can't return
|
|
|
50
50
|
|
|
51
51
|
The cost is a flatter page: titles and section headings are both 16px, separated by position and spacing. It's the right flat, because the home page already worked that way and the detail pages were the outlier.
|
|
52
52
|
|
|
53
|
-
**Nothing in this ladder resizes
|
|
53
|
+
**Nothing in this ladder resizes** (the domain's responsive-sizes dial).
|
|
54
54
|
|
|
55
55
|
→ Full derivation, including how to add a role without bloating the ladder: `glass/type/scale.md`
|
|
56
56
|
|
|
@@ -67,7 +67,7 @@ Quiet or lifted is set by what the line is doing there, one lift per group, one
|
|
|
67
67
|
|
|
68
68
|
A contents rail's active row takes `font-medium` *and* primary colour, so state is never on colour alone; no state greys a title (domain laws 3 and 5).
|
|
69
69
|
|
|
70
|
-
→ Weight and tracking in depth
|
|
70
|
+
→ Weight and tracking in depth: `glass/type/weight-and-tracking.md`
|
|
71
71
|
|
|
72
72
|
## 3. Leading runs inverse to size
|
|
73
73
|
|
|
@@ -10,7 +10,7 @@ How Glass sets the reading half: looser leading and spacing than anything else o
|
|
|
10
10
|
|
|
11
11
|
Prose body is 14/1.75: a UI paragraph's size, noticeably more open (1.75 vs 1.55).
|
|
12
12
|
|
|
13
|
-
Why 1.75:
|
|
13
|
+
Why 1.75: the ~90-character measure (`glass/layout/measure-and-rhythm.md`). A shorter measure could pull it back toward 1.6.
|
|
14
14
|
|
|
15
15
|
## The reading column is two sizes
|
|
16
16
|
|
|
@@ -24,25 +24,9 @@ Why 1.75: a 576px text column at 14px holds roughly 85–95 characters a line, p
|
|
|
24
24
|
| Pull quote | 14 | a 3px rounded bar and a 16px indent |
|
|
25
25
|
| Note | 13 | a 1px rule, the faint tier, and a step down in size |
|
|
26
26
|
|
|
27
|
-
##
|
|
27
|
+
## The blocks, in code
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
**`keyline`** — body size, weight 500, `my-9`.
|
|
32
|
-
|
|
33
|
-
```tsx
|
|
34
|
-
case "keyline":
|
|
35
|
-
return <p className="reveal my-9 text-[14px] font-medium leading-[1.6] text-gray-1000">…</p>;
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
**`note`** — 13px, faint tier, a 1px left rule and 16px of indent.
|
|
39
|
-
|
|
40
|
-
```tsx
|
|
41
|
-
case "note":
|
|
42
|
-
return <p className="reveal my-7 border-l border-gray-alpha-300 pl-4 text-[13px] leading-[1.6] text-faint">…</p>;
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
The note's rule is 1px and the quote's 3px, with the type inside four steps apart, so the two indented rules can't be mistaken for each other.
|
|
29
|
+
Every block's classes — lead, body, keyline, note, h2, quote, figure, lists — are in `glass/type/assets/prose.tsx`, with the reason for each beside it. Keyline and note both came from one diagnosis, a body of writing that was 114 paragraphs and almost nothing else; what each is for, and the build check that keeps them honest: `domains/type/prose-and-emphasis.md`, **Two block types worth having**.
|
|
46
30
|
|
|
47
31
|
## Block spacing
|
|
48
32
|
|
|
@@ -59,61 +43,12 @@ The note's rule is 1px and the quote's 3px, with the type inside four steps apar
|
|
|
59
43
|
|
|
60
44
|
The heading's 40 above and 12 below, ~3.3:1, is the domain's asymmetric heading space; the keyline is its symmetric exception.
|
|
61
45
|
|
|
62
|
-
##
|
|
63
|
-
|
|
64
|
-
```tsx
|
|
65
|
-
<h2 className="mt-10 mb-3 text-[16px] font-medium leading-[1.2] tracking-[-0.015em]">
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
16px against 14px body is one step; the weight (500), the tight leading (1.2) and the 40px above do the real work.
|
|
69
|
-
|
|
70
|
-
It matches a page-level section heading and the detail page's own `<h1>`: three levels at one size, separated by position and spacing. Flatter than most article templates, and deliberate — the alternative was an in-body heading larger than the page title.
|
|
71
|
-
|
|
72
|
-
## Quotes
|
|
73
|
-
|
|
74
|
-
```tsx
|
|
75
|
-
<blockquote className="my-9 flex gap-4">
|
|
76
|
-
<span aria-hidden className="w-[3px] shrink-0 rounded-full bg-gray-500" />
|
|
77
|
-
<div className="text-[14px] leading-[1.75] text-gray-1000">…</div>
|
|
78
|
-
</blockquote>
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
The type matches the body around it, so the bar is the entire signal: the 2px `gray-alpha-300` border it replaced is domain law 4's example of auditing what's left after a signal goes. Why a flex child with no vertical margin: `domains/type/prose-and-emphasis.md`, **Quotes**.
|
|
82
|
-
|
|
83
|
-
- **`bg-gray-500`, from the grey ramp, not the alpha ramp.** It's a decorative mark on an opaque surface — see the Glass colour guide (`glass/color/guide.md`) on why decorative marks stay on the solid ramp.
|
|
84
|
-
|
|
85
|
-
Still absent: italic (globally disabled), quotation marks, background, colour step. Stepping the quote *down* to secondary, as earlier versions did, at body size just makes a de-emphasised paragraph.
|
|
86
|
-
|
|
87
|
-
The citation goes quieter and explicitly kills the browser's italic (domain law 9):
|
|
88
|
-
|
|
89
|
-
```tsx
|
|
90
|
-
<cite className="mt-2 block text-[13px] not-italic text-quiet">— {cite}</cite>
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
## Figures
|
|
94
|
-
|
|
95
|
-
```tsx
|
|
96
|
-
<figure className="reveal my-9">
|
|
97
|
-
<div className="overflow-hidden rounded-[10px] bg-gray-100 ring-1 ring-gray-alpha-300">
|
|
98
|
-
<div style={{ aspectRatio: b.aspect ?? "16 / 9" }}>
|
|
99
|
-
<img src={b.src} alt={b.alt} loading="lazy" decoding="async" className="h-full w-full object-cover" />
|
|
100
|
-
</div>
|
|
101
|
-
</div>
|
|
102
|
-
<figcaption className="mt-2.5 text-[13px] leading-[1.55] text-faint">{caption}</figcaption>
|
|
103
|
-
</figure>
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
The aspect ratio sits on a wrapper so the box is reserved before the image loads. `ring-1` rather than a border, so the outline stays out of the box model and the corner radius stays exact.
|
|
107
|
-
|
|
108
|
-
**The caption is the point of the figure, not a label on it**: what the thing was for, and which constraint shaped it. It sits at the note/meta tier (13px, faint).
|
|
109
|
-
|
|
110
|
-
## Lists
|
|
111
|
-
|
|
112
|
-
```tsx
|
|
113
|
-
<ul className="mb-6 list-disc space-y-2 pl-5 marker:text-gray-500">
|
|
114
|
-
```
|
|
46
|
+
## What the code doesn't say
|
|
115
47
|
|
|
116
|
-
|
|
48
|
+
- **Prose headings** match a page-level section heading and the detail page's own `<h1>`: three levels at 16px, separated by position and spacing. The alternative was an in-body heading larger than the page title.
|
|
49
|
+
- **The pull quote's bar is the entire signal**, since its type matches the body: the 2px `gray-alpha-300` border it replaced is domain law 4's example of auditing what's left after a signal goes. `bg-gray-500` is from the solid ramp, a decorative mark on an opaque surface (`glass/color/guide.md`). Still absent: italic, quotation marks, background, a colour step.
|
|
50
|
+
- **A figure's caption is the point of the figure**, not a label on it: what the thing was for, and which constraint shaped it.
|
|
51
|
+
- **Authored lists keep the UA marker** (`list-disc` / `list-decimal`) on `gray-500`, the domain's quiet marker; imported Markdown uses the 3px dot instead (§8 of `glass/type/guide.md`). Both are what the site ships.
|
|
117
52
|
|
|
118
53
|
## Hand-rolled bullets in UI context
|
|
119
54
|
|
|
@@ -144,7 +79,7 @@ Every block carries `reveal`, so paragraphs fade up one by one as they enter the
|
|
|
144
79
|
<p className="mt-2 text-[14px] leading-[1.55] text-muted">{dek}</p>
|
|
145
80
|
```
|
|
146
81
|
|
|
147
|
-
Body size, muted, tighter leading
|
|
82
|
+
Body size, muted, tighter leading: it separates on colour and leading alone, 1.55 against the body's 1.75 and the lead's 1.9. Dek and lead are the domain's tightest pairing; check them first.
|
|
148
83
|
|
|
149
84
|
## Cover art and the measure
|
|
150
85
|
|
|
@@ -17,7 +17,7 @@ A modular scale gives too few sizes in the 11–16px band, where Glass needs 11,
|
|
|
17
17
|
- 12.5px code. Monospace runs wide, so it steps down to occupy the same measure as the prose around it — the one size that compensates for a *family* rather than a role.
|
|
18
18
|
- 12px. Tags, prev/next labels, hover-card titles; small enough to read as annotation.
|
|
19
19
|
- 11px. Tooltips and avatar initials. The floor for anything a user is expected to read.
|
|
20
|
-
- 8.5px lettermarks, 7.5px card lettering. Below the floor on purpose — recognised rather than read
|
|
20
|
+
- 8.5px lettermarks, 7.5px card lettering. Below the floor on purpose — recognised rather than read (the 600 exception: §2 of the guide).
|
|
21
21
|
|
|
22
22
|
**What was deleted**
|
|
23
23
|
|
|
@@ -44,11 +44,7 @@ In the 11–16px band the steps are 1px, and that is enough (`domains/type/ladde
|
|
|
44
44
|
|
|
45
45
|
## Fixed sizes, not `rem`, and why that's a choice
|
|
46
46
|
|
|
47
|
-
The reference implementation uses `px` throughout (`text-[14px]`, not `text-sm` or `0.875rem`).
|
|
48
|
-
|
|
49
|
-
The trade: `rem` respects a user's browser font-size setting and `px` doesn't, a real accessibility cost. But this hierarchy depends on precise 1px relationships in the 11–16 band, a root scaled to 20px turns them into 15px steps that break the ladder, and browser *zoom* — which everyone uses, and which scales `px` fine — already covers the need.
|
|
50
|
-
|
|
51
|
-
If your audience skews toward users who set a root font size — documentation, government, healthcare — use `rem` and accept a looser ladder. Convert at a fixed 16px root: 18px → 1.125rem, 14px → 0.875rem, 13px → 0.8125rem. Never mix the two (the domain's unit dial).
|
|
47
|
+
The reference implementation uses `px` throughout (`text-[14px]`, not `text-sm` or `0.875rem`), to keep the 1px steps of the 11–16 band exact under a scaled root; the trade is the domain's unit dial. Moving to `rem`, convert at a fixed 16px root: 18px → 1.125rem, 14px → 0.875rem, 13px → 0.8125rem.
|
|
52
48
|
|
|
53
49
|
## Keeping every element on it
|
|
54
50
|
|
|
@@ -16,13 +16,11 @@ All body copy. All prose. Deks. Meta lines. Tags. Tooltips. Timestamps. Quiet na
|
|
|
16
16
|
**500 — structure and state.**
|
|
17
17
|
Headings h1–h4. `<strong>`. The active row in a table of contents. Names and wordmarks. Captions that label an image rather than describe it. Lettermarks. Company/role titles in a list, where the role is structural and the date supporting.
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
The lists are defaults; the choice is made per spot (domain law 5): the same 13px line is 400 as a date under a title and 500 as the name heading a group.
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
### 300
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
Rarely, and never for text read at length: at a very large display size (48px+), where 400 starts to look heavy, or a decorative numeral. If your interface tops out at 30px — most do — you don't need it. The reference implementation loaded 300 for a while and never used it; dropping it from the font request was a straight performance win.
|
|
23
|
+
Not loaded. The reference implementation loaded it for a while and never used it; dropping it from the font request was a straight performance win.
|
|
26
24
|
|
|
27
25
|
### Weight as an accessibility signal
|
|
28
26
|
|
|
@@ -32,23 +30,9 @@ The active table-of-contents row takes the primary text colour *and* weight 500
|
|
|
32
30
|
className={`… ${tone} ${state === "active" ? "font-medium" : ""}`}
|
|
33
31
|
```
|
|
34
32
|
|
|
35
|
-
Weight is cheaper here than a background pill, border or icon, each of which adds visual weight to a list meant to be quiet.
|
|
36
|
-
|
|
37
33
|
## Tracking
|
|
38
34
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
| Size | Tracking | Reasoning |
|
|
42
|
-
|---|---|---|
|
|
43
|
-
| 7.5px | −0.01em | Card lettering, set solid. The exception below the floor — see the note. |
|
|
44
|
-
| 8.5–11px | +0.02em | Tiny glyphs and near-caps need air or the counters close up. |
|
|
45
|
-
| 12–14px | 0 | Default metrics are designed for this range. Leave them. |
|
|
46
|
-
| 16px | −0.015em | Enough to look set; small enough to be invisible as a decision. |
|
|
47
|
-
| 18px | −0.02em | A page title is a tighter, more deliberate object than a section heading two pixels below. |
|
|
48
|
-
|
|
49
|
-
Only two rows are the negative-tracking-at-size rule, because the ladder tops out at 18px. An earlier table ran to 30px at −0.025em, with separate values for a 19px heading and a 19px quote; all three sizes left the ladder and took their tracking with them.
|
|
50
|
-
|
|
51
|
-
The 7.5px row doesn't contradict the 8.5–11px one: that lettering is set solid at near-caps size on a card, so the tightening compensates for the *setting*, not the size. It's also the one place a 600 weight is allowed. Two exceptions in one place hint you're outside the text system — which you are, since nobody reads it.
|
|
35
|
+
The values are §4 of `glass/type/guide.md`. Only 16px and 18px are the negative-tracking-at-size rule, because the ladder tops out at 18px. The 7.5px card lettering's −0.01em doesn't contradict the lettermarks' +0.02em: it is set solid at near-caps size, so the tightening compensates for the *setting*, not the size.
|
|
52
36
|
|
|
53
37
|
### The 8.5px lettermark
|
|
54
38
|
|
|
@@ -13,12 +13,6 @@ export function blankComments(text) {
|
|
|
13
13
|
return text.replace(/\/\*[\s\S]*?\*\//g, (m) => m.replace(/[^\n]/g, " "));
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
export function lineAt(text, offset) {
|
|
17
|
-
let line = 1;
|
|
18
|
-
for (let i = 0; i < offset && i < text.length; i++) if (text.charCodeAt(i) === 10) line++;
|
|
19
|
-
return line;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
16
|
/** Lines start offsets, for fast offset → line lookups on big files. */
|
|
23
17
|
export function lineIndex(text) {
|
|
24
18
|
const starts = [0];
|
|
@@ -44,8 +38,7 @@ export function parseCss(source, baseLine = 1) {
|
|
|
44
38
|
const at = lineIndex(text);
|
|
45
39
|
const decls = [];
|
|
46
40
|
const atRules = [];
|
|
47
|
-
const
|
|
48
|
-
const stack = []; // open blocks: { kind: 'rule'|'at', prelude, block }
|
|
41
|
+
const stack = []; // open blocks: { kind: 'rule'|'at', prelude, block: { decls } }
|
|
49
42
|
let start = 0;
|
|
50
43
|
let quote = null;
|
|
51
44
|
let paren = 0;
|
|
@@ -65,7 +58,7 @@ export function parseCss(source, baseLine = 1) {
|
|
|
65
58
|
const value = raw.slice(colon + 1).trim();
|
|
66
59
|
const lead = from + raw.search(/\S/);
|
|
67
60
|
const ctx = context();
|
|
68
|
-
const d = { prop, value, line: at(lead) + baseLine - 1,
|
|
61
|
+
const d = { prop, value, line: at(lead) + baseLine - 1, selector: ctx.selector, atRules: ctx.atRules, block: ctx.block };
|
|
69
62
|
decls.push(d);
|
|
70
63
|
ctx.block?.decls.push(d);
|
|
71
64
|
};
|
|
@@ -85,10 +78,8 @@ export function parseCss(source, baseLine = 1) {
|
|
|
85
78
|
const prelude = text.slice(start, i).trim().replace(/\s+/g, " ");
|
|
86
79
|
const kind = prelude.startsWith("@") ? "at" : "rule";
|
|
87
80
|
const lead = start + text.slice(start, i).search(/\S/);
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
blocks.push(block);
|
|
91
|
-
stack.push({ kind, prelude, block });
|
|
81
|
+
if (kind === "at") atRules.push({ prelude, line: at(Math.max(lead, 0)) + baseLine - 1 });
|
|
82
|
+
stack.push({ kind, prelude, block: { decls: [] } });
|
|
92
83
|
start = i + 1;
|
|
93
84
|
} else if (c === ";") {
|
|
94
85
|
if (stack.length) pushDecl(start, i);
|
|
@@ -103,7 +94,7 @@ export function parseCss(source, baseLine = 1) {
|
|
|
103
94
|
start = i + 1;
|
|
104
95
|
}
|
|
105
96
|
}
|
|
106
|
-
return { decls, atRules
|
|
97
|
+
return { decls, atRules };
|
|
107
98
|
}
|
|
108
99
|
|
|
109
100
|
/** Parse a `style="..."` attribute's body. */
|
|
@@ -50,7 +50,9 @@ export function readDesign(root) {
|
|
|
50
50
|
// or mentions one — their values are the ladder.
|
|
51
51
|
const layout = section(md, "Layout");
|
|
52
52
|
const zLadder = new Set();
|
|
53
|
-
|
|
53
|
+
// A project rung (`--z-header`, `$z-header`, a `z-header` utility) counts when it opens the token cell, the row's second.
|
|
54
|
+
const zCell = (c) => /^`(--|\$)?z-[\w-]+`/.test(c);
|
|
55
|
+
for (const row of layout.split("\n").filter((l) => /^\|\s*`?z\./.test(l) || (l.startsWith("|") && (/`-?z-\[?-?\d|`z\./.test(l) || zCell((l.split("|")[2] ?? "").trim()))))) {
|
|
54
56
|
const cells = row.split("|").slice(1, -1).map((c) => c.trim());
|
|
55
57
|
for (const c of cells.slice(1)) for (const m of c.matchAll(/(?<![\w.#-])(-?\d+)(?![\w.%])/g)) zLadder.add(Number(m[1]));
|
|
56
58
|
}
|