trykeyline 0.1.0 → 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/LICENSE +0 -16
- 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 +22 -0
- package/plugins/keyline/skills/keyline/SKILL.md +22 -32
- package/plugins/keyline/skills/keyline/commands/audit.md +8 -3
- package/plugins/keyline/skills/keyline/commands/critique.md +3 -26
- package/plugins/keyline/skills/keyline/commands/init.md +5 -4
- package/plugins/keyline/skills/keyline/commands/polish.md +2 -2
- package/plugins/keyline/skills/keyline/commands/refine.md +6 -16
- package/plugins/keyline/skills/keyline/commands/sync.md +1 -3
- package/plugins/keyline/skills/keyline/commands/ux-laws.md +26 -0
- package/plugins/keyline/skills/keyline/domains/color/deriving-a-ramp.md +1 -1
- package/plugins/keyline/skills/keyline/domains/color/guide.md +10 -46
- package/plugins/keyline/skills/keyline/domains/color/status-and-exceptions.md +2 -2
- package/plugins/keyline/skills/keyline/domains/depth/elevation-recipes.md +2 -8
- package/plugins/keyline/skills/keyline/domains/depth/guide.md +12 -41
- package/plugins/keyline/skills/keyline/domains/layout/guide.md +31 -56
- package/plugins/keyline/skills/keyline/domains/layout/measure-and-rhythm.md +16 -16
- package/plugins/keyline/skills/keyline/domains/layout/radius-lines-and-layers.md +2 -2
- package/plugins/keyline/skills/keyline/domains/motion/guide.md +12 -47
- package/plugins/keyline/skills/keyline/domains/motion/patterns.md +1 -1
- package/plugins/keyline/skills/keyline/domains/motion/performance.md +1 -1
- package/plugins/keyline/skills/keyline/domains/patterns/guide.md +20 -62
- package/plugins/keyline/skills/keyline/domains/patterns/iteration.md +1 -9
- package/plugins/keyline/skills/keyline/domains/type/guide.md +13 -37
- package/plugins/keyline/skills/keyline/domains/type/ladders.md +1 -1
- package/plugins/keyline/skills/keyline/domains/type/type-audit.md +5 -8
- package/plugins/keyline/skills/keyline/glass/color/assets/color-tokens.css +34 -165
- package/plugins/keyline/skills/keyline/glass/color/assets/theme.ts +10 -70
- package/plugins/keyline/skills/keyline/glass/color/guide.md +3 -30
- package/plugins/keyline/skills/keyline/glass/color/intent-ramp.md +2 -34
- package/plugins/keyline/skills/keyline/glass/color/theming.md +5 -72
- 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/guide.md +4 -73
- 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 -138
- package/plugins/keyline/skills/keyline/glass/layout/guide.md +10 -15
- package/plugins/keyline/skills/keyline/glass/layout/measure-and-rhythm.md +4 -6
- package/plugins/keyline/skills/keyline/glass/layout/radius-and-elevation.md +3 -2
- 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 +10 -41
- 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/shared-element.md +1 -1
- package/plugins/keyline/skills/keyline/glass/motion/sound.md +10 -80
- package/plugins/keyline/skills/keyline/glass/type/assets/prose.tsx +8 -43
- package/plugins/keyline/skills/keyline/glass/type/assets/type-tokens.css +49 -86
- package/plugins/keyline/skills/keyline/glass/type/guide.md +5 -7
- 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 +6 -3
- 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/design-md.md +4 -4
- package/plugins/keyline/skills/keyline/system/guide.md +4 -8
- package/plugins/keyline/skills/keyline/system/presets.md +16 -16
- package/plugins/keyline/skills/keyline/system/roles.md +8 -8
|
@@ -59,65 +59,13 @@ The desktop panel needs no equivalent: it *mounts fresh* on open and reads the v
|
|
|
59
59
|
|
|
60
60
|
## Case 2: the table of contents
|
|
61
61
|
|
|
62
|
-
Same tree, two containers, shared row rendering.
|
|
63
|
-
|
|
64
|
-
```tsx
|
|
65
|
-
/** Shared — used by both presentations. */
|
|
66
|
-
function Rows({ nodes, active, parents, onJump }) { … }
|
|
67
|
-
|
|
68
|
-
/** The sticky rail, in the left margin on wide screens. */
|
|
69
|
-
export function SectionNav({ nodes, active, onJump }) {
|
|
70
|
-
return (
|
|
71
|
-
<nav aria-label="On this page">
|
|
72
|
-
<ul className="flex flex-col">
|
|
73
|
-
<Rows … />
|
|
74
|
-
</ul>
|
|
75
|
-
</nav>
|
|
76
|
-
);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/** The narrow-screen disclosure — one line above the article. */
|
|
80
|
-
export function SectionNavCompact({ nodes, active, onJump }) {
|
|
81
|
-
const [open, setOpen] = useState(false);
|
|
82
|
-
const jump = (id: string) => { setOpen(false); onJump(id); };
|
|
83
|
-
return (
|
|
84
|
-
<details open={open} onToggle={(e) => setOpen(e.currentTarget.open)}
|
|
85
|
-
className="group rounded-xl border border-gray-alpha-300">
|
|
86
|
-
<summary className="… [&::-webkit-details-marker]:hidden">
|
|
87
|
-
On this page
|
|
88
|
-
<span className="… group-open:rotate-180 motion-reduce:transition-none">
|
|
89
|
-
<ChevronDownIcon />
|
|
90
|
-
</span>
|
|
91
|
-
</summary>
|
|
92
|
-
<ul className="… border-t border-gray-alpha-300">
|
|
93
|
-
<Rows … onJump={jump} />
|
|
94
|
-
</ul>
|
|
95
|
-
</details>
|
|
96
|
-
);
|
|
97
|
-
}
|
|
98
|
-
```
|
|
99
|
-
|
|
100
|
-
Both are rendered; CSS picks one:
|
|
101
|
-
|
|
102
|
-
```tsx
|
|
103
|
-
<div className="absolute inset-y-0 left-8 hidden w-[196px] xl:block">
|
|
104
|
-
<div className="sticky top-28 max-h-[calc(100dvh-11rem)] overflow-y-auto">
|
|
105
|
-
<SectionNav {...nav} />
|
|
106
|
-
</div>
|
|
107
|
-
</div>
|
|
108
|
-
|
|
109
|
-
<div className="reveal mt-8 xl:hidden">
|
|
110
|
-
<SectionNavCompact {...nav} />
|
|
111
|
-
</div>
|
|
112
|
-
```
|
|
113
|
-
|
|
114
|
-
The props are memoised so both get a stable object:
|
|
62
|
+
Same tree, two containers, shared row rendering: `glass/layout/assets/SectionNav.tsx`. Both are rendered and CSS picks one — the rail in the 1280px frame from `xl` (§2 of the guide), the compact `<details>` above the article below that (`reveal mt-8 xl:hidden`). The props are memoised so both get one stable object:
|
|
115
63
|
|
|
116
64
|
```tsx
|
|
117
65
|
const nav = useMemo(() => ({ nodes: toc, active, onJump: jump }), [active, jump]);
|
|
118
66
|
```
|
|
119
67
|
|
|
120
|
-
The compact version is a `<details>` (layout law 11)
|
|
68
|
+
The compact version is a `<details>` (layout law 11), and costs **one line** above the article, which is what makes a nav in the reading flow acceptable.
|
|
121
69
|
|
|
122
70
|
## Case 3: two views of one collection, chosen by the reader
|
|
123
71
|
|
|
@@ -125,24 +73,7 @@ The first two cases fork on a *device* capability; the gallery's **wall** and **
|
|
|
125
73
|
|
|
126
74
|
### The state can't live in either component
|
|
127
75
|
|
|
128
|
-
The control and what it controls are siblings, so a
|
|
129
|
-
|
|
130
|
-
```ts
|
|
131
|
-
export type GalleryLayout = "grid" | "roll";
|
|
132
|
-
let current: GalleryLayout = "grid";
|
|
133
|
-
|
|
134
|
-
const listeners = new Set<(layout: GalleryLayout) => void>();
|
|
135
|
-
export function onGalleryLayoutChange(fn: (l: GalleryLayout) => void) {
|
|
136
|
-
listeners.add(fn);
|
|
137
|
-
return () => { listeners.delete(fn); };
|
|
138
|
-
}
|
|
139
|
-
export function setGalleryLayout(layout: GalleryLayout) {
|
|
140
|
-
current = layout;
|
|
141
|
-
for (const fn of listeners) fn(layout);
|
|
142
|
-
}
|
|
143
|
-
```
|
|
144
|
-
|
|
145
|
-
The shape of a theme or sound store, for the same reason: neither consumer can be the other's parent without distorting the layout.
|
|
76
|
+
The control and what it controls are siblings, so a module store owns the value (its code is §6 of the guide), mirrored in a hook:
|
|
146
77
|
|
|
147
78
|
```ts
|
|
148
79
|
// Mirror it in a hook. Read in an EFFECT, not during render: on a
|
|
@@ -175,10 +106,6 @@ Find the item nearest the viewport's centre before the outgoing layout unmounts
|
|
|
175
106
|
|
|
176
107
|
Nothing is cropped, so `object-cover` has nothing to do; nothing shifts on load; and one image travels between layouts undistorted, because both ends compute to the same shape.
|
|
177
108
|
|
|
178
|
-
## Hover in Tailwind
|
|
179
|
-
|
|
180
|
-
Tailwind v4's `hover:` variant already compiles to a `(hover: hover)` guard, so utility hover states are safe by default. Imperative JavaScript needs the explicit check.
|
|
181
|
-
|
|
182
109
|
## The touch-versus-hover audit
|
|
183
110
|
|
|
184
111
|
Glass's answers to the audit in `domains/layout/forks-gating-and-disclosures.md`:
|
|
@@ -213,19 +140,6 @@ The last is a hover card *below* its row on narrow screens and *beside* it at `x
|
|
|
213
140
|
| `sm` | 640px | Mobile dock ↔ desktop dock. Page padding. Masonry columns. Stack columns. |
|
|
214
141
|
| `xl` | 1280px | The TOC rail appears. The hover card moves beside its row. |
|
|
215
142
|
|
|
216
|
-
Two for the whole site, both derived from content. Both are viewport breakpoints only because here the column *is* the page. `sm` coincides with the reading measure: below it the column is padding-limited, the natural place for the layout to change character. `xl` is where a 640px column finally leaves margin for a 196px rail. Type size is *not* on the list
|
|
217
|
-
|
|
218
|
-
## Breakpoints on the container
|
|
219
|
-
|
|
220
|
-
Where a pane shares the screen with something that opens and closes, it breaks on its own width (layout law 8). In Tailwind:
|
|
221
|
-
|
|
222
|
-
```tsx
|
|
223
|
-
<section className="@container"> {/* the pane, not the page */}
|
|
224
|
-
<div className="grid @2xl:grid-cols-[minmax(240px,1fr)_2fr]">
|
|
225
|
-
<List />
|
|
226
|
-
<Detail className="@container" /> {/* sizes its own insides */}
|
|
227
|
-
</div>
|
|
228
|
-
</section>
|
|
229
|
-
```
|
|
143
|
+
Two for the whole site, both derived from content. Both are viewport breakpoints only because here the column *is* the page. `sm` coincides with the reading measure: below it the column is padding-limited, the natural place for the layout to change character. `xl` is where a 640px column finally leaves margin for a 196px rail. Type size is *not* on the list (`glass/type/scale.md`).
|
|
230
144
|
|
|
231
|
-
|
|
145
|
+
Glass uses no container breakpoints; where a pane shares the screen with something that opens and closes, see `domains/layout/forks-gating-and-disclosures.md`.
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* copied as links; the click handler takes over to scroll smoothly and keep
|
|
13
13
|
* the URL in step.
|
|
14
14
|
*
|
|
15
|
-
* Pair with useActiveSection (`glass/motion/
|
|
15
|
+
* Pair with useActiveSection (`glass/motion/assets/useScrollTick.ts`) for the scroll spy, and
|
|
16
16
|
* give every heading a scroll-margin-top just under its READING_LINE.
|
|
17
17
|
*/
|
|
18
18
|
|
|
@@ -59,11 +59,8 @@ function ChevronDownIcon() {
|
|
|
59
59
|
/**
|
|
60
60
|
* One row.
|
|
61
61
|
*
|
|
62
|
-
* Top-level rows sit flush and carry no marker
|
|
63
|
-
*
|
|
64
|
-
* signalled by colour alone (WCAG 1.4.1). Weight is a genuine second channel:
|
|
65
|
-
* it survives greyscale, colour-blindness and low-contrast displays, and it's
|
|
66
|
-
* cheaper than a background pill in a list that's meant to be quiet.
|
|
62
|
+
* Top-level rows sit flush and carry no marker; the active one takes weight
|
|
63
|
+
* 500 as well as colour, so state is never on colour alone (type law 5).
|
|
67
64
|
*
|
|
68
65
|
* Nested rows carry a horizontal marker that extends and darkens when active,
|
|
69
66
|
* and which doubles as their indent. It sits in a FIXED-WIDTH box so growing
|
|
@@ -3,43 +3,15 @@
|
|
|
3
3
|
───────────────────────────────────────────────────────────────────────── */
|
|
4
4
|
|
|
5
5
|
@theme {
|
|
6
|
-
/*
|
|
7
|
-
|
|
8
|
-
characters per line: the top of the comfortable range, which is the
|
|
9
|
-
right place for a site that expects sustained reading (long lines are
|
|
10
|
-
efficient once the reader is committed, and 1.75 leading pays for the
|
|
11
|
-
longer return sweep).
|
|
12
|
-
|
|
13
|
-
EVERY page uses this. The payoff isn't visual consistency, it's
|
|
14
|
-
positional continuity: clicking from a summary into its detail view
|
|
15
|
-
doesn't move the text, so the transition reads as expansion rather than
|
|
16
|
-
as arriving at a different document. */
|
|
6
|
+
/* 640px outer, 32px padding → 576px of text, on every page. Why:
|
|
7
|
+
glass/layout/measure-and-rhythm.md. */
|
|
17
8
|
--container-col: 640px;
|
|
18
|
-
|
|
19
|
-
/* The outer frame that rails anchor to, alongside the column. Anchoring
|
|
20
|
-
both to the SAME frame is what makes the gap between them hold steady
|
|
21
|
-
from 1280px up — a rail positioned against the viewport instead drifts
|
|
22
|
-
away from the content it indexes as the window grows. */
|
|
23
|
-
--container-frame: 1280px;
|
|
24
|
-
--rail-width: 196px;
|
|
25
9
|
}
|
|
26
10
|
|
|
27
11
|
@layer base {
|
|
28
|
-
/*
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
drops abruptly to zero where it meets the straight edge. The eye reads
|
|
32
|
-
that discontinuity as a slight hardness. A superellipse varies curvature
|
|
33
|
-
continuously into the edge.
|
|
34
|
-
|
|
35
|
-
superellipse(k) uses exponent 2^k — k=1 is a normal round corner, k=2 a
|
|
36
|
-
full iOS squircle, so 1.5 lands about halfway. Not 2, because a full
|
|
37
|
-
squircle is noticeable at small sizes (it starts to look like a swollen
|
|
38
|
-
rectangle); halfway reads as "well-drawn corner" rather than as an
|
|
39
|
-
effect.
|
|
40
|
-
|
|
41
|
-
Unsupported browsers ignore it and fall back to normal rounding. Keep it
|
|
42
|
-
that way — nothing else in the layout should depend on it. */
|
|
12
|
+
/* Continuous corners on every rounded element. 1.5 sits halfway between a
|
|
13
|
+
round corner (1) and a full squircle (2), which swells at small sizes.
|
|
14
|
+
Unsupported browsers fall back to normal rounding; nothing depends on it. */
|
|
43
15
|
*,
|
|
44
16
|
*::before,
|
|
45
17
|
*::after {
|
|
@@ -54,9 +26,8 @@
|
|
|
54
26
|
padding: 0 32px;
|
|
55
27
|
}
|
|
56
28
|
|
|
57
|
-
/* Skip link
|
|
58
|
-
|
|
59
|
-
page needs a matching id="main". */
|
|
29
|
+
/* Skip link: parked with a transform, never display:none (which drops it
|
|
30
|
+
from the tab order). Every page needs a matching id="main". */
|
|
60
31
|
.skip {
|
|
61
32
|
position: absolute;
|
|
62
33
|
left: 16px;
|
|
@@ -69,20 +40,14 @@
|
|
|
69
40
|
text-decoration: none;
|
|
70
41
|
border-radius: 6px;
|
|
71
42
|
transform: translateY(-200%);
|
|
72
|
-
transition: transform
|
|
43
|
+
transition: transform var(--duration-state) var(--ease-geist);
|
|
73
44
|
}
|
|
74
45
|
.skip:focus {
|
|
75
46
|
transform: translateY(0);
|
|
76
47
|
}
|
|
77
48
|
}
|
|
78
49
|
|
|
79
|
-
/*
|
|
80
|
-
Print. A real presentation, not an afterthought.
|
|
81
|
-
|
|
82
|
-
The .reveal reset is the one people forget: scroll-triggered content is at
|
|
83
|
-
opacity: 0 until an IntersectionObserver fires, and printing never fires
|
|
84
|
-
it — so the page prints blank without this.
|
|
85
|
-
───────────────────────────────────────────────────────────────────────── */
|
|
50
|
+
/* Print. The .reveal reset lives in glass/motion/assets/motion-tokens.css. */
|
|
86
51
|
@media print {
|
|
87
52
|
nav,
|
|
88
53
|
footer,
|
|
@@ -96,11 +61,6 @@
|
|
|
96
61
|
font-size: 11pt;
|
|
97
62
|
}
|
|
98
63
|
|
|
99
|
-
.reveal {
|
|
100
|
-
opacity: 1;
|
|
101
|
-
transform: none;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
64
|
/* An underlined URL-less link is noise on paper. */
|
|
105
65
|
a {
|
|
106
66
|
color: inherit;
|
|
@@ -108,92 +68,5 @@
|
|
|
108
68
|
}
|
|
109
69
|
}
|
|
110
70
|
|
|
111
|
-
/*
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
── The radius ladder ──────────────────────────────────────────────────
|
|
115
|
-
Nesting rule: INNER = OUTER − the padding between them. Geometry, not
|
|
116
|
-
taste — two concentric rounded rectangles share a centre of curvature
|
|
117
|
-
only if their radii differ by exactly the gap between their edges.
|
|
118
|
-
|
|
119
|
-
rounded-full pills, tags, dots, markers
|
|
120
|
-
rounded-[5px] a lettermark box
|
|
121
|
-
rounded-md 6 tooltips, the skip link
|
|
122
|
-
rounded-[9px] 9 small cards in a stack
|
|
123
|
-
rounded-[10px]10 avatar inside a button · a list highlight bar
|
|
124
|
-
rounded-xl 12 cards, hover panels, disclosures, icon buttons
|
|
125
|
-
rounded-2xl 16 outer containers — a dock, a sheet, a pocket
|
|
126
|
-
|
|
127
|
-
Worked example:
|
|
128
|
-
Dock rounded-2xl (16) p-1 (4)
|
|
129
|
-
Icon button rounded-[12px] 16 − 4 = 12 ✓
|
|
130
|
-
Avatar rounded-[10px] 12 − 2 = 10 ✓
|
|
131
|
-
|
|
132
|
-
Radius scales with SIZE, not importance — and sub-proportionally, because
|
|
133
|
-
a large element with a proportional radius reads as a lozenge.
|
|
134
|
-
|
|
135
|
-
── Hairline weights ───────────────────────────────────────────────────
|
|
136
|
-
The choice is about what the line is DOING:
|
|
137
|
-
|
|
138
|
-
0.5px internal articulation — a divider between icon groups inside
|
|
139
|
-
one dock. Renders as a true sub-pixel hairline on retina and
|
|
140
|
-
rounds UP to 1px elsewhere, so it degrades rather than vanishes.
|
|
141
|
-
1px a real edge — where a card ends.
|
|
142
|
-
2px a mark with editorial weight — a blockquote rule, a focus ring.
|
|
143
|
-
|
|
144
|
-
On translucent surfaces use the ALPHA ramp (border-gray-alpha-400). A
|
|
145
|
-
solid border on glass doesn't participate in what's behind it and reads as
|
|
146
|
-
a pasted-on frame.
|
|
147
|
-
|
|
148
|
-
An <hr> needs border-0 before you give it a background, or you get the UA
|
|
149
|
-
border AND your line. Non-semantic dividers get aria-hidden.
|
|
150
|
-
|
|
151
|
-
── The z-index ladder ─────────────────────────────────────────────────
|
|
152
|
-
Ordered by PERMANENCE, not importance. Gaps of 10 so something can be
|
|
153
|
-
inserted without renumbering.
|
|
154
|
-
|
|
155
|
-
100 skip link — must beat everything, including modals
|
|
156
|
-
60 a once-a-session overlay — over the dock, under the skip link
|
|
157
|
-
50 fixed navigation
|
|
158
|
-
40 tap-outside catcher — under the nav, over the page
|
|
159
|
-
30 a pocket front, above the cards it contains
|
|
160
|
-
20 hover cards, dropdowns
|
|
161
|
-
10 a hovered item lifting above its siblings
|
|
162
|
-
-10 a sliding highlight, behind content inside `isolate`
|
|
163
|
-
|
|
164
|
-
60 is what the gaps were for: a greeting overlay arrived long after this
|
|
165
|
-
ladder was set and slotted in without touching an existing value.
|
|
166
|
-
|
|
167
|
-
Two pairs to understand rather than copy. 40/50: a full-screen catcher
|
|
168
|
-
must be above the page (to catch the click) and below the menu it
|
|
169
|
-
dismisses (or it eats the menu's own clicks). 60/100: a splash overlay
|
|
170
|
-
covers the navigation — or it isn't covering the page — but must never
|
|
171
|
-
cover the skip link, because a keyboard user tabbing during a two-second
|
|
172
|
-
greeting has to be able to get past it.
|
|
173
|
-
|
|
174
|
-
── Two techniques ─────────────────────────────────────────────────────
|
|
175
|
-
isolate + negative z: a highlight bar behind icons but in front of its
|
|
176
|
-
container's background. `-z-10` alone drops it behind the container;
|
|
177
|
-
`isolate` scopes the negative z inside.
|
|
178
|
-
|
|
179
|
-
pointer-events-none on a full-width strip, auto on the content: a fixed
|
|
180
|
-
nav strip spans the viewport for centring but must not swallow every click
|
|
181
|
-
along the bottom of the page.
|
|
182
|
-
|
|
183
|
-
And the strip carries NO TRANSFORM. A transform makes an element the
|
|
184
|
-
containing block for position:fixed descendants — a full-bleed w-screen
|
|
185
|
-
overlay inside it would be trapped in the strip's bounds. Same class of
|
|
186
|
-
problem as transforms weakening backdrop-filter: when something positioned
|
|
187
|
-
or filtered behaves inexplicably, check every ancestor for transform,
|
|
188
|
-
filter, perspective, contain, and will-change.
|
|
189
|
-
|
|
190
|
-
── Viewport units ─────────────────────────────────────────────────────
|
|
191
|
-
Use dvh, never vh. `vh` refers to the viewport WITHOUT mobile browser
|
|
192
|
-
chrome, so a 100vh element extends under the address bar and its bottom is
|
|
193
|
-
unreachable. `dvh` tracks the actual visible area.
|
|
194
|
-
|
|
195
|
-
── Bottom padding ─────────────────────────────────────────────────────
|
|
196
|
-
Anything with fixed bottom chrome needs pb-28 (112px) audited page by
|
|
197
|
-
page, or the last line sits under the nav. The most common "looks fine on
|
|
198
|
-
my long page" bug.
|
|
199
|
-
───────────────────────────────────────────────────────────────────────── */
|
|
71
|
+
/* The radius ladder, hairlines, z-index ladder, dvh and pb-28 are not classes:
|
|
72
|
+
glass/layout/guide.md and glass/layout/radius-and-elevation.md. */
|
|
@@ -14,7 +14,7 @@ For mostly-text interfaces, where layout should get out of the way. One decision
|
|
|
14
14
|
}
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
-
At most 576px of text,
|
|
17
|
+
At most 576px of text, about 90 characters a line (85–95) at the 14px prose size: past comfortable on purpose, with prose leading opened to match. **Every page uses the class** (layout law 1).
|
|
18
18
|
|
|
19
19
|
- **Non-text content may leave the column**: a masonry image wall has no reading measure, so it goes full-bleed.
|
|
20
20
|
- **An article's cover image stays in it.** Bled wide, it becomes a banner *above* the article, not part of it.
|
|
@@ -40,16 +40,16 @@ Rail and column share one centred 1280px frame, so their gap holds from 1280px u
|
|
|
40
40
|
## 3. Spacing groups; asymmetry is the mechanism
|
|
41
41
|
|
|
42
42
|
```
|
|
43
|
-
mt-
|
|
43
|
+
mt-10 (40px) ← heading
|
|
44
44
|
mb-3 (12px)
|
|
45
45
|
← the paragraph it introduces
|
|
46
46
|
```
|
|
47
47
|
|
|
48
|
-
~3.
|
|
48
|
+
~3.3:1 above to below makes them one unit (layout law 2). It holds at every scale:
|
|
49
49
|
|
|
50
50
|
| Relationship | Inner | Outer | Ratio |
|
|
51
51
|
|---|---|---|---|
|
|
52
|
-
| Heading → its paragraph | 12px below |
|
|
52
|
+
| Heading → its paragraph | 12px below | 40px above | 3.3:1 |
|
|
53
53
|
| List items → the next block | 8px between | 24px below | 3:1 |
|
|
54
54
|
| Bio paragraphs → the links after | 16px between | 32px after last | 2:1 |
|
|
55
55
|
| Section heading → its content | 16–32px | 36px+ between sections | ~2:1 |
|
|
@@ -88,7 +88,8 @@ Glass sets the corner shape on everything, pseudo-elements included, with no exc
|
|
|
88
88
|
|---|---|
|
|
89
89
|
| `0.5px` | Chrome borders, dividers, glass edges |
|
|
90
90
|
| `1px` | Card borders, rings, disclosure outlines |
|
|
91
|
-
| `2px` |
|
|
91
|
+
| `2px` | Focus rings, an imported Markdown quote's rule |
|
|
92
|
+
| `3px` | The pull-quote bar: a rounded flex child, not a border |
|
|
92
93
|
| `1px` (`h-px`) | Table-of-contents markers |
|
|
93
94
|
|
|
94
95
|
Weight follows the job (layout law 6): dividing icon groups inside one dock, 0.5px; defining a card's edge, 1px. On translucent surfaces the edge is the **alpha ramp** (`border-gray-alpha-400`; see `glass/color/guide.md`).
|
|
@@ -228,17 +229,11 @@ Where a custom animation rules out `<details>`, glass collapses height to zero (
|
|
|
228
229
|
|
|
229
230
|
The content stays in the served HTML, for crawlers, `llms.txt` and find-in-page.
|
|
230
231
|
|
|
231
|
-
|
|
232
|
-
@media print {
|
|
233
|
-
nav, footer, .skip { display: none; }
|
|
234
|
-
.reveal { opacity: 1; transform: none; }
|
|
235
|
-
a { color: inherit; text-decoration: none; }
|
|
236
|
-
}
|
|
237
|
-
```
|
|
232
|
+
Print: `glass/layout/assets/layout-tokens.css`, with the `.reveal` reset in `glass/motion/assets/motion-tokens.css`.
|
|
238
233
|
|
|
239
234
|
## Assets
|
|
240
235
|
|
|
241
|
-
- `glass/layout/assets/layout-tokens.css` — column,
|
|
236
|
+
- `glass/layout/assets/layout-tokens.css` — column, continuous corners, skip link, print.
|
|
242
237
|
- `glass/layout/assets/SectionNav.tsx` — two presentations of one tree, with shared row rendering.
|
|
243
238
|
|
|
244
239
|
## Checklist
|
|
@@ -247,10 +242,10 @@ The domain checklist applies in full; these are glass's values for it.
|
|
|
247
242
|
|
|
248
243
|
- [ ] `.col` — 640px, 32px padding — on every page; article covers stay in it.
|
|
249
244
|
- [ ] Rails in the 1280px frame: `left-8`, `w-[196px]`, `sticky top-28`, `max-h-[calc(100dvh-11rem)]`, shown from `xl`.
|
|
250
|
-
- [ ] Prose headings `mt-
|
|
245
|
+
- [ ] Prose headings `mt-10` / `mb-3`; home sections `py-9`; long pages `mt-16 border-t pt-10`, the first `mt-14`.
|
|
251
246
|
- [ ] Rules between Work, Experience and Life only; Watching and Socials follow on.
|
|
252
247
|
- [ ] Radii from the ladder: 6 / 9 / 10 / 12 / 16px and full; `superellipse(1.5)` on everything.
|
|
253
|
-
- [ ] 0.5px chrome, 1px object edges, 2px
|
|
248
|
+
- [ ] 0.5px chrome, 1px object edges, 2px focus rings, the pull quote's 3px rounded bar; `gray-alpha` on translucent surfaces.
|
|
254
249
|
- [ ] `MobileDock` / `DesktopDock` and `SectionNav` / `SectionNavCompact`, sharing data and rows.
|
|
255
250
|
- [ ] The gallery opens on the wall; its layout lives in module memory.
|
|
256
251
|
- [ ] `pb-28` under the dock on every page that can end near the viewport bottom.
|
|
@@ -6,12 +6,10 @@ Glass's values for layout laws 1–3; the derivations and reasons every preset s
|
|
|
6
6
|
|
|
7
7
|
`.col` (§1 of the guide) is 640px outer with 32px padding each side → **576px of text**.
|
|
8
8
|
|
|
9
|
-
At
|
|
9
|
+
At the 14px prose size that's about 90 characters a line (85–95, measured in Archivo), past the comfortable range on purpose: long lines are efficient once the reader is committed, and 1.75 leading compensates for the longer return sweep. At 16px the same column would hold 75–85.
|
|
10
10
|
|
|
11
11
|
The padding does double duty: on a 375px phone the column is `375 − 64 = 311px`, and on desktop the 32px keeps text off the edge of a maximised window.
|
|
12
12
|
|
|
13
|
-
That 311px once forced a responsive type step (a 30px title wrapping to three lines) until an 18px type ceiling removed it: layout law 1's narrowest-measure check, settled in the scale.
|
|
14
|
-
|
|
15
13
|
640px also equals Tailwind's `sm` breakpoint — convenient, not meaningful: below it the column is padding-limited, so the two coincide naturally.
|
|
16
14
|
|
|
17
15
|
The summary and detail pairs it keeps still: home-page role summary → `/experience`, writing index → article.
|
|
@@ -64,13 +62,13 @@ Tailwind's 4px scale, with a preference for a few well-worn values:
|
|
|
64
62
|
| `7` | 28 | Capability groups |
|
|
65
63
|
| `8` | 32 | Section heading → content, column padding |
|
|
66
64
|
| `9` | 36 | Home section padding |
|
|
67
|
-
| `10` | 40 |
|
|
68
|
-
| `11` | 44 |
|
|
65
|
+
| `10` | 40 | Prose heading top margin, section content after a rule |
|
|
66
|
+
| `11` | 44 | Between roles on the experience page |
|
|
69
67
|
| `14` | 56 | First section on a long page |
|
|
70
68
|
| `16` | 64 | Between major sections on a long page |
|
|
71
69
|
| `20`/`28` | 80/112 | Page top/bottom padding |
|
|
72
70
|
|
|
73
|
-
The values in heaviest use are `2.5`, `3.5`, `9`, `
|
|
71
|
+
The values in heaviest use are `2.5`, `3.5`, `9`, `10` — mostly half-steps and odd numbers. They exist because 10/14/36/40 are where the *asymmetry ratios* land, not because the scale demanded them.
|
|
74
72
|
|
|
75
73
|
## Asymmetry, in detail
|
|
76
74
|
|
|
@@ -46,9 +46,10 @@ The rule is in §4 of the guide: `superellipse(1.5)` on every element and pseudo
|
|
|
46
46
|
|---|---|---|
|
|
47
47
|
| 0.5px | `border-[0.5px]`, `h-[0.5px]` | Chrome borders, dividers, glass edges |
|
|
48
48
|
| 1px | `border`, `ring-1`, `h-px` | Card borders, TOC markers |
|
|
49
|
-
| 2px | `border-l-2` |
|
|
49
|
+
| 2px | `outline-2`, `border-l-2` | Focus rings, an imported Markdown quote's rule |
|
|
50
|
+
| 3px | `w-[3px] rounded-full` | The pull-quote bar |
|
|
50
51
|
|
|
51
|
-
By job (layout law 6): two groups of icons within one dock, 0.5px; where a card ends, 1px; a
|
|
52
|
+
By job (layout law 6): two groups of icons within one dock, 0.5px; where a card ends, 1px; a focus ring, 2px. The authored pull quote's bar is 3px and rounded, so a flex child rather than a border (`glass/type/assets/prose.tsx`); imported Markdown, which can't take classes, keeps a 2px `gray-alpha-300` border.
|
|
52
53
|
|
|
53
54
|
Vertical dividers use `w-[0.5px]` with an explicit height and `self-center`:
|
|
54
55
|
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
* pill). All of them grow out of a trigger sitting against one edge, so all
|
|
5
5
|
* of them stretch from that edge.
|
|
6
6
|
*
|
|
7
|
-
* The effect comes from letting two springs DISAGREE
|
|
8
|
-
*
|
|
7
|
+
* The effect comes from letting two springs DISAGREE (motion-presets.ts;
|
|
8
|
+
* the reasoning is glass/motion/springs.md).
|
|
9
9
|
*
|
|
10
10
|
* Two structural decisions worth keeping:
|
|
11
11
|
*
|
|
@@ -16,10 +16,8 @@
|
|
|
16
16
|
* 2. The wrapper is ALWAYS present, so a trigger's aria-controls still
|
|
17
17
|
* resolves to a real element when the menu is closed.
|
|
18
18
|
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* subtree — the wrapper scales, the child blurs, and they must be different
|
|
22
|
-
* elements.
|
|
19
|
+
* It animates a bare m.div and expects its child to carry the glass
|
|
20
|
+
* (glass/depth/guide.md §3).
|
|
23
21
|
*/
|
|
24
22
|
|
|
25
23
|
import { AnimatePresence, LazyMotion, domAnimation, m, useReducedMotion } from "motion/react";
|