trykeyline 0.0.0-stage → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +18 -0
- package/LICENSE +21 -0
- package/README.md +117 -2
- package/bin/keyline.mjs +280 -0
- package/package.json +38 -4
- package/plugins/keyline/.claude-plugin/plugin.json +14 -0
- package/plugins/keyline/plugin.json +22 -0
- package/plugins/keyline/skills/keyline/SKILL.md +47 -0
- package/plugins/keyline/skills/keyline/commands/audit.md +30 -0
- package/plugins/keyline/skills/keyline/commands/color.md +7 -0
- package/plugins/keyline/skills/keyline/commands/critique.md +20 -0
- package/plugins/keyline/skills/keyline/commands/depth.md +7 -0
- package/plugins/keyline/skills/keyline/commands/init.md +16 -0
- package/plugins/keyline/skills/keyline/commands/layout.md +7 -0
- package/plugins/keyline/skills/keyline/commands/motion.md +7 -0
- package/plugins/keyline/skills/keyline/commands/patterns.md +7 -0
- package/plugins/keyline/skills/keyline/commands/polish.md +19 -0
- package/plugins/keyline/skills/keyline/commands/refine.md +19 -0
- package/plugins/keyline/skills/keyline/commands/sync.md +15 -0
- package/plugins/keyline/skills/keyline/commands/type.md +7 -0
- package/plugins/keyline/skills/keyline/commands/ux-laws.md +26 -0
- package/plugins/keyline/skills/keyline/domains/color/deriving-a-ramp.md +144 -0
- package/plugins/keyline/skills/keyline/domains/color/guide.md +207 -0
- package/plugins/keyline/skills/keyline/domains/color/status-and-exceptions.md +174 -0
- package/plugins/keyline/skills/keyline/domains/color/theming-mechanics.md +121 -0
- package/plugins/keyline/skills/keyline/domains/depth/elevation-recipes.md +185 -0
- package/plugins/keyline/skills/keyline/domains/depth/fallbacks-and-performance.md +132 -0
- package/plugins/keyline/skills/keyline/domains/depth/guide.md +226 -0
- package/plugins/keyline/skills/keyline/domains/depth/translucent-materials.md +159 -0
- package/plugins/keyline/skills/keyline/domains/layout/forks-gating-and-disclosures.md +201 -0
- package/plugins/keyline/skills/keyline/domains/layout/guide.md +259 -0
- package/plugins/keyline/skills/keyline/domains/layout/measure-and-rhythm.md +193 -0
- package/plugins/keyline/skills/keyline/domains/layout/radius-lines-and-layers.md +122 -0
- package/plugins/keyline/skills/keyline/domains/motion/guards.md +162 -0
- package/plugins/keyline/skills/keyline/domains/motion/guide.md +198 -0
- package/plugins/keyline/skills/keyline/domains/motion/ladder-and-curves.md +139 -0
- package/plugins/keyline/skills/keyline/domains/motion/patterns.md +196 -0
- package/plugins/keyline/skills/keyline/domains/motion/performance.md +121 -0
- package/plugins/keyline/skills/keyline/domains/patterns/actions-and-forms.md +204 -0
- package/plugins/keyline/skills/keyline/domains/patterns/agents-and-live-states.md +111 -0
- package/plugins/keyline/skills/keyline/domains/patterns/collections-and-dashboards.md +131 -0
- package/plugins/keyline/skills/keyline/domains/patterns/copy.md +123 -0
- package/plugins/keyline/skills/keyline/domains/patterns/guide.md +174 -0
- package/plugins/keyline/skills/keyline/domains/patterns/iteration.md +70 -0
- package/plugins/keyline/skills/keyline/domains/type/display-tier.md +108 -0
- package/plugins/keyline/skills/keyline/domains/type/guide.md +227 -0
- package/plugins/keyline/skills/keyline/domains/type/ladders.md +169 -0
- package/plugins/keyline/skills/keyline/domains/type/numerals-and-loading.md +107 -0
- package/plugins/keyline/skills/keyline/domains/type/prose-and-emphasis.md +125 -0
- package/plugins/keyline/skills/keyline/domains/type/type-audit.md +87 -0
- package/plugins/keyline/skills/keyline/glass/color/assets/color-tokens.css +299 -0
- package/plugins/keyline/skills/keyline/glass/color/assets/theme.ts +119 -0
- package/plugins/keyline/skills/keyline/glass/color/guide.md +195 -0
- package/plugins/keyline/skills/keyline/glass/color/intent-ramp.md +136 -0
- package/plugins/keyline/skills/keyline/glass/color/theming.md +106 -0
- package/plugins/keyline/skills/keyline/glass/depth/assets/GlassPanel.tsx +171 -0
- package/plugins/keyline/skills/keyline/glass/depth/assets/glass.css +322 -0
- package/plugins/keyline/skills/keyline/glass/depth/blur-and-tint.md +50 -0
- package/plugins/keyline/skills/keyline/glass/depth/depth-cues.md +87 -0
- package/plugins/keyline/skills/keyline/glass/depth/guide.md +147 -0
- package/plugins/keyline/skills/keyline/glass/depth/performance-and-fallbacks.md +37 -0
- package/plugins/keyline/skills/keyline/glass/layout/adaptive-presentations.md +231 -0
- package/plugins/keyline/skills/keyline/glass/layout/assets/SectionNav.tsx +244 -0
- package/plugins/keyline/skills/keyline/glass/layout/assets/layout-tokens.css +201 -0
- package/plugins/keyline/skills/keyline/glass/layout/guide.md +259 -0
- package/plugins/keyline/skills/keyline/glass/layout/measure-and-rhythm.md +134 -0
- package/plugins/keyline/skills/keyline/glass/layout/radius-and-elevation.md +103 -0
- package/plugins/keyline/skills/keyline/glass/motion/assets/DropMenu.tsx +106 -0
- package/plugins/keyline/skills/keyline/glass/motion/assets/motion-presets.ts +210 -0
- package/plugins/keyline/skills/keyline/glass/motion/assets/motion-tokens.css +176 -0
- package/plugins/keyline/skills/keyline/glass/motion/assets/useReveal.ts +65 -0
- package/plugins/keyline/skills/keyline/glass/motion/assets/useScrollTick.ts +183 -0
- package/plugins/keyline/skills/keyline/glass/motion/easing-and-duration.md +108 -0
- package/plugins/keyline/skills/keyline/glass/motion/guide.md +255 -0
- package/plugins/keyline/skills/keyline/glass/motion/reduced-motion.md +81 -0
- package/plugins/keyline/skills/keyline/glass/motion/scroll-performance.md +58 -0
- package/plugins/keyline/skills/keyline/glass/motion/shared-element.md +161 -0
- package/plugins/keyline/skills/keyline/glass/motion/sound.md +195 -0
- package/plugins/keyline/skills/keyline/glass/motion/springs.md +51 -0
- package/plugins/keyline/skills/keyline/glass/type/assets/prose.tsx +268 -0
- package/plugins/keyline/skills/keyline/glass/type/assets/type-tokens.css +416 -0
- package/plugins/keyline/skills/keyline/glass/type/guide.md +220 -0
- package/plugins/keyline/skills/keyline/glass/type/long-form-prose.md +151 -0
- package/plugins/keyline/skills/keyline/glass/type/scale.md +69 -0
- package/plugins/keyline/skills/keyline/glass/type/weight-and-tracking.md +61 -0
- package/plugins/keyline/skills/keyline/scripts/check/css.mjs +112 -0
- package/plugins/keyline/skills/keyline/scripts/check/design.mjs +67 -0
- package/plugins/keyline/skills/keyline/scripts/check/files.mjs +44 -0
- package/plugins/keyline/skills/keyline/scripts/check/markup.mjs +149 -0
- package/plugins/keyline/skills/keyline/scripts/check/rules.mjs +584 -0
- package/plugins/keyline/skills/keyline/scripts/check.mjs +297 -0
- package/plugins/keyline/skills/keyline/scripts/contrast.mjs +153 -0
- package/plugins/keyline/skills/keyline/scripts/scan.mjs +340 -0
- package/plugins/keyline/skills/keyline/system/design-md.md +136 -0
- package/plugins/keyline/skills/keyline/system/detection.md +53 -0
- package/plugins/keyline/skills/keyline/system/guide.md +97 -0
- package/plugins/keyline/skills/keyline/system/presets.md +58 -0
- package/plugins/keyline/skills/keyline/system/roles.md +150 -0
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
/* ─────────────────────────────────────────────────────────────────────────
|
|
2
|
+
Typographic system — tokens
|
|
3
|
+
Drop-in for Tailwind v4 (@theme) or any CSS setup (the :root block).
|
|
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.
|
|
9
|
+
───────────────────────────────────────────────────────────────────────── */
|
|
10
|
+
|
|
11
|
+
@theme {
|
|
12
|
+
/* Self-hosted, with a metric-matched fallback generated by the loader
|
|
13
|
+
(next/font, fontaine, or a hand-tuned @font-face with size-adjust). The
|
|
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. */
|
|
17
|
+
--font-sans: var(--font-archivo), system-ui, -apple-system, "Helvetica Neue", sans-serif;
|
|
18
|
+
|
|
19
|
+
/* Code, commands and paths. System stack on purpose — a second webfont for
|
|
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. */
|
|
22
|
+
--font-mono: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, monospace;
|
|
23
|
+
|
|
24
|
+
/* ── Size ladder ──────────────────────────────────────────────────────
|
|
25
|
+
Each size exists for a named role. See glass/type/scale.md before
|
|
26
|
+
adding one — most new roles fit an existing size at a new leading. */
|
|
27
|
+
--text-page: 18px; /* index-page title — the top of the ladder */
|
|
28
|
+
--text-section: 16px; /* detail-page title · section heading · prose heading */
|
|
29
|
+
--text-copy: 14px; /* body · lead · keyline · quote · dek · UI rows */
|
|
30
|
+
--text-summary: 13.5px; /* collapsed summary lines — the one half-step */
|
|
31
|
+
--text-dense: 13px; /* notes · captions · meta · facts · footer */
|
|
32
|
+
--text-code: 12.5px; /* monospace runs, stepped down for a wider family */
|
|
33
|
+
--text-micro: 12px; /* tags · micro-labels */
|
|
34
|
+
--text-tip: 11px; /* tooltips · initials */
|
|
35
|
+
--text-mark: 8.5px; /* lettermarks — recognised, not read */
|
|
36
|
+
--text-card: 7.5px; /* card lettering — the one place 600 is allowed */
|
|
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.
|
|
41
|
+
|
|
42
|
+
With the size ladder this short, leading is a ROLE SIGNAL, not a
|
|
43
|
+
refinement: lead / body / dek are the same size and differ only here. */
|
|
44
|
+
--leading-page: 1.25;
|
|
45
|
+
--leading-title: 1.3; /* a detail-page title, which wraps to two lines */
|
|
46
|
+
--leading-heading: 1.2; /* a section heading, which doesn't */
|
|
47
|
+
--leading-lead: 1.9; /* the opening paragraph */
|
|
48
|
+
--leading-prose: 1.75; /* body — the baseline the others read against */
|
|
49
|
+
--leading-read: 1.65;
|
|
50
|
+
--leading-keyline: 1.6;
|
|
51
|
+
--leading-body: 1.55;
|
|
52
|
+
--leading-tight: 1.4;
|
|
53
|
+
--leading-none: 1;
|
|
54
|
+
|
|
55
|
+
/* ── Tracking: a large-size correction ────────────────────────────────
|
|
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. */
|
|
58
|
+
--tracking-page: -0.02em; /* 18px */
|
|
59
|
+
--tracking-heading: -0.015em; /* 16px */
|
|
60
|
+
--tracking-card: -0.01em; /* 7.5px, set solid */
|
|
61
|
+
--tracking-mark: 0.02em; /* tiny caps need the opposite */
|
|
62
|
+
|
|
63
|
+
/* ── Weight: two, and only two ─────────────────────────────────────── */
|
|
64
|
+
--font-weight-normal: 400; /* all body, all prose, all quiet UI */
|
|
65
|
+
--font-weight-medium: 500; /* headings, <strong>, active state, marks */
|
|
66
|
+
/* The one exception, and it lives below the readable floor: at 7.5px a 500
|
|
67
|
+
doesn't survive rasterisation, so the choice is 600 or nothing. One
|
|
68
|
+
instance of this in a codebase is the right number. */
|
|
69
|
+
--font-weight-card: 600;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/* Text colour tiers live in the Glass colour guide (`glass/color/guide.md`). The roles below
|
|
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. */
|
|
84
|
+
|
|
85
|
+
@layer base {
|
|
86
|
+
body {
|
|
87
|
+
font-family: var(--font-sans);
|
|
88
|
+
font-weight: var(--font-weight-normal);
|
|
89
|
+
font-size: var(--text-copy);
|
|
90
|
+
line-height: var(--leading-body);
|
|
91
|
+
|
|
92
|
+
/* Tabular by default. In a text-heavy interface most numbers sit in a
|
|
93
|
+
column that should align — dates, timestamps, read times, clocks.
|
|
94
|
+
A live-updating number is unreadable without this. */
|
|
95
|
+
font-variant-numeric: tabular-nums;
|
|
96
|
+
|
|
97
|
+
-webkit-font-smoothing: antialiased;
|
|
98
|
+
text-rendering: optimizeLegibility;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/* Emphasis is re-mapped onto weight and colour. A separate italic file
|
|
102
|
+
fights geometric letterforms, and a 700 bold blots open prose. Keep the
|
|
103
|
+
semantic tags — only the visual mapping changed. */
|
|
104
|
+
em,
|
|
105
|
+
i {
|
|
106
|
+
font-style: normal;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/* <cite> and <blockquote> carry UA italics the rule above doesn't reach. */
|
|
110
|
+
cite {
|
|
111
|
+
font-style: normal;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/* ─────────────────────────────────────────────────────────────────────────
|
|
116
|
+
Role classes — optional. Use these, or apply the tokens inline through
|
|
117
|
+
Tailwind arbitrary values (text-[14px] leading-[1.65]) as the reference
|
|
118
|
+
implementation does. Either way the numbers come from one place.
|
|
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.
|
|
123
|
+
───────────────────────────────────────────────────────────────────────── */
|
|
124
|
+
|
|
125
|
+
@layer components {
|
|
126
|
+
/* An index page's title. The largest role there is. */
|
|
127
|
+
.t-page {
|
|
128
|
+
font-size: var(--text-page);
|
|
129
|
+
line-height: var(--leading-page);
|
|
130
|
+
letter-spacing: var(--tracking-page);
|
|
131
|
+
font-weight: var(--font-weight-medium);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/* A detail page's title. Looser leading than a section heading because it
|
|
135
|
+
wraps; otherwise identical, and deliberately so. */
|
|
136
|
+
.t-title {
|
|
137
|
+
font-size: var(--text-section);
|
|
138
|
+
line-height: var(--leading-title);
|
|
139
|
+
letter-spacing: var(--tracking-heading);
|
|
140
|
+
font-weight: var(--font-weight-medium);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
.t-section {
|
|
144
|
+
font-size: var(--text-section);
|
|
145
|
+
line-height: var(--leading-heading);
|
|
146
|
+
letter-spacing: var(--tracking-heading);
|
|
147
|
+
font-weight: var(--font-weight-medium);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
.t-sub {
|
|
151
|
+
font-size: var(--text-copy);
|
|
152
|
+
line-height: var(--leading-tight);
|
|
153
|
+
font-weight: var(--font-weight-medium);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/* Long-form body. Same size as a UI paragraph; the leading is the whole
|
|
157
|
+
difference, and it has to be exact. */
|
|
158
|
+
.t-prose {
|
|
159
|
+
font-size: var(--text-copy);
|
|
160
|
+
line-height: var(--leading-prose);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/* The opening paragraph. Separates from .t-prose on leading alone. */
|
|
164
|
+
.t-lead {
|
|
165
|
+
font-size: var(--text-copy);
|
|
166
|
+
line-height: var(--leading-lead);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/* The one sentence a section is for. Separates on weight and on air —
|
|
170
|
+
symmetric margins, because it belongs to neither neighbour. */
|
|
171
|
+
.t-keyline {
|
|
172
|
+
font-size: var(--text-copy);
|
|
173
|
+
line-height: var(--leading-keyline);
|
|
174
|
+
font-weight: var(--font-weight-medium);
|
|
175
|
+
margin-block: 36px;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/* A standfirst. Separates from .t-lead on colour and leading — the
|
|
179
|
+
tightest pairing in the system. Check this one first if a page starts
|
|
180
|
+
to look undifferentiated. */
|
|
181
|
+
.t-dek {
|
|
182
|
+
font-size: var(--text-copy);
|
|
183
|
+
line-height: var(--leading-body);
|
|
184
|
+
color: var(--color-muted);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
.t-read {
|
|
188
|
+
font-size: var(--text-copy);
|
|
189
|
+
line-height: var(--leading-read);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
.t-body {
|
|
193
|
+
font-size: var(--text-copy);
|
|
194
|
+
line-height: var(--leading-body);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/* Single-line UI text in a fixed-height box. leading-none so a descender
|
|
198
|
+
can't shift the box and vertical centring is exact. */
|
|
199
|
+
.t-row {
|
|
200
|
+
font-size: var(--text-copy);
|
|
201
|
+
line-height: var(--leading-none);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
.t-summary {
|
|
205
|
+
font-size: var(--text-summary);
|
|
206
|
+
line-height: var(--leading-keyline);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
.t-dense {
|
|
210
|
+
font-size: var(--text-dense);
|
|
211
|
+
line-height: var(--leading-tight);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/* An aside set outside the argument — a caveat, a scope limit, a piece of
|
|
215
|
+
provenance. Its 1px rule is deliberately lighter than the pull quote's
|
|
216
|
+
3px bar: two indented rules on one page must be unmistakable. */
|
|
217
|
+
.t-note {
|
|
218
|
+
font-size: var(--text-dense);
|
|
219
|
+
line-height: var(--leading-keyline);
|
|
220
|
+
color: var(--color-faint);
|
|
221
|
+
border-left: 1px solid var(--color-gray-alpha-300);
|
|
222
|
+
padding-left: 16px;
|
|
223
|
+
margin-block: 28px;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
.t-meta {
|
|
227
|
+
font-size: var(--text-dense);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
.t-code {
|
|
231
|
+
font-family: var(--font-mono);
|
|
232
|
+
font-size: var(--text-code);
|
|
233
|
+
line-height: var(--leading-keyline);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
.t-micro {
|
|
237
|
+
font-size: var(--text-micro);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
.t-tip {
|
|
241
|
+
font-size: var(--text-tip);
|
|
242
|
+
line-height: var(--leading-none);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/* Not text — a two-or-three-letter stand-in for a logo, sized below the
|
|
246
|
+
readability floor because you recognise it rather than read it. */
|
|
247
|
+
.t-mark {
|
|
248
|
+
font-size: var(--text-mark);
|
|
249
|
+
line-height: var(--leading-none);
|
|
250
|
+
letter-spacing: var(--tracking-mark);
|
|
251
|
+
font-weight: var(--font-weight-medium);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
.t-card {
|
|
255
|
+
font-size: var(--text-card);
|
|
256
|
+
line-height: 1.2;
|
|
257
|
+
letter-spacing: var(--tracking-card);
|
|
258
|
+
font-weight: var(--font-weight-card);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/* ─────────────────────────────────────────────────────────────────────────
|
|
263
|
+
Imported prose — a rendered Markdown document, a CMS field, a README.
|
|
264
|
+
It arrives as an HTML string, so it can't carry utility classes. Style it
|
|
265
|
+
by element inside one scoped class, and map it onto the roles above rather
|
|
266
|
+
than letting it invent a second ladder.
|
|
267
|
+
───────────────────────────────────────────────────────────────────────── */
|
|
268
|
+
|
|
269
|
+
@layer components {
|
|
270
|
+
.prose-imported {
|
|
271
|
+
font-size: var(--text-copy);
|
|
272
|
+
line-height: var(--leading-read);
|
|
273
|
+
color: var(--color-muted);
|
|
274
|
+
}
|
|
275
|
+
/* The document starts with whatever it starts with; a leading <h2> would
|
|
276
|
+
otherwise push 44px of nothing above itself. */
|
|
277
|
+
.prose-imported > :first-child {
|
|
278
|
+
margin-top: 0;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/* Three levels, not two — an imported document nests deeper than authored
|
|
282
|
+
prose, so h4 gets a real role instead of a browser default. */
|
|
283
|
+
.prose-imported h2 {
|
|
284
|
+
margin: 44px 0 12px;
|
|
285
|
+
font-size: var(--text-section);
|
|
286
|
+
font-weight: var(--font-weight-medium);
|
|
287
|
+
line-height: var(--leading-heading);
|
|
288
|
+
letter-spacing: var(--tracking-heading);
|
|
289
|
+
color: var(--color-gray-1000);
|
|
290
|
+
}
|
|
291
|
+
.prose-imported h3 {
|
|
292
|
+
margin: 32px 0 10px;
|
|
293
|
+
font-size: var(--text-copy);
|
|
294
|
+
font-weight: var(--font-weight-medium);
|
|
295
|
+
line-height: var(--leading-tight);
|
|
296
|
+
color: var(--color-gray-1000);
|
|
297
|
+
}
|
|
298
|
+
.prose-imported h4 {
|
|
299
|
+
margin: 24px 0 8px;
|
|
300
|
+
font-size: var(--text-dense);
|
|
301
|
+
font-weight: var(--font-weight-medium);
|
|
302
|
+
line-height: var(--leading-tight);
|
|
303
|
+
color: var(--color-gray-900);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
.prose-imported p,
|
|
307
|
+
.prose-imported ul,
|
|
308
|
+
.prose-imported ol,
|
|
309
|
+
.prose-imported blockquote {
|
|
310
|
+
margin: 12px 0;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/* A UA disc at 14px is far too heavy for this system. Same 3px dot as the
|
|
314
|
+
hand-rolled bullets everywhere else. */
|
|
315
|
+
.prose-imported ul {
|
|
316
|
+
list-style: none;
|
|
317
|
+
padding-left: 18px;
|
|
318
|
+
}
|
|
319
|
+
.prose-imported ul > li {
|
|
320
|
+
position: relative;
|
|
321
|
+
}
|
|
322
|
+
.prose-imported ul > li::before {
|
|
323
|
+
content: "";
|
|
324
|
+
position: absolute;
|
|
325
|
+
left: -14px;
|
|
326
|
+
top: 10px;
|
|
327
|
+
width: 3px;
|
|
328
|
+
height: 3px;
|
|
329
|
+
border-radius: 999px;
|
|
330
|
+
background: var(--color-gray-500);
|
|
331
|
+
}
|
|
332
|
+
.prose-imported ol {
|
|
333
|
+
list-style: decimal;
|
|
334
|
+
padding-left: 18px;
|
|
335
|
+
}
|
|
336
|
+
/* Tabular, so a run past 9 doesn't shift its text. */
|
|
337
|
+
.prose-imported li::marker {
|
|
338
|
+
color: var(--color-faint);
|
|
339
|
+
font-variant-numeric: tabular-nums;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
.prose-imported strong {
|
|
343
|
+
font-weight: var(--font-weight-medium);
|
|
344
|
+
color: var(--color-gray-1000);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
.prose-imported code {
|
|
348
|
+
font-family: var(--font-mono);
|
|
349
|
+
font-size: 0.87em;
|
|
350
|
+
background: var(--color-gray-100);
|
|
351
|
+
border-radius: 4px;
|
|
352
|
+
padding: 1.5px 4px;
|
|
353
|
+
color: var(--color-gray-1000);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/* Code blocks and tables are the two things a Markdown document has that a
|
|
357
|
+
designed page doesn't, and both are unbounded. Each scrolls inside its
|
|
358
|
+
own box — the page body must never scroll sideways. */
|
|
359
|
+
.prose-imported pre {
|
|
360
|
+
margin: 16px 0;
|
|
361
|
+
padding: 14px 16px;
|
|
362
|
+
background: var(--color-background-200);
|
|
363
|
+
border: 1px solid var(--color-gray-alpha-300);
|
|
364
|
+
border-radius: 12px;
|
|
365
|
+
overflow-x: auto;
|
|
366
|
+
}
|
|
367
|
+
.prose-imported pre code {
|
|
368
|
+
background: none;
|
|
369
|
+
padding: 0;
|
|
370
|
+
font-size: var(--text-code);
|
|
371
|
+
line-height: var(--leading-keyline);
|
|
372
|
+
color: var(--color-gray-900);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/* Wrap each <table> in a div with this class at render time. */
|
|
376
|
+
.prose-imported .table-scroll {
|
|
377
|
+
margin: 16px 0;
|
|
378
|
+
overflow-x: auto;
|
|
379
|
+
}
|
|
380
|
+
.prose-imported table {
|
|
381
|
+
width: 100%;
|
|
382
|
+
border-collapse: collapse;
|
|
383
|
+
font-size: var(--text-dense);
|
|
384
|
+
line-height: 1.5;
|
|
385
|
+
}
|
|
386
|
+
.prose-imported th,
|
|
387
|
+
.prose-imported td {
|
|
388
|
+
padding: 8px 12px 8px 0;
|
|
389
|
+
text-align: left;
|
|
390
|
+
vertical-align: top;
|
|
391
|
+
border-bottom: 1px solid var(--color-gray-alpha-300);
|
|
392
|
+
}
|
|
393
|
+
.prose-imported th {
|
|
394
|
+
font-weight: var(--font-weight-medium);
|
|
395
|
+
color: var(--color-gray-1000);
|
|
396
|
+
white-space: nowrap;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/* Imported Markdown can't take the authored pull quote's 3px rounded bar
|
|
400
|
+
(glass/type/assets/prose.tsx), so it keeps a plain 2px rule. */
|
|
401
|
+
.prose-imported blockquote {
|
|
402
|
+
padding-left: 15px;
|
|
403
|
+
border-left: 2px solid var(--color-gray-alpha-300);
|
|
404
|
+
color: var(--color-faint);
|
|
405
|
+
}
|
|
406
|
+
.prose-imported hr {
|
|
407
|
+
margin: 32px 0;
|
|
408
|
+
border: 0;
|
|
409
|
+
border-top: 1px solid var(--color-gray-alpha-300);
|
|
410
|
+
}
|
|
411
|
+
.prose-imported img {
|
|
412
|
+
max-width: 100%;
|
|
413
|
+
height: auto;
|
|
414
|
+
border-radius: 12px;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
# Glass Typography
|
|
2
|
+
|
|
3
|
+
> **Glass preset.** This is the type system running on [modimihir.com](https://modimihir.com), with its real values, meant to be copied whole. Read it beside `domains/type/guide.md`: the laws and their reasons are there, and this guide holds only what Glass decides — its ladder, its values, its recipes and why it chose them. Under any other preset, the domain guide leads and this guide is only a worked example.
|
|
4
|
+
|
|
5
|
+
For interfaces where the text *is* the product — portfolios, docs, writing. The aim: **a reader should never notice the typography, only the hierarchy.** The example is Archivo in a 640px measure with Tailwind v4 tokens.
|
|
6
|
+
|
|
7
|
+
## 1. The role ladder
|
|
8
|
+
|
|
9
|
+
Each role gets a size in steps of 1–2px, as densely as the roles demand (domain law 1):
|
|
10
|
+
|
|
11
|
+
| Role | Size | Leading | Tracking | Weight | Text tier |
|
|
12
|
+
|---|---|---|---|---|---|
|
|
13
|
+
| Index page title (h1) | 18 | 1.25 | −0.02em | 500 | primary |
|
|
14
|
+
| Detail page title (h1) | 16 | 1.3 | −0.015em | 500 | primary |
|
|
15
|
+
| Section heading (h2) | 16 | 1.2 | −0.015em | 500 | primary |
|
|
16
|
+
| Prose heading (h2) | 16 | 1.2 | −0.015em | 500 | primary |
|
|
17
|
+
| Keyline — a section's claim | 14 | 1.6 | — | 500 | primary |
|
|
18
|
+
| Lead paragraph | 14 | 1.9 | — | 400 | primary |
|
|
19
|
+
| Prose body | 14 | 1.75 | — | 400 | primary |
|
|
20
|
+
| Pull quote | 14 | 1.75 | — | 400 | primary |
|
|
21
|
+
| Dek / standfirst | 14 | 1.55 | — | 400 | muted |
|
|
22
|
+
| Reading paragraph | 14 | 1.65 | — | 400 | primary or muted |
|
|
23
|
+
| Sub-heading (h3/h4) | 14 | 1.4 | — | 500 | primary |
|
|
24
|
+
| UI row | 14 | 1 | — | 400 | secondary |
|
|
25
|
+
| Summary line | 13.5 | 1.6 | — | 400 | primary |
|
|
26
|
+
| List row / dense label | 13 | 1.4 | — | 400 | secondary |
|
|
27
|
+
| Note, caption, meta, fact | 13 | 1.5–1.6 | — | 400 | faint or quiet |
|
|
28
|
+
| Code | 12.5 | 1.6 | — | 400 mono | secondary |
|
|
29
|
+
| Tag, micro-label | 12 | — | — | 400 | muted |
|
|
30
|
+
| Tooltip | 11 | 1 | — | 400 | inverted |
|
|
31
|
+
| Lettermark | 8.5 | 1 | +0.02em | 500 | muted |
|
|
32
|
+
| Card lettering | 7.5 | 1.2 | −0.01em | 600 | primary |
|
|
33
|
+
|
|
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
|
+
|
|
36
|
+
**Eighteen is the ceiling, and it's lower than you think.** An earlier ladder topped out at 30px, with a 19px prose heading, a 17px dek and a 16px prose body. Case studies and articles opened at 30px and every other page at 18px, so a case study felt like a different site (domain law 2). The rest of the site ran on **two sizes**, 16 for headings and 14 for everything read; the detail templates came down to those, and the four sizes were deleted, not replaced. One element likewise takes one role on every page, and sibling controls match (domain law 2).
|
|
37
|
+
|
|
38
|
+
### When size stops doing the work
|
|
39
|
+
|
|
40
|
+
Collapse the ladder and eight roles share 14px — the constraint the system is *for*. Each separates on a different axis (domain law 4):
|
|
41
|
+
|
|
42
|
+
| Role | The signal | Not |
|
|
43
|
+
|---|---|---|
|
|
44
|
+
| Keyline | weight 500, and 36px of air on both sides | a size step |
|
|
45
|
+
| Lead | leading — 1.9 against the body's 1.75 | a size step |
|
|
46
|
+
| Pull quote | a 3px rounded bar and a 16px indent | a size step |
|
|
47
|
+
| Dek | the muted tier, and tighter leading at 1.55 | a size step |
|
|
48
|
+
|
|
49
|
+
At one size, the 17px keyline that once shouted over a 16px heading can't return (domain law 3).
|
|
50
|
+
|
|
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
|
+
|
|
53
|
+
**Nothing in this ladder resizes.** The old step, a 30px title dropping to 26px below `sm`, went with the size that needed it (the domain's responsive-sizes dial).
|
|
54
|
+
|
|
55
|
+
→ Full derivation, including how to add a role without bloating the ladder: `glass/type/scale.md`
|
|
56
|
+
|
|
57
|
+
## 2. Two weights
|
|
58
|
+
|
|
59
|
+
Load 400 and 500. That is the system.
|
|
60
|
+
|
|
61
|
+
- **400** — all body copy, all reading copy, all quiet UI.
|
|
62
|
+
- **500** — headings at every level, active states, `<strong>`, lettermarks.
|
|
63
|
+
|
|
64
|
+
Quiet or lifted is set by what the line is doing there, one lift per group, one weight per set of titles; no 600, no 700 (domain law 5).
|
|
65
|
+
|
|
66
|
+
**The one exception** is a single `font-semibold`, on 7.5px folder-card lettering. Below about 8px a 500 doesn't survive rasterisation as a weight — too few pixels across a stem — so it's 600 or nothing. It's below the readable floor, on something recognised rather than read: the only kind of exception to accept, and one is the right number.
|
|
67
|
+
|
|
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
|
+
|
|
70
|
+
→ Weight and tracking in depth, including when 300 is legitimate: `glass/type/weight-and-tracking.md`
|
|
71
|
+
|
|
72
|
+
## 3. Leading runs inverse to size
|
|
73
|
+
|
|
74
|
+
Glass's values on the three curves (domain law 6):
|
|
75
|
+
|
|
76
|
+
| Job | Range | Rule |
|
|
77
|
+
|---|---|---|
|
|
78
|
+
| **Display** — titles, headings | 1.2 – 1.3 | Bigger means tighter. A 16px heading at 1.65 reads as two loose lines, not one heading. |
|
|
79
|
+
| **Reading** — paragraphs, prose | 1.55 – 1.9 | Longer passages, more open: prose body 14/1.75 against a UI paragraph's 14/1.55. |
|
|
80
|
+
| **UI** — rows, tooltips, labels | 1 – 1.4 | Single-line text in a fixed-height box takes `leading-none`. |
|
|
81
|
+
|
|
82
|
+
**Two headings at one size can still differ**: a detail-page title is 16/1.3 and a section heading 16/1.2, because titles wrap and section headings are one word. In prose the 0.15 step between lead (1.9) and body (1.75) is the whole signal, so it must be exact.
|
|
83
|
+
|
|
84
|
+
## 4. Tracking is negative and scales with size
|
|
85
|
+
|
|
86
|
+
```
|
|
87
|
+
≤14px 0
|
|
88
|
+
16px −0.015em (headings and detail-page titles)
|
|
89
|
+
18px −0.02em (index-page titles — the top of the ladder)
|
|
90
|
+
7.5px −0.01em (card lettering, which is set solid and needs pulling in)
|
|
91
|
+
tiny caps +0.02em (8.5px lettermarks, 11px initials)
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Why each direction, and never on body copy: domain law 7. With no large sizes the system barely needs tracking — two negative values, 2px apart.
|
|
95
|
+
|
|
96
|
+
## 5. Emphasis without italic or bold
|
|
97
|
+
|
|
98
|
+
```css
|
|
99
|
+
/* Italics are switched off globally. */
|
|
100
|
+
em, i { font-style: normal; }
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Archivo's true italic is a separate file whose slant fights its geometric letterforms, so emphasis moves to two axes the system already has:
|
|
104
|
+
|
|
105
|
+
- `*emphasis*` → the **muted** text colour. It recedes rather than tilts.
|
|
106
|
+
- `**strong**` → weight 500 + the **primary** text colour. It advances.
|
|
107
|
+
|
|
108
|
+
```tsx
|
|
109
|
+
// From the inline markdown renderer
|
|
110
|
+
if (bold) return <strong className="font-medium text-gray-1000">{text}</strong>;
|
|
111
|
+
if (em) return <em className="text-muted">{text}</em>;
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Why the mapping works, and the semantic tags it keeps: domain law 9 and `domains/type/prose-and-emphasis.md`.
|
|
115
|
+
|
|
116
|
+
## 6. Numerals
|
|
117
|
+
|
|
118
|
+
```css
|
|
119
|
+
body { font-variant-numeric: tabular-nums; }
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Glass sets the numerals dial (domain law 10) globally, not per component: text-heavy interfaces put numbers in columns that should align — date ranges, timestamps, read times, versions. Proportional numerals win only inside a running sentence, too rare to set the default.
|
|
123
|
+
|
|
124
|
+
## 7. One family, self-hosted
|
|
125
|
+
|
|
126
|
+
One family, plus the one second register that passes domain law 5:
|
|
127
|
+
|
|
128
|
+
```css
|
|
129
|
+
--font-sans: var(--font-archivo), system-ui, -apple-system, "Helvetica Neue", sans-serif;
|
|
130
|
+
/* Code, commands and paths. System stack on purpose — a second webfont for the
|
|
131
|
+
few monospace runs on the site isn't worth the request. */
|
|
132
|
+
--font-mono: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, monospace;
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
A few `<code>` spans and two code blocks don't justify a request. *A second family earns a download only if it's carrying reading copy.*
|
|
136
|
+
|
|
137
|
+
### Self-host, and let the loader generate the fallback
|
|
138
|
+
|
|
139
|
+
```tsx
|
|
140
|
+
// next/font — fetched at build time, served from your own origin.
|
|
141
|
+
const archivo = Archivo({ subsets: ["latin"], display: "swap", variable: "--font-archivo" });
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Google's CDN cost the site three round trips over two third-party hosts before the font was requested; self-hosted, it rides the existing connection with a `<link rel="preload">` alongside the HTML (domain law 12). `display: swap` stays, and `next/font` synthesises the metric-matched fallback, a local face scaled to Archivo's metrics, so a swap that *does* happen doesn't move the line.
|
|
145
|
+
|
|
146
|
+
**Where a swap costs most here** is the opening screen, one line alone on the page; tune loading for that frame.
|
|
147
|
+
|
|
148
|
+
```css
|
|
149
|
+
body {
|
|
150
|
+
-webkit-font-smoothing: antialiased;
|
|
151
|
+
text-rendering: optimizeLegibility;
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## 8. Prose you don't control
|
|
156
|
+
|
|
157
|
+
Imported Markdown, styled by element in one scoped class on the same ladder (domain law 11):
|
|
158
|
+
|
|
159
|
+
```css
|
|
160
|
+
.skill-prose { font-size: 14px; line-height: 1.65; color: var(--color-muted); }
|
|
161
|
+
.skill-prose h2 { margin: 44px 0 12px; font-size: 16px; font-weight: 500; }
|
|
162
|
+
.skill-prose h3 { margin: 32px 0 10px; font-size: 14px; font-weight: 500; }
|
|
163
|
+
.skill-prose h4 { margin: 24px 0 8px; font-size: 13px; font-weight: 500; }
|
|
164
|
+
.skill-prose > :first-child { margin-top: 0; }
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
What Glass adds to the domain's mapping (`domains/type/prose-and-emphasis.md`):
|
|
168
|
+
|
|
169
|
+
- **`h4` at 13px, secondary tier.**
|
|
170
|
+
- **A wrapping `.table-scroll` div on each table**, and `overflow-x: auto` on the `<pre>`.
|
|
171
|
+
- **Its own `<ul>` marker**: `list-style: none` and a 3px `::before` dot matching the hand-rolled bullets, because a UA disc at 14px is far too heavy.
|
|
172
|
+
|
|
173
|
+
## 9. UI copy and reading copy are different systems
|
|
174
|
+
|
|
175
|
+
Glass's two systems (domain law 8):
|
|
176
|
+
|
|
177
|
+
| | UI copy | Reading copy |
|
|
178
|
+
|---|---|---|
|
|
179
|
+
| Size | 11–16 | 13–18 |
|
|
180
|
+
| Leading | 1 – 1.55 | 1.55 – 1.9 |
|
|
181
|
+
| Lives in | chrome, rows, labels, nav | a constrained measure |
|
|
182
|
+
| Optimised for | scanning, glancing | sustained reading |
|
|
183
|
+
| Colour | often secondary/quiet | primary |
|
|
184
|
+
|
|
185
|
+
The ranges overlap, so leading and measure carry the distinction. Reading leading on a UI row is the usual symptom here: it grows, and the dock loses its rhythm.
|
|
186
|
+
|
|
187
|
+
**Labels are sentence case, generated ones included** (patterns law 1), and **icons beside text** follow domain law 13: 13px text, a 13–14px icon, a 4–6px gap.
|
|
188
|
+
|
|
189
|
+
→ How prose blocks are set — headings, quotes, lists, spacing: `glass/type/long-form-prose.md`
|
|
190
|
+
|
|
191
|
+
## Assets
|
|
192
|
+
|
|
193
|
+
- `glass/type/assets/type-tokens.css` — the ladder as CSS custom properties and Tailwind v4 `@theme` tokens, the role classes, and a scoped block for imported Markdown.
|
|
194
|
+
- `glass/type/assets/prose.tsx` — the long-form renderer: the two-size column, the `lead` / `keyline` / `note` / `quote` / `figure` blocks and their axes, inline emphasis and link mapping.
|
|
195
|
+
|
|
196
|
+
## Applying this to an existing codebase
|
|
197
|
+
|
|
198
|
+
Inventory and sweep as the domain guide does (**Auditing existing code**). Moving a codebase onto Glass, the order that pays:
|
|
199
|
+
|
|
200
|
+
1. **Collapse weights first** — the highest-leverage change, and the most visible.
|
|
201
|
+
2. **Map sizes onto the ladder above**, by role.
|
|
202
|
+
3. **Set leading per curve.** Most drift is here.
|
|
203
|
+
4. **Add tracking only above 14px.**
|
|
204
|
+
5. **Switch emphasis to weight + colour.** Then delete the italic font file.
|
|
205
|
+
6. **Sweep**, moving raw sizes onto Glass's role tokens. → Its `tailwind-merge` registration: `glass/type/scale.md`
|
|
206
|
+
|
|
207
|
+
## Checklist
|
|
208
|
+
|
|
209
|
+
The domain checklist, plus Glass's values:
|
|
210
|
+
|
|
211
|
+
- [ ] Two weights loaded and used, 400 and 500; 600 only on the 7.5px card lettering.
|
|
212
|
+
- [ ] Every size is on the ladder above; nothing above 18px; no role resizes.
|
|
213
|
+
- [ ] The 14px roles separate as the table in §1 says.
|
|
214
|
+
- [ ] Tracking: −0.015em at 16px, −0.02em at 18px, +0.02em on lettermarks and initials, −0.01em on card lettering; nothing else.
|
|
215
|
+
- [ ] No `font-style: italic` in output; `em` maps to muted; `<strong>` is 500 + primary.
|
|
216
|
+
- [ ] `tabular-nums` global; live-updating numbers verified stable.
|
|
217
|
+
- [ ] Archivo self-hosted through the loader, `display: swap`, generated fallback; mono a system stack.
|
|
218
|
+
- [ ] Imported prose under one scoped class, with the §8 additions.
|
|
219
|
+
- [ ] Sentence case on every label, generated ones included.
|
|
220
|
+
- [ ] Icons beside text sized to it: 13px text, 13–14px icon, 4–6px gap.
|