trykeyline 0.1.0 → 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 +1 -1
- package/LICENSE +0 -16
- 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 +11 -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 +12 -36
- package/plugins/keyline/skills/keyline/domains/type/type-audit.md +5 -8
- package/plugins/keyline/skills/keyline/glass/color/assets/color-tokens.css +5 -25
- package/plugins/keyline/skills/keyline/glass/color/guide.md +2 -29
- package/plugins/keyline/skills/keyline/glass/color/theming.md +1 -7
- package/plugins/keyline/skills/keyline/glass/depth/depth-cues.md +1 -1
- package/plugins/keyline/skills/keyline/glass/depth/guide.md +4 -73
- package/plugins/keyline/skills/keyline/glass/layout/assets/layout-tokens.css +5 -3
- package/plugins/keyline/skills/keyline/glass/layout/guide.md +8 -7
- package/plugins/keyline/skills/keyline/glass/layout/measure-and-rhythm.md +4 -4
- package/plugins/keyline/skills/keyline/glass/layout/radius-and-elevation.md +3 -2
- package/plugins/keyline/skills/keyline/glass/motion/easing-and-duration.md +1 -1
- package/plugins/keyline/skills/keyline/glass/motion/guide.md +7 -38
- package/plugins/keyline/skills/keyline/glass/motion/reduced-motion.md +1 -1
- package/plugins/keyline/skills/keyline/glass/motion/shared-element.md +1 -1
- package/plugins/keyline/skills/keyline/glass/motion/sound.md +4 -1
- package/plugins/keyline/skills/keyline/glass/type/assets/type-tokens.css +3 -1
- package/plugins/keyline/skills/keyline/glass/type/guide.md +2 -4
- package/plugins/keyline/skills/keyline/scripts/check.mjs +4 -1
- package/plugins/keyline/skills/keyline/system/design-md.md +4 -4
- package/plugins/keyline/skills/keyline/system/guide.md +3 -7
- package/plugins/keyline/skills/keyline/system/presets.md +16 -16
- package/plugins/keyline/skills/keyline/system/roles.md +8 -8
|
@@ -30,12 +30,7 @@ backdrop-filter: blur(20px) saturate(1.8);
|
|
|
30
30
|
|
|
31
31
|
## 3. The 97% panel
|
|
32
32
|
|
|
33
|
-
`glass-panel` pairs a 24px blur with **97% opacity** because its menus' wrappers *scale open*, and a transform on an ancestor weakens `backdrop-filter` beneath it (depth law 9): for the whole animation the tint does the work. Where you can, the animating wrapper and the glass surface are different elements:
|
|
34
|
-
|
|
35
|
-
```tsx
|
|
36
|
-
<DropMenu open={open}> {/* animates scale + y */}
|
|
37
|
-
<ul className="glass-panel …"> {/* carries the blur, never transformed */}
|
|
38
|
-
```
|
|
33
|
+
`glass-panel` pairs a 24px blur with **97% opacity** because its menus' wrappers *scale open*, and a transform on an ancestor weakens `backdrop-filter` beneath it (depth law 9): for the whole animation the tint does the work. Where you can, the animating wrapper and the glass surface are different elements: `DropMenu` scales and moves, the `glass-panel` inside it carries the blur and is never transformed (`glass/motion/assets/DropMenu.tsx`).
|
|
39
34
|
|
|
40
35
|
## 4. Glass over glass
|
|
41
36
|
|
|
@@ -53,30 +48,7 @@ Depth law 3's translucent edge is the alpha ramp (`glass/color/guide.md`): `gray
|
|
|
53
48
|
|
|
54
49
|
### The shadow stack
|
|
55
50
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
```css
|
|
59
|
-
.dock-shadow {
|
|
60
|
-
box-shadow:
|
|
61
|
-
inset 0 1px 0 rgba(255, 255, 255, 0.6), /* top-edge catch light */
|
|
62
|
-
0 1px 1px rgba(0, 0, 0, 0.02), /* contact */
|
|
63
|
-
0 8px 16px -4px rgba(0, 0, 0, 0.04), /* near ambient */
|
|
64
|
-
0 24px 32px -8px rgba(0, 0, 0, 0.06); /* far ambient */
|
|
65
|
-
}
|
|
66
|
-
:root[data-theme="dark"] .dock-shadow {
|
|
67
|
-
box-shadow:
|
|
68
|
-
inset 0 1px 0 rgba(255, 255, 255, 0.1),
|
|
69
|
-
0 1px 1px rgba(0, 0, 0, 0.3),
|
|
70
|
-
0 8px 16px -4px rgba(0, 0, 0, 0.5),
|
|
71
|
-
0 24px 32px -8px rgba(0, 0, 0, 0.6);
|
|
72
|
-
}
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
Opacities are 2–6% in light. Dark is a different shadow, not a re-tint (depth law 5): shadows ~10× stronger (4% black on `#0b0b0b` is invisible), highlight ~6× weaker (0.6 white on dark is a glowing edge, not a catch light).
|
|
76
|
-
|
|
77
|
-
**Extracted the moment it appeared twice.** This ~400-character string was once inline in five places here, each with a dark twin. Not being a colour, it *can't* be a design token (colour law 10), so a class is its only home.
|
|
78
|
-
|
|
79
|
-
→ Every elevation tier, the pocket's edge, and stacking inside a glass surface: `glass/depth/depth-cues.md`
|
|
51
|
+
`.dock-shadow`: four layers, one light (depth law 4), 2–6% black in light and a different shadow in dark (depth law 5); one class with its dark twin, not a token or a JS constant. → `glass/depth/assets/glass.css`; tiers, the pocket's edge, stacking: `glass/depth/depth-cues.md`
|
|
80
52
|
|
|
81
53
|
## 6. A gradient edge, without a wrapper or a mask
|
|
82
54
|
|
|
@@ -141,54 +113,13 @@ The class drops on `animationend`; under reduced motion, plain accent colour.
|
|
|
141
113
|
|
|
142
114
|
## 8. Fallbacks
|
|
143
115
|
|
|
144
|
-
Depth law 15, at glass's tokens.
|
|
145
|
-
|
|
146
|
-
### `prefers-reduced-transparency`
|
|
147
|
-
|
|
148
|
-
```css
|
|
149
|
-
/* AFTER both theme blocks — it re-declares the same properties. */
|
|
150
|
-
@media (prefers-reduced-transparency: reduce) {
|
|
151
|
-
:root, :root[data-theme="dark"] {
|
|
152
|
-
--glass-nav-bg: var(--color-gray-100);
|
|
153
|
-
--glass-panel-bg: var(--color-gray-100);
|
|
154
|
-
--folder-glass-bg: var(--color-background-100);
|
|
155
|
-
}
|
|
156
|
-
.glass-nav, .glass-panel, .folder-glass {
|
|
157
|
-
backdrop-filter: none;
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
```
|
|
161
|
-
|
|
162
|
-
### `prefers-contrast: more`
|
|
163
|
-
|
|
164
|
-
```css
|
|
165
|
-
@media (prefers-contrast: more) {
|
|
166
|
-
.glass-nav, .glass-panel, .folder-glass { border-color: var(--color-gray-1000); }
|
|
167
|
-
}
|
|
168
|
-
```
|
|
169
|
-
|
|
170
|
-
### `@supports` — the gap in the reference implementation
|
|
171
|
-
|
|
172
|
-
The reference implementation has **no `@supports` guard**, so without `backdrop-filter` it gets a bare 72% tint: legible, but weak. `glass/depth/assets/glass.css` ships with the guard:
|
|
173
|
-
|
|
174
|
-
```css
|
|
175
|
-
.glass-nav { background: var(--color-gray-100); } /* opaque baseline */
|
|
176
|
-
|
|
177
|
-
@supports (backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px)) {
|
|
178
|
-
.glass-nav {
|
|
179
|
-
background: var(--glass-nav-bg);
|
|
180
|
-
backdrop-filter: blur(20px) saturate(1.8);
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
```
|
|
184
|
-
|
|
185
|
-
**Don't hand-write `-webkit-backdrop-filter`.** Tailwind v4's Lightning CSS adds it and keeps only the last of a hand-written pair, so the site ships the prefix alone and Firefox loses every blur (`glass/depth/performance-and-fallbacks.md`).
|
|
116
|
+
Depth law 15, at glass's tokens, in `glass/depth/assets/glass.css`: reduced transparency swaps the tints to `gray-100` / `background-100` and drops the filter, after both theme blocks; `prefers-contrast: more` turns the edge `gray-1000`; an `@supports` guard over a `gray-100` baseline, which the site lacks. **Never hand-write `-webkit-backdrop-filter`**: Lightning CSS keeps only the last of a pair, and Firefox loses every blur (§9).
|
|
186
117
|
|
|
187
118
|
## 9. Performance
|
|
188
119
|
|
|
189
120
|
Depth law 16, within glass's budget: three or four surfaces at most, each small — a dock a few hundred pixels wide, not a full-viewport overlay — and closed menus unmount.
|
|
190
121
|
|
|
191
|
-
→ What keeps it affordable
|
|
122
|
+
→ What keeps it affordable, and the prefix bug: `glass/depth/performance-and-fallbacks.md`
|
|
192
123
|
|
|
193
124
|
## 10. Confirmations
|
|
194
125
|
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
@theme {
|
|
6
6
|
/* ── The measure ────────────────────────────────────────────────────────
|
|
7
|
-
640px outer, 32px padding → 576px of text. At
|
|
8
|
-
characters per line:
|
|
7
|
+
640px outer, 32px padding → 576px of text. At the 14px prose size that's
|
|
8
|
+
~90 characters per line (85–95): past the comfortable range, which is the
|
|
9
9
|
right place for a site that expects sustained reading (long lines are
|
|
10
10
|
efficient once the reader is committed, and 1.75 leading pays for the
|
|
11
11
|
longer return sweep).
|
|
@@ -139,7 +139,9 @@
|
|
|
139
139
|
one dock. Renders as a true sub-pixel hairline on retina and
|
|
140
140
|
rounds UP to 1px elsewhere, so it degrades rather than vanishes.
|
|
141
141
|
1px a real edge — where a card ends.
|
|
142
|
-
2px a
|
|
142
|
+
2px a focus ring; imported Markdown's blockquote rule.
|
|
143
|
+
3px the authored pull quote's bar — a rounded flex child, not a
|
|
144
|
+
border, since a border can't be rounded on its own.
|
|
143
145
|
|
|
144
146
|
On translucent surfaces use the ALPHA ramp (border-gray-alpha-400). A
|
|
145
147
|
solid border on glass doesn't participate in what's behind it and reads as
|
|
@@ -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`).
|
|
@@ -247,10 +248,10 @@ The domain checklist applies in full; these are glass's values for it.
|
|
|
247
248
|
|
|
248
249
|
- [ ] `.col` — 640px, 32px padding — on every page; article covers stay in it.
|
|
249
250
|
- [ ] Rails in the 1280px frame: `left-8`, `w-[196px]`, `sticky top-28`, `max-h-[calc(100dvh-11rem)]`, shown from `xl`.
|
|
250
|
-
- [ ] Prose headings `mt-
|
|
251
|
+
- [ ] Prose headings `mt-10` / `mb-3`; home sections `py-9`; long pages `mt-16 border-t pt-10`, the first `mt-14`.
|
|
251
252
|
- [ ] Rules between Work, Experience and Life only; Watching and Socials follow on.
|
|
252
253
|
- [ ] Radii from the ladder: 6 / 9 / 10 / 12 / 16px and full; `superellipse(1.5)` on everything.
|
|
253
|
-
- [ ] 0.5px chrome, 1px object edges, 2px
|
|
254
|
+
- [ ] 0.5px chrome, 1px object edges, 2px focus rings, the pull quote's 3px rounded bar; `gray-alpha` on translucent surfaces.
|
|
254
255
|
- [ ] `MobileDock` / `DesktopDock` and `SectionNav` / `SectionNavCompact`, sharing data and rows.
|
|
255
256
|
- [ ] The gallery opens on the wall; its layout lives in module memory.
|
|
256
257
|
- [ ] `pb-28` under the dock on every page that can end near the viewport bottom.
|
|
@@ -6,7 +6,7 @@ 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
|
|
|
@@ -64,13 +64,13 @@ Tailwind's 4px scale, with a preference for a few well-worn values:
|
|
|
64
64
|
| `7` | 28 | Capability groups |
|
|
65
65
|
| `8` | 32 | Section heading → content, column padding |
|
|
66
66
|
| `9` | 36 | Home section padding |
|
|
67
|
-
| `10` | 40 |
|
|
68
|
-
| `11` | 44 |
|
|
67
|
+
| `10` | 40 | Prose heading top margin, section content after a rule |
|
|
68
|
+
| `11` | 44 | Between roles on the experience page |
|
|
69
69
|
| `14` | 56 | First section on a long page |
|
|
70
70
|
| `16` | 64 | Between major sections on a long page |
|
|
71
71
|
| `20`/`28` | 80/112 | Page top/bottom padding |
|
|
72
72
|
|
|
73
|
-
The values in heaviest use are `2.5`, `3.5`, `9`, `
|
|
73
|
+
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
74
|
|
|
75
75
|
## Asymmetry, in detail
|
|
76
76
|
|
|
@@ -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/long-form-prose.md`, Quotes); 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
|
|
|
@@ -68,7 +68,7 @@ The class is applied only on an explicit toggle, then removed:
|
|
|
68
68
|
|
|
69
69
|
```ts
|
|
70
70
|
export function setThemeChoice(choice: ThemeChoice): void {
|
|
71
|
-
localStorage.setItem(KEY, choice);
|
|
71
|
+
try { localStorage.setItem(KEY, choice); } catch { /* won't persist */ }
|
|
72
72
|
const root = document.documentElement;
|
|
73
73
|
root.classList.add("theme-transition");
|
|
74
74
|
applyTheme(choice);
|
|
@@ -38,7 +38,7 @@ Glass uses two of law 4's classes:
|
|
|
38
38
|
|
|
39
39
|
One curve for the whole site. The `1.1` end control point overshoots slightly and settles back, physical but not bouncy. The exceptions:
|
|
40
40
|
|
|
41
|
-
- **
|
|
41
|
+
- **100ms opacity hovers and icon fills** (§13): `ease-out`. Opacity entrances (`.reveal`, the route fade) keep the house curve on purpose: on opacity the overshoot clamps at 1.
|
|
42
42
|
- **The theme crossfade**: plain `ease`.
|
|
43
43
|
- **Anything spring-driven**: no bezier at all.
|
|
44
44
|
- **Any track that also drives a filter**, or any property floored at zero (law 5): expo-out, `cubic-bezier(0.16, 1, 0.3, 1)`, which never exceeds 1. It is the same intent in a curve legal for the property, not a second character.
|
|
@@ -116,11 +116,7 @@ The details that keep it from flickering are in `domains/motion/patterns.md`, th
|
|
|
116
116
|
|
|
117
117
|
### When the DOM element isn't enough
|
|
118
118
|
|
|
119
|
-
A *photograph* travelling between layouts escalates to the GPU (law 13)
|
|
120
|
-
|
|
121
|
-
**Carry the reading position across**: open the other layout on the tile nearest the viewport centre, measured before React commits it, and give each end its own rect around a shared centre (law 8).
|
|
122
|
-
|
|
123
|
-
→ The measurement (`centreFrame`) and the WebGL notes: `glass/motion/shared-element.md`, escalating to the GPU
|
|
119
|
+
A *photograph* travelling between layouts escalates to the GPU (law 13), opening on the tile nearest the viewport centre (law 8): `glass/motion/shared-element.md`, escalating to the GPU
|
|
124
120
|
|
|
125
121
|
## 7. Reduced motion
|
|
126
122
|
|
|
@@ -152,34 +148,11 @@ One passive listener and one rAF per behaviour (law 12; the shape is in `domains
|
|
|
152
148
|
|
|
153
149
|
## 9. Reveal on scroll
|
|
154
150
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
```css
|
|
158
|
-
.reveal { opacity: 0; transform: translateY(8px);
|
|
159
|
-
transition: opacity 300ms var(--ease-geist), transform 300ms var(--ease-geist); }
|
|
160
|
-
.reveal[data-in] { opacity: 1; transform: translateY(0); }
|
|
161
|
-
|
|
162
|
-
@media (prefers-reduced-motion: reduce) {
|
|
163
|
-
.reveal { opacity: 1; transform: none; transition: none; }
|
|
164
|
-
}
|
|
165
|
-
```
|
|
166
|
-
|
|
167
|
-
`threshold: 0` with `rootMargin: "0px 0px -40px 0px"`, one-shot, re-run on the pathname. 8px of travel registers without a page of paragraphs seeming to slide. Every guard is law 11 and `domains/motion/guards.md`; the two the site hit:
|
|
168
|
-
|
|
169
|
-
- **A fractional threshold on a tall element.** Every rendered SKILL.md on the site, eight to thirteen thousand pixels in one `.reveal`, once sat invisible at opacity 0 behind `0.1`.
|
|
170
|
-
- **A class the framework owns.** Tag-filtered rows went blank as the pointer left, when React rewrote a state-dependent `className` and wiped an `in` added with `classList.add`. Hence `data-in`.
|
|
171
|
-
|
|
172
|
-
The no-JavaScript reset, in the head:
|
|
173
|
-
|
|
174
|
-
```html
|
|
175
|
-
<noscript>
|
|
176
|
-
<style>.reveal { opacity: 1 !important; transform: none !important; }</style>
|
|
177
|
-
</noscript>
|
|
178
|
-
```
|
|
151
|
+
300ms on the house curve, 8px of travel, `threshold: 0`, `rootMargin: "0px 0px -40px 0px"`, marked by `data-in`; the guards are law 11, whose two bugs are this site's. → `glass/motion/assets/motion-tokens.css`, `glass/motion/assets/useReveal.ts`
|
|
179
152
|
|
|
180
153
|
## 10. Performance: `m` not `motion`
|
|
181
154
|
|
|
182
|
-
`m.div` inside `<LazyMotion features={domAnimation} strict>`, never a bare `motion.*` (`domains/motion/performance.md`). The menu
|
|
155
|
+
`m.div` inside `<LazyMotion features={domAnimation} strict>`, never a bare `motion.*` (`domains/motion/performance.md`). The menu's wrapper scales and its `glass-panel` child carries the blur (law 13; `glass/depth/guide.md` §3).
|
|
183
156
|
|
|
184
157
|
## 11. Hover intent
|
|
185
158
|
|
|
@@ -204,11 +177,7 @@ active:scale-[0.92] /* 8% — felt, not seen */
|
|
|
204
177
|
|
|
205
178
|
On `pointerdown` (law 3).
|
|
206
179
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
**Sound can be a phrase, not just an event.** The opening screen's run, a note per photograph, is written out in A major pentatonic, where no two notes clash, so a run cut short by a failed image still resolves; the landing note, the only accented one, sits a fifth *below* the start, closing the phrase rather than adding a step.
|
|
210
|
-
|
|
211
|
-
Sound is *on* by default here, which is defensible only on a personal site and only if you're confident. → Synthesis walkthrough and delegation pattern: `glass/motion/sound.md`
|
|
180
|
+
Sound is *on* by default here, defensible only on a personal site: a synthesised "tok" on hover and click, muteable; `[data-quiet-hover]` silences hover on dense surfaces. → `glass/motion/sound.md`; the rules: `domains/motion/patterns.md`, sound
|
|
212
181
|
|
|
213
182
|
## 13. Fill means chosen
|
|
214
183
|
|
|
@@ -223,7 +192,7 @@ export const detailFade = (on: boolean) =>
|
|
|
223
192
|
`transition-opacity duration-200 ease-out motion-reduce:transition-none ${on ? "opacity-0" : "opacity-100"}`;
|
|
224
193
|
```
|
|
225
194
|
|
|
226
|
-
**200ms `ease-out`, not the house curve
|
|
195
|
+
**200ms `ease-out`, not the house curve** (§2). The knockout mask for interior lines is in `domains/motion/patterns.md`, fill means chosen.
|
|
227
196
|
|
|
228
197
|
**How the marks that can't fill signal instead:**
|
|
229
198
|
|
|
@@ -277,7 +246,7 @@ The domain checklist first; then glass's values:
|
|
|
277
246
|
|
|
278
247
|
- [ ] Every duration on the ladder: no 250ms, 500ms or 1s.
|
|
279
248
|
- [ ] Off-ladder timing is the subject of the moment (the 620ms dissolve) or a once-a-session set piece, and says which.
|
|
280
|
-
- [ ] `--ease-geist` everywhere, plus `ease-out` for opacity
|
|
249
|
+
- [ ] `--ease-geist` everywhere, opacity entrances included, plus `ease-out` for 100ms opacity hovers and icon fills, plain `ease` for the theme fade, and expo-out on any track driving a filter.
|
|
281
250
|
- [ ] Menus open on the `GROW`/`CROSS` pair and close on `CLOSE`.
|
|
282
251
|
- [ ] Hover timed by area: a link 100ms, a card-sized wash 200ms.
|
|
283
252
|
- [ ] No translate on hover, bar the folder's rising cards.
|
|
@@ -60,7 +60,7 @@ One variable turns a blur-crossfade into a plain crossfade; `blur(0px)` → `blu
|
|
|
60
60
|
|
|
61
61
|
## In CSS
|
|
62
62
|
|
|
63
|
-
The `.reveal` rule is
|
|
63
|
+
The `.reveal` rule is in `glass/motion/assets/motion-tokens.css`; the theme crossfade's opt-in block is in `glass/motion/easing-and-duration.md`. The route fade:
|
|
64
64
|
|
|
65
65
|
```css
|
|
66
66
|
@media (prefers-reduced-motion: reduce) {
|
|
@@ -114,7 +114,7 @@ export type Rect = { x: number; y: number; width: number; height: number };
|
|
|
114
114
|
export function lerpRect(a: Rect, b: Rect, t: number): Rect { /* … */ }
|
|
115
115
|
```
|
|
116
116
|
|
|
117
|
-
The rules are law 13 and `domains/motion/performance.md`, escalating to the GPU. Here the wall stays DOM and the open photo is `alt=""`, its labels in the strip beside it.
|
|
117
|
+
The rules are law 13 and `domains/motion/performance.md`, escalating to the GPU. Here the wall stays DOM, since a hundred tiles as textures is ~590MB before atlasing, and the open photo is `alt=""`, its labels in the strip beside it.
|
|
118
118
|
|
|
119
119
|
**Failing returns `null`:**
|
|
120
120
|
|
|
@@ -169,7 +169,10 @@ It's still a set piece, not a texture — once a session. See §14 of `glass/mot
|
|
|
169
169
|
## Mute, and persistence
|
|
170
170
|
|
|
171
171
|
```ts
|
|
172
|
-
|
|
172
|
+
// Every storage access is guarded: it throws in some private modes.
|
|
173
|
+
let muted = (() => {
|
|
174
|
+
try { return localStorage.getItem("ui-sound") === "off"; } catch { return false; }
|
|
175
|
+
})();
|
|
173
176
|
|
|
174
177
|
export function setMuted(value: boolean): void {
|
|
175
178
|
muted = value;
|
|
@@ -396,8 +396,10 @@
|
|
|
396
396
|
white-space: nowrap;
|
|
397
397
|
}
|
|
398
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. */
|
|
399
401
|
.prose-imported blockquote {
|
|
400
|
-
padding-left:
|
|
402
|
+
padding-left: 15px;
|
|
401
403
|
border-left: 2px solid var(--color-gray-alpha-300);
|
|
402
404
|
color: var(--color-faint);
|
|
403
405
|
}
|
|
@@ -108,7 +108,7 @@ Archivo's true italic is a separate file whose slant fights its geometric letter
|
|
|
108
108
|
```tsx
|
|
109
109
|
// From the inline markdown renderer
|
|
110
110
|
if (bold) return <strong className="font-medium text-gray-1000">{text}</strong>;
|
|
111
|
-
if (em) return <em className="text-
|
|
111
|
+
if (em) return <em className="text-muted">{text}</em>;
|
|
112
112
|
```
|
|
113
113
|
|
|
114
114
|
Why the mapping works, and the semantic tags it keeps: domain law 9 and `domains/type/prose-and-emphasis.md`.
|
|
@@ -184,9 +184,7 @@ Glass's two systems (domain law 8):
|
|
|
184
184
|
|
|
185
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
186
|
|
|
187
|
-
**
|
|
188
|
-
|
|
189
|
-
**Icons beside text** follow domain law 13: 13px text takes a 13–14px icon with a 4–6px gap. It is the mass problem `glass/color/guide.md` solves for colour, by resting icons a tier below, solved here for size.
|
|
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.
|
|
190
188
|
|
|
191
189
|
→ How prose blocks are set — headings, quotes, lists, spacing: `glass/type/long-form-prose.md`
|
|
192
190
|
|
|
@@ -277,6 +277,9 @@ export function run(argv) {
|
|
|
277
277
|
}
|
|
278
278
|
|
|
279
279
|
const result = check({ root: opts.root, paths: opts.paths.length ? opts.paths : ["."], ignores: opts.ignores, rules: opts.rules });
|
|
280
|
+
// The verdict counts every finding, so --quiet trims the output, never the exit code.
|
|
281
|
+
const all = summarise(result.findings);
|
|
282
|
+
const failed = all.error > 0 || (opts.strict && all.warning > 0);
|
|
280
283
|
if (opts.quiet) result.findings = result.findings.filter((f) => f.severity === "error");
|
|
281
284
|
const s = summarise(result.findings);
|
|
282
285
|
|
|
@@ -286,7 +289,7 @@ export function run(argv) {
|
|
|
286
289
|
} else {
|
|
287
290
|
console.log(human(result, { all: opts.all, color: process.stdout.isTTY && !process.env.NO_COLOR }));
|
|
288
291
|
}
|
|
289
|
-
return
|
|
292
|
+
return failed ? 1 : 0;
|
|
290
293
|
}
|
|
291
294
|
|
|
292
295
|
if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
@@ -80,9 +80,9 @@ Copy this shape. Drop any table or row that doesn't apply; add project roles und
|
|
|
80
80
|
|
|
81
81
|
| Role | Name here | Value |
|
|
82
82
|
|---|---|---|
|
|
83
|
-
| `z.popover` | `z-popover` |
|
|
84
|
-
| `z.overlay` | `z-overlay` |
|
|
85
|
-
| `z.toast` | `z-toast` |
|
|
83
|
+
| `z.popover` | `z-popover` | 20 |
|
|
84
|
+
| `z.overlay` | `z-overlay` | 60 |
|
|
85
|
+
| `z.toast` | `z-toast` | 70 |
|
|
86
86
|
|
|
87
87
|
**Breakpoints**
|
|
88
88
|
|
|
@@ -123,7 +123,7 @@ Plus springs by character, and the reduced-motion policy in one sentence.
|
|
|
123
123
|
## Decisions
|
|
124
124
|
|
|
125
125
|
- YYYY-MM-DD — Card titles at 500, not 600: they read heavy beside the pane title. (User decision; the type guide dial.)
|
|
126
|
-
- YYYY-MM-DD — Three weights, not two: the billing
|
|
126
|
+
- YYYY-MM-DD — Three weights, not two: the billing screens need a 600 heading weight above the 500 that lifts lines inside rows. (Client feedback; the type guide dial override.)
|
|
127
127
|
- YYYY-MM-DD — Page on white `surface.page`, not the grey `bg-neutral-50`: the text tiers failed against the grey. (Law; the colour guide.)
|
|
128
128
|
|
|
129
129
|
## Open issues
|
|
@@ -46,7 +46,7 @@ node <keyline>/scripts/contrast.mjs --ladder "#ffffff" "#16161a" "#4b4b52" "#6b6
|
|
|
46
46
|
|
|
47
47
|
## 3. Choose the preset
|
|
48
48
|
|
|
49
|
-
From what the reader is mostly doing — reading, working, or being persuaded — propose `editorial`, `product` or `expressive` (`presets.md`). Offer `glass` only when the user wants modimihir.com's exact system rather than their own; it fills the dials from `glass
|
|
49
|
+
From what the reader is mostly doing — reading, working, or being persuaded — propose `editorial`, `product` or `expressive` (`presets.md`). Offer `glass` only when the user wants modimihir.com's exact system rather than their own; it fills the dials from the tables in each `glass/<domain>/guide.md`, under the project's existing names wherever it has them.
|
|
50
50
|
|
|
51
51
|
State the evidence: *"Mostly long-form pages under `app/blog`, two weights in use, one accent — this reads as `editorial`."* Mixed projects get a base preset plus areas: `product`, with `app/(marketing)/** → expressive`.
|
|
52
52
|
|
|
@@ -70,20 +70,16 @@ Don't ask what you can find out, and don't ask for approval of the whole mapping
|
|
|
70
70
|
Follow `system/design-md.md`: the project's names in `Name here`, canonical roles in `Role`, values as the source states them, measured contrast for every text tier, per-theme columns only where themes exist, nothing for roles the project lacks. Then:
|
|
71
71
|
|
|
72
72
|
- **`## Decisions`** — every dial set differently from the preset, with its reason, or a question mark if unknown.
|
|
73
|
-
- **`## Open issues`** — every law the code breaks, one line each with evidence (a ratio, a count, a file).
|
|
73
|
+
- **`## Open issues`** — every law the code breaks, one line each with evidence (a ratio, a count, a file). Cite each law by the right number (`commands/init.md`). **Don't fix anything** unless asked; a setup run that also rewrites forty components can't be reviewed.
|
|
74
74
|
|
|
75
75
|
Tell the user briefly: the preset and why, how many roles mapped cleanly, what you proposed names for, and the three most important open issues.
|
|
76
76
|
|
|
77
77
|
## 6. Generating tokens, only when asked
|
|
78
78
|
|
|
79
|
-
|
|
79
|
+
Apart from the `glass` preset's drop-in files under `glass/<domain>/assets/`, the set ships no token files, because the right file depends on the stack. Asked for tokens — for a new project, or to fill `proposed` rows — generate them **in the project's existing mechanism**: a Tailwind v4 `@theme` block, a `tailwind.config` extension, CSS custom properties, a JS theme object, a design-tokens JSON. Match the existing layout and naming; never add a second token system beside the first.
|
|
80
80
|
|
|
81
81
|
With no tokens yet, `presets.md` and each domain guide's `## Dials` section give defaults. Say they're defaults, choose names with the user, and record the choices in `DESIGN.md`.
|
|
82
82
|
|
|
83
|
-
## How the other commands use this file
|
|
84
|
-
|
|
85
|
-
Each domain command reads `DESIGN.md`, finds its section, and works in the project's names. If the file is missing, it runs steps 1–2 for its own domain, does the task and recommends `/keyline init` — it doesn't refuse. A decision that changes the system — a new role, a moved dial, a law knowingly broken, a change a law forced that a later session might undo — goes back under `Decisions` — dated, marked user decision, client feedback or law, newest first, replacing what it supersedes — which is what keeps the file true after the first day.
|
|
86
|
-
|
|
87
83
|
## Checklist
|
|
88
84
|
|
|
89
85
|
- [ ] An existing `DESIGN.md` was reconciled, not overwritten; every user-chosen name and decision survived.
|
|
@@ -25,34 +25,34 @@ When the evidence doesn't settle it, propose one, say what evidence you used, an
|
|
|
25
25
|
|
|
26
26
|
*The interface should disappear so the reading doesn't.* Restraint is the point, and a single loud moment reads as craft because nothing else competes with it.
|
|
27
27
|
|
|
28
|
-
- **Type** — a dense ladder that keeps the reading loudest
|
|
29
|
-
- **Colour** — near-monochrome
|
|
30
|
-
- **Layout** — one reading measure every page shares
|
|
31
|
-
- **Surfaces** — mostly flat
|
|
32
|
-
- **Motion** — short, and only where it makes a change legible.
|
|
28
|
+
- **Type** — a dense ladder that keeps the reading loudest.
|
|
29
|
+
- **Colour** — near-monochrome, the accent spent on focus.
|
|
30
|
+
- **Layout** — one reading measure every page shares.
|
|
31
|
+
- **Surfaces** — mostly flat; translucency, if any, only on chrome.
|
|
32
|
+
- **Motion** — short, and only where it makes a change legible.
|
|
33
33
|
|
|
34
34
|
## `product`
|
|
35
35
|
|
|
36
36
|
*The interface is a tool, and the reader is fluent in it.* Density, predictability and scannability beat atmosphere. Signal has to survive a screen full of data.
|
|
37
37
|
|
|
38
|
-
- **Type** — tighter, smaller steps; UI and data roles dominate.
|
|
39
|
-
- **Colour** — near-monochrome
|
|
40
|
-
- **Layout** — an app shell rather than a single measure
|
|
41
|
-
- **Surfaces** — content on the page, rows parted by hairlines; a
|
|
42
|
-
- **Motion** — shortest of the three
|
|
38
|
+
- **Type** — tighter, smaller steps; UI and data roles dominate.
|
|
39
|
+
- **Colour** — near-monochrome with a real status palette, and one accent hue that acts.
|
|
40
|
+
- **Layout** — an app shell rather than a single measure; tighter spacing, still grouped.
|
|
41
|
+
- **Surfaces** — content on the page, rows parted by hairlines; a card only for one object.
|
|
42
|
+
- **Motion** — shortest of the three; nothing delays a repeated action.
|
|
43
43
|
|
|
44
44
|
## `expressive`
|
|
45
45
|
|
|
46
46
|
*The interface is making an impression, usually once.* Scale, colour and motion are allowed to carry brand — but still by rule, or it turns into a template.
|
|
47
47
|
|
|
48
|
-
- **Type** — a real display tier far above body, possibly a second family
|
|
49
|
-
- **Colour** — a brand palette used broadly,
|
|
50
|
-
- **Layout** — sections may
|
|
51
|
-
- **Surfaces** — depth, gradients and translucency
|
|
52
|
-
- **Motion** — longer and more characterful on entrances; exits still get out of the way.
|
|
48
|
+
- **Type** — a real display tier far above body, possibly a second family.
|
|
49
|
+
- **Colour** — a brand palette used broadly, each colour placed by rule.
|
|
50
|
+
- **Layout** — sections may go full-bleed; paragraphs keep the measure.
|
|
51
|
+
- **Surfaces** — depth, gradients and translucency as brand material, each with its fallback.
|
|
52
|
+
- **Motion** — longer and more characterful on entrances; exits still get out of the way.
|
|
53
53
|
|
|
54
54
|
## `glass`
|
|
55
55
|
|
|
56
|
-
The Glass preset's exact values: `editorial` in character, with every dial already decided on [modimihir.com](https://modimihir.com). Offer it only when the user wants that system itself rather than their own — `/keyline init` copies its values from `glass
|
|
56
|
+
The Glass preset's exact values: `editorial` in character, with every dial already decided on [modimihir.com](https://modimihir.com). Offer it only when the user wants that system itself rather than their own — `/keyline init` copies its values from the tables in each `glass/<domain>/guide.md` into `DESIGN.md`, in the project's own names wherever the project already has names, and each guide supplies the exact recipes, with drop-in token files and components under its `assets/`.
|
|
57
57
|
|
|
58
58
|
A `glass` project can still override a dial. It should expect to: the Glass values were chosen for one text-heavy portfolio, and a project that isn't one will find the places they don't fit.
|
|
@@ -32,7 +32,7 @@ Sizes, leading, tracking and weight all hang off these. Owned by the type guide.
|
|
|
32
32
|
| `type.data` | Numerals that must align — tables, prices, stats. Often `type.ui` or `type.body` plus tabular figures rather than its own size |
|
|
33
33
|
|
|
34
34
|
Families: `family.text`, `family.display` (only if different), `family.ui` (only if different — interface text beside a serif reading face), `family.mono`.
|
|
35
|
-
Weights: `weight.regular`, `weight.strong`. Two optional ones, each only where the preset allows it: `weight.medium`, a
|
|
35
|
+
Weights: `weight.regular`, `weight.strong`. Two optional ones, each only where the preset allows it: `weight.medium`, a third weight above `weight.strong` for headings on a dense screen, only where a project records a job the other two can't carry, and `weight.display`, the display tier's own weight, never reused below it.
|
|
36
36
|
|
|
37
37
|
## Colour
|
|
38
38
|
|
|
@@ -62,7 +62,7 @@ A project may slot extra tiers between secondary and tertiary (the Glass preset
|
|
|
62
62
|
| `surface.raised` | A component's own surface — a card, an input, a menu |
|
|
63
63
|
| `surface.hover` | That surface, hovered |
|
|
64
64
|
| `surface.pressed` | That surface, pressed or active |
|
|
65
|
-
| `surface.selected` | A persistent selected state, distinct from hover. Under product and expressive, the accent
|
|
65
|
+
| `surface.selected` | A persistent selected state, distinct from hover. Under product and expressive, the accent tint colour law 7 defines |
|
|
66
66
|
| `surface.floating` · `surface.overlay` | A floating or modal level's own surface, where it differs from `surface.raised` — usually only in dark themes, where elevation is shown by lightening |
|
|
67
67
|
| `surface.inverse` | Tooltips, toasts, solid-filled chrome |
|
|
68
68
|
|
|
@@ -85,8 +85,8 @@ A project may slot extra tiers between secondary and tertiary (the Glass preset
|
|
|
85
85
|
|---|---|
|
|
86
86
|
| `focus` | The focus ring. Must hit 3:1 against every surface it can land on |
|
|
87
87
|
| `accent` · `accent.hover` | The brand or action colour, if the system has one, and its hovered state. `accent` may share a token with `focus` |
|
|
88
|
-
| `status.danger` · `status.warning` · `status.success` · `status.info` | Each a pair — `status.<name>.fg`, a foreground
|
|
89
|
-
| `exception.<name>` | A deliberate chromatic spend outside the accent
|
|
88
|
+
| `status.danger` · `status.warning` · `status.success` · `status.info` | Each a pair — `status.<name>.fg`, a foreground read as text, and `status.<name>.bg`, a background tint — plus `status.<name>.solid` where a filled control needs it (a destructive button) |
|
|
89
|
+
| `exception.<name>` | A deliberate chromatic spend outside the accent, always named after what it's *for*; a hue family answering one question is `exception.<question>.<member>` (`exception.type.task`, `exception.area.<name>`, `exception.person.<n>`), each listed in `DESIGN.md` |
|
|
90
90
|
|
|
91
91
|
## Layout
|
|
92
92
|
|
|
@@ -97,19 +97,19 @@ Owned by the layout guide.
|
|
|
97
97
|
| `measure.reading` | The width of running text |
|
|
98
98
|
| `measure.wide` | Content that isn't prose but should still align — grids, tables |
|
|
99
99
|
| `measure.app` | An application shell's content area, if there is one |
|
|
100
|
-
| `measure.form` | Forms and settings inside a panel,
|
|
100
|
+
| `measure.form` | Forms and settings inside a panel, so controls stay near their labels |
|
|
101
101
|
| `measure.frame` | The wider frame that the reading column and its rails are both anchored to, so the gap between them holds as the window grows |
|
|
102
102
|
| `measure.rail` | The width of a rail in the outer margin — a table of contents, an aside |
|
|
103
103
|
| `gutter` | Horizontal page padding at the smallest width (Material's *margin*) |
|
|
104
104
|
| `grid.columns` · `space.column` | A column grid's count per breakpoint, and the gap between columns (Material's *gutter*). Product and expressive only, and only where unlike blocks sit side by side |
|
|
105
|
-
| `target.min` | The smallest hit area a control may have:
|
|
105
|
+
| `target.min` | The smallest hit area a control may have, per pointer: never under 24px fine or 44px on touch, which a preset's dial may raise |
|
|
106
106
|
| `space.tight` | Inside a group — a label and its value, items in a list |
|
|
107
107
|
| `space.group` | Between groups inside a section |
|
|
108
108
|
| `space.section` | Between sections |
|
|
109
109
|
| `space.heading-above` · `space.heading-below` | The asymmetric pair around a heading. Their *ratio* is the rule, not their values |
|
|
110
110
|
| `radius.pill` · `radius.small` · `radius.control` · `radius.card` · `radius.container` | Smallest to outermost. Concentric corners obey inner = outer − padding; anything away from a corner takes a radius for its size |
|
|
111
111
|
| `hairline.chrome` · `hairline.object` · `rule.emphasis` | Divider weight inside chrome, the edge of an object, and a deliberate heavy rule (a quote bar, a focus ring) |
|
|
112
|
-
| `z.below` · `z.raised` · `z.popover` · `z.dismiss` · `z.nav` · `z.overlay` · `z.toast` · `z.skip-link` | The stacking ladder, low to high
|
|
112
|
+
| `z.below` · `z.raised` · `z.popover` · `z.dismiss` · `z.nav` · `z.overlay` · `z.toast` · `z.skip-link` | The stacking ladder, low to high (layout law 7) |
|
|
113
113
|
| `bp.<name>` | Breakpoints, named for what changes at them, not for devices |
|
|
114
114
|
|
|
115
115
|
## Surfaces and depth
|
|
@@ -134,7 +134,7 @@ Owned by the motion guide.
|
|
|
134
134
|
|
|
135
135
|
| Role | Job |
|
|
136
136
|
|---|---|
|
|
137
|
-
| `duration.instant` |
|
|
137
|
+
| `duration.instant` | Press feedback, focus rings |
|
|
138
138
|
| `duration.micro` | Opacity and colour on hover |
|
|
139
139
|
| `duration.state` | Small state changes: colour, background, icon swaps, small transforms |
|
|
140
140
|
| `duration.popover` | Something small appearing: a hover card, a menu, a chevron |
|