@prestack/thread-ui 0.1.10 → 0.1.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (209) hide show
  1. package/CHANGELOG.md +187 -0
  2. package/dist/ui/attach.js +6 -7
  3. package/dist/ui/compositions.d.ts +0 -8
  4. package/dist/ui/compositions.js +3 -166
  5. package/dist/ui/generators/routes.js +1 -0
  6. package/package.json +2 -2
  7. package/dist/ui/animations/anm_appear.ts +0 -13
  8. package/dist/ui/animations/anm_blur_in.ts +0 -19
  9. package/dist/ui/animations/anm_blur_out.ts +0 -18
  10. package/dist/ui/animations/anm_dismiss.ts +0 -18
  11. package/dist/ui/animations/anm_drop.ts +0 -19
  12. package/dist/ui/animations/anm_fall.ts +0 -18
  13. package/dist/ui/animations/anm_flash.ts +0 -18
  14. package/dist/ui/animations/anm_glow.ts +0 -16
  15. package/dist/ui/animations/anm_lift.ts +0 -16
  16. package/dist/ui/animations/anm_nudge.ts +0 -16
  17. package/dist/ui/animations/anm_pop_in.ts +0 -23
  18. package/dist/ui/animations/anm_pop_out.ts +0 -22
  19. package/dist/ui/animations/anm_press.ts +0 -12
  20. package/dist/ui/animations/anm_pulse.ts +0 -13
  21. package/dist/ui/animations/anm_rise.ts +0 -19
  22. package/dist/ui/animations/anm_shake.ts +0 -13
  23. package/dist/ui/animations/anm_slide_in_left.ts +0 -19
  24. package/dist/ui/animations/anm_slide_in_right.ts +0 -19
  25. package/dist/ui/animations/anm_slide_out_left.ts +0 -18
  26. package/dist/ui/animations/anm_slide_out_right.ts +0 -18
  27. package/dist/ui/animations/anm_spin.ts +0 -12
  28. package/dist/ui/animations/anm_swell.ts +0 -16
  29. package/dist/ui/animations/anm_wobble.ts +0 -24
  30. package/dist/ui/animations/anm_zoom_in.ts +0 -19
  31. package/dist/ui/animations/anm_zoom_out.ts +0 -18
  32. package/dist/ui/animations.ts +0 -308
  33. package/dist/ui/attach.ts +0 -553
  34. package/dist/ui/charts.tsx +0 -69
  35. package/dist/ui/checked.ts +0 -32
  36. package/dist/ui/collector.ts +0 -140
  37. package/dist/ui/compositions.ts +0 -613
  38. package/dist/ui/consent.ts +0 -55
  39. package/dist/ui/content.tsx +0 -27
  40. package/dist/ui/contrast.ts +0 -27
  41. package/dist/ui/controls.tsx +0 -25
  42. package/dist/ui/disclosure.tsx +0 -20
  43. package/dist/ui/dismiss-layer.ts +0 -87
  44. package/dist/ui/door.ts +0 -140
  45. package/dist/ui/drag.ts +0 -316
  46. package/dist/ui/elements/accordion.tsx +0 -47
  47. package/dist/ui/elements/accordion.works.ts +0 -66
  48. package/dist/ui/elements/aspect-ratio.tsx +0 -31
  49. package/dist/ui/elements/aspect-ratio.works.ts +0 -5
  50. package/dist/ui/elements/avatar.tsx +0 -34
  51. package/dist/ui/elements/avatar.works.ts +0 -16
  52. package/dist/ui/elements/badge.tsx +0 -33
  53. package/dist/ui/elements/badge.works.ts +0 -9
  54. package/dist/ui/elements/bars.tsx +0 -50
  55. package/dist/ui/elements/bars.works.ts +0 -29
  56. package/dist/ui/elements/box.tsx +0 -36
  57. package/dist/ui/elements/breadcrumb.tsx +0 -38
  58. package/dist/ui/elements/breadcrumb.works.ts +0 -24
  59. package/dist/ui/elements/button.tsx +0 -46
  60. package/dist/ui/elements/button.works.tsx +0 -52
  61. package/dist/ui/elements/column.tsx +0 -29
  62. package/dist/ui/elements/consent.tsx +0 -71
  63. package/dist/ui/elements/consent.works.tsx +0 -132
  64. package/dist/ui/elements/donut.tsx +0 -45
  65. package/dist/ui/elements/donut.works.ts +0 -42
  66. package/dist/ui/elements/each.tsx +0 -34
  67. package/dist/ui/elements/each.works.tsx +0 -82
  68. package/dist/ui/elements/empty.tsx +0 -33
  69. package/dist/ui/elements/field.tsx +0 -45
  70. package/dist/ui/elements/field.works.ts +0 -65
  71. package/dist/ui/elements/funnel.tsx +0 -50
  72. package/dist/ui/elements/funnel.works.ts +0 -37
  73. package/dist/ui/elements/gauge.tsx +0 -40
  74. package/dist/ui/elements/gauge.works.ts +0 -42
  75. package/dist/ui/elements/glyph.tsx +0 -30
  76. package/dist/ui/elements/glyph.works.tsx +0 -42
  77. package/dist/ui/elements/grid.tsx +0 -29
  78. package/dist/ui/elements/heatmap.tsx +0 -48
  79. package/dist/ui/elements/heatmap.works.ts +0 -24
  80. package/dist/ui/elements/hit-box.tsx +0 -47
  81. package/dist/ui/elements/hit-box.works.tsx +0 -576
  82. package/dist/ui/elements/image.tsx +0 -29
  83. package/dist/ui/elements/line-chart.tsx +0 -45
  84. package/dist/ui/elements/line-chart.works.ts +0 -41
  85. package/dist/ui/elements/link.tsx +0 -38
  86. package/dist/ui/elements/link.works.ts +0 -25
  87. package/dist/ui/elements/media.tsx +0 -57
  88. package/dist/ui/elements/menu.tsx +0 -51
  89. package/dist/ui/elements/menu.works.tsx +0 -154
  90. package/dist/ui/elements/money.tsx +0 -36
  91. package/dist/ui/elements/money.works.ts +0 -9
  92. package/dist/ui/elements/pagination.tsx +0 -39
  93. package/dist/ui/elements/pagination.works.ts +0 -20
  94. package/dist/ui/elements/progress.tsx +0 -34
  95. package/dist/ui/elements/progress.works.ts +0 -8
  96. package/dist/ui/elements/radio-group.tsx +0 -50
  97. package/dist/ui/elements/radio-group.works.ts +0 -33
  98. package/dist/ui/elements/row.tsx +0 -29
  99. package/dist/ui/elements/scatter.tsx +0 -42
  100. package/dist/ui/elements/scatter.works.ts +0 -38
  101. package/dist/ui/elements/select.tsx +0 -43
  102. package/dist/ui/elements/select.works.ts +0 -5
  103. package/dist/ui/elements/separator.tsx +0 -31
  104. package/dist/ui/elements/skeleton.tsx +0 -31
  105. package/dist/ui/elements/slider.tsx +0 -34
  106. package/dist/ui/elements/slider.works.ts +0 -5
  107. package/dist/ui/elements/sparkline.tsx +0 -37
  108. package/dist/ui/elements/sparkline.works.ts +0 -32
  109. package/dist/ui/elements/spinner.tsx +0 -33
  110. package/dist/ui/elements/stack.tsx +0 -29
  111. package/dist/ui/elements/stat.tsx +0 -36
  112. package/dist/ui/elements/stat.works.ts +0 -14
  113. package/dist/ui/elements/surface.tsx +0 -36
  114. package/dist/ui/elements/surface.works.ts +0 -9
  115. package/dist/ui/elements/table.tsx +0 -62
  116. package/dist/ui/elements/table.works.ts +0 -30
  117. package/dist/ui/elements/tabs.tsx +0 -47
  118. package/dist/ui/elements/tabs.works.ts +0 -84
  119. package/dist/ui/elements/text.tsx +0 -33
  120. package/dist/ui/elements/textarea.tsx +0 -38
  121. package/dist/ui/elements/textarea.works.ts +0 -5
  122. package/dist/ui/elements/time.tsx +0 -40
  123. package/dist/ui/elements/time.works.ts +0 -29
  124. package/dist/ui/elements/toggle.tsx +0 -34
  125. package/dist/ui/elements/toggle.works.ts +0 -5
  126. package/dist/ui/elements/tooltip.tsx +0 -40
  127. package/dist/ui/elements/tooltip.works.ts +0 -77
  128. package/dist/ui/elements/value.tsx +0 -38
  129. package/dist/ui/elements/value.works.ts +0 -9
  130. package/dist/ui/elements.ts +0 -127
  131. package/dist/ui/focus-scope.ts +0 -121
  132. package/dist/ui/fonts.ts +0 -355
  133. package/dist/ui/generators/dev.d.ts +0 -1
  134. package/dist/ui/generators/dev.js +0 -62
  135. package/dist/ui/generators/dev.ts +0 -75
  136. package/dist/ui/generators/elements.ts +0 -796
  137. package/dist/ui/generators/i18n.ts +0 -283
  138. package/dist/ui/generators/icons.ts +0 -207
  139. package/dist/ui/generators/react.ts +0 -267
  140. package/dist/ui/generators/routes.ts +0 -847
  141. package/dist/ui/generators/show.ts +0 -261
  142. package/dist/ui/generators/styles.ts +0 -677
  143. package/dist/ui/generators/variants.ts +0 -196
  144. package/dist/ui/i18n/catalogs/en.ts +0 -191
  145. package/dist/ui/i18n/format.ts +0 -221
  146. package/dist/ui/i18n/locales.ts +0 -33
  147. package/dist/ui/i18n/parse.ts +0 -315
  148. package/dist/ui/i18n/translator.ts +0 -31
  149. package/dist/ui/i18n/types.ts +0 -96
  150. package/dist/ui/icons.ts +0 -331
  151. package/dist/ui/islands/map.ts +0 -58
  152. package/dist/ui/localizations.ts +0 -75
  153. package/dist/ui/material.ts +0 -236
  154. package/dist/ui/materials/mat_bordered.ts +0 -13
  155. package/dist/ui/materials/mat_ceramic.ts +0 -13
  156. package/dist/ui/materials/mat_clay.ts +0 -13
  157. package/dist/ui/materials/mat_elevated.ts +0 -24
  158. package/dist/ui/materials/mat_flat.ts +0 -34
  159. package/dist/ui/materials/mat_glass.ts +0 -20
  160. package/dist/ui/materials/mat_lit.ts +0 -13
  161. package/dist/ui/materials/mat_moulded.ts +0 -11
  162. package/dist/ui/materials.ts +0 -197
  163. package/dist/ui/modes.ts +0 -44
  164. package/dist/ui/navigation.tsx +0 -29
  165. package/dist/ui/positioning.ts +0 -90
  166. package/dist/ui/presented.tsx +0 -119
  167. package/dist/ui/provider.tsx +0 -184
  168. package/dist/ui/render-loop.ts +0 -133
  169. package/dist/ui/roving-focus.ts +0 -161
  170. package/dist/ui/screens.ts +0 -141
  171. package/dist/ui/scroll-lock.ts +0 -28
  172. package/dist/ui/series.ts +0 -97
  173. package/dist/ui/signals.ts +0 -386
  174. package/dist/ui/styleguide.ts +0 -231
  175. package/dist/ui/styleguides.ts +0 -374
  176. package/dist/ui/themes.ts +0 -22
  177. package/dist/ui/token.ts +0 -78
  178. package/dist/ui/tokens/shadow.ts +0 -25
  179. package/dist/ui/tokens.ts +0 -234
  180. package/dist/ui/validators.ts +0 -232
  181. package/dist/ui/variant.ts +0 -349
  182. package/dist/ui/variants/var_avatar-round.ts +0 -14
  183. package/dist/ui/variants/var_bubble-me.ts +0 -14
  184. package/dist/ui/variants/var_bubble-them.ts +0 -13
  185. package/dist/ui/variants/var_button-border.ts +0 -76
  186. package/dist/ui/variants/var_button-cta.ts +0 -76
  187. package/dist/ui/variants/var_button-destructive-ghost.ts +0 -72
  188. package/dist/ui/variants/var_button-destructive.ts +0 -76
  189. package/dist/ui/variants/var_button-fill.ts +0 -76
  190. package/dist/ui/variants/var_button-ghost.ts +0 -72
  191. package/dist/ui/variants/var_button-glass.ts +0 -55
  192. package/dist/ui/variants/var_button-normal.ts +0 -76
  193. package/dist/ui/variants/var_button-quiet.ts +0 -74
  194. package/dist/ui/variants/var_card.ts +0 -17
  195. package/dist/ui/variants/var_chip.ts +0 -13
  196. package/dist/ui/variants/var_choice.ts +0 -15
  197. package/dist/ui/variants/var_framed.ts +0 -10
  198. package/dist/ui/variants/var_glyph-plain.ts +0 -11
  199. package/dist/ui/variants/var_input.ts +0 -43
  200. package/dist/ui/variants/var_link-inline.ts +0 -29
  201. package/dist/ui/variants/var_list-row.ts +0 -34
  202. package/dist/ui/variants/var_rule.ts +0 -10
  203. package/dist/ui/variants/var_skeleton-block.ts +0 -12
  204. package/dist/ui/variants/var_spinner-turning.ts +0 -12
  205. package/dist/ui/variants/var_stat-tile.ts +0 -13
  206. package/dist/ui/variants/var_tab-bar.ts +0 -15
  207. package/dist/ui/variants/var_tap-target.ts +0 -25
  208. package/dist/ui/variants.ts +0 -64
  209. package/dist/ui/wrappers.tsx +0 -429
@@ -1,231 +0,0 @@
1
- import type { MODES } from './modes.ts'
2
- import { held, sheeted, type Holds, type Token } from './token.ts'
3
- import type { TOKENS } from './tokens.ts'
4
-
5
- type ModeId = (typeof MODES)['holds'][number]['id']
6
-
7
- export interface StyleGuide {
8
- readonly id: string
9
-
10
- readonly picks: readonly {
11
- readonly id: string
12
-
13
- readonly token: (typeof TOKENS)['holds'][number]['id'] | (typeof TOKENS)['declares'][number]['id']
14
-
15
- readonly is?: string
16
-
17
- readonly asks?: Readonly<Record<string, string | number>>
18
-
19
- readonly modes?: Readonly<Partial<Record<ModeId, string>>>
20
-
21
- readonly themes?: Readonly<
22
- Record<
23
- string,
24
- {
25
- readonly is?: string
26
- readonly modes?: Readonly<Partial<Record<ModeId, string>>>
27
- }
28
- >
29
- >
30
- }[]
31
- }
32
-
33
- export function themedBy(theme: string): string {
34
- return `:root[data-theme="${theme}"]`
35
- }
36
-
37
- function tokenFor(
38
- guide: StyleGuide | undefined,
39
- pick: { readonly id: string; readonly token: string },
40
- tokens: Readonly<Record<string, Token>>,
41
- ): Token {
42
- const found = tokens[pick.token]
43
- if (found !== undefined) return found
44
- throw new Error(
45
- `\`${guide?.id ?? 'a style guide'}\` states \`${pick.id}\`, and no token \`${pick.token}\` is ` +
46
- 'declared. A token has to exist before the style guide can give it a value.',
47
- )
48
- }
49
-
50
- const HEX = /^#[0-9a-fA-F]{3,8}$/
51
- const ZERO_OR_LENGTH = /^(0|\d+(\.\d+)?px)$/
52
- const LENGTH = /^\d+(\.\d+)?px$/
53
- const INTEGER = /^\d+$/
54
- const DECIMAL = /^\d+(\.\d+)?$/
55
- const TRACKING = /^-?\d+(\.\d+)?(em)?$/
56
- const DURATION = /^\d+(\.\d+)?ms$/
57
- const RATIO = /^\d+\s*\/\s*\d+$/
58
- const CALL = /^[a-z][a-zA-Z-]*\([^()]*\)$/
59
-
60
- interface Shape {
61
- readonly suits: (value: string) => boolean
62
- readonly looks: string
63
- }
64
-
65
- const matches = (test: RegExp, looks: string): Shape => ({ suits: (value) => test.test(value), looks })
66
-
67
- const among = (words: readonly string[]): Shape => ({
68
- suits: (value) => words.includes(value),
69
- looks: words.map((word) => `\`${word}\``).join(' · '),
70
- })
71
-
72
- const prefixed = (starts: readonly string[], looks: string): Shape => ({
73
- suits: (value) => starts.some((one) => value.startsWith(one)),
74
- looks,
75
- })
76
-
77
- const SHAPES: Readonly<Record<string, Shape>> = {
78
- colour: {
79
- suits: (value) => HEX.test(value) || value.startsWith('color-mix(') || value === 'transparent',
80
- looks: 'a hex colour, a `color-mix(...)`, or `transparent`',
81
- },
82
-
83
- shadow: {
84
- suits: (value) => value === 'none' || /^(inset\s+)?-?\d[\d.]*[a-z%]*\s+-?\d/.test(value),
85
- looks: 'a box-shadow list — at least an x and a y offset — or `none`',
86
- },
87
- space: matches(ZERO_OR_LENGTH, 'a length in pixels, or `0`'),
88
- radius: matches(ZERO_OR_LENGTH, 'a length in pixels, or `0`'),
89
- edge: matches(LENGTH, 'a length in pixels'),
90
- type: matches(LENGTH, 'a length in pixels'),
91
- icon: matches(LENGTH, 'a length in pixels'),
92
- control: matches(LENGTH, 'a length in pixels'),
93
- weight: matches(INTEGER, 'a bare number'),
94
- layer: matches(INTEGER, 'a bare number'),
95
- leading: matches(DECIMAL, 'a bare decimal'),
96
- opacity: matches(DECIMAL, 'a bare decimal'),
97
- tracking: matches(TRACKING, 'a number, signed, with an optional `em`'),
98
- motion: matches(DURATION, 'a duration in `ms`'),
99
- ratio: matches(RATIO, 'two numbers divided, like `16 / 9`'),
100
- filter: matches(CALL, 'a single CSS function call'),
101
- transform: matches(CALL, 'a single CSS function call'),
102
-
103
- animation: matches(/^[a-z][a-z0-9]*(-[a-z0-9]+)*$/, 'the name of a declared animation'),
104
- line: among(['solid', 'dashed', 'none']),
105
- case: among(['uppercase', 'none']),
106
- cursor: among(['pointer', 'grab', 'grabbing', 'not-allowed']),
107
- img: among(['none']),
108
-
109
- layout: among(['flex', 'block', 'inline-block']),
110
- flow: among(['row', 'column']),
111
-
112
- align: among(['center', 'flex-start', 'flex-end', 'stretch', 'baseline', 'space-between']),
113
- wrap: among(['wrap', 'nowrap']),
114
- position: among(['static', 'relative', 'absolute', 'fixed', 'sticky']),
115
- visibility: among(['visible', 'hidden', 'collapse']),
116
- reach: among(['auto', 'none']),
117
- picking: among(['auto', 'none', 'text', 'all']),
118
- touch: among(['auto', 'none', 'pan-x', 'pan-y', 'manipulation']),
119
- readying: among(['auto', 'transform', 'opacity', 'contents', 'scroll-position']),
120
- origin: among(['center', 'top', 'bottom', 'left', 'right']),
121
- overflow: among(['visible', 'hidden', 'clip', 'scroll', 'auto']),
122
- blur: prefixed(['blur('], 'a `blur(...)`'),
123
- ease: prefixed(['cubic-bezier('], 'a `cubic-bezier(...)`'),
124
- noise: prefixed(['url('], 'a `url(...)`'),
125
- gradient: prefixed(
126
- ['linear-gradient(', 'radial-gradient(', 'conic-gradient('],
127
- 'a `linear-gradient(...)`, `radial-gradient(...)` or `conic-gradient(...)`',
128
- ),
129
- texture: prefixed(
130
- ['repeating-linear-gradient(', 'repeating-radial-gradient('],
131
- 'a `repeating-linear-gradient(...)` or `repeating-radial-gradient(...)`',
132
- ),
133
- }
134
-
135
- export function kindsHeld(): readonly string[] {
136
- return Object.keys(SHAPES).sort()
137
- }
138
-
139
- export function shapeOf(kind: string): { readonly suits: (value: string) => boolean; readonly looks: string } | undefined {
140
- return SHAPES[kind]
141
- }
142
-
143
- function suited(
144
- guide: StyleGuide | undefined,
145
- pick: { readonly id: string; readonly token: string },
146
- value: string,
147
- ): string {
148
- const shape = SHAPES[pick.token]
149
- if (shape === undefined || shape.suits(value)) return value
150
- throw new Error(
151
- `\`${guide?.id ?? 'a style guide'}\` states \`${pick.id}\` as "${value}", and \`${pick.token}\` holds ` +
152
- `${shape.looks}. A row's value has to look like the kind it names.`,
153
- )
154
- }
155
-
156
- function valued(
157
- guide: StyleGuide | undefined,
158
- pick: StyleGuide['picks'][number],
159
- tokens: Readonly<Record<string, Token>>,
160
- ): Holds {
161
- const token = tokenFor(guide, pick, tokens)
162
- if (pick.is !== undefined) return sheeted(pick.id, suited(guide, pick, pick.is))
163
- if (token.makes !== undefined) return sheeted(pick.id, token.makes(pick.asks ?? {}))
164
- throw new Error(
165
- `\`${pick.id}\` states no value, and \`${pick.token}\` carries no rule to make one from. ` +
166
- 'A style guide row is either a value or the arguments a token turns into one.',
167
- )
168
- }
169
-
170
- export function chosen(
171
- guide: StyleGuide | undefined,
172
- tokens: Readonly<Record<string, Token>>,
173
- ): Readonly<Record<string, Holds>> {
174
- const made: Record<string, Holds> = {}
175
- for (const pick of guide?.picks ?? []) made[pick.id] = valued(guide, pick, tokens)
176
-
177
- const named = new Set(Object.keys(made))
178
- for (const id of Object.keys(made)) held(named, made[id] ?? '', id)
179
-
180
- return made
181
- }
182
-
183
- function themesFor(
184
- guide: StyleGuide | undefined,
185
- pick: StyleGuide['picks'][number],
186
- themes: ReadonlySet<string>,
187
- ): void {
188
- for (const theme of Object.keys(pick.themes ?? {})) {
189
- if (themes.has(theme)) continue
190
- const list = [...themes].sort().map((one) => `\`${one}\``)
191
- const known =
192
- list.length === 0
193
- ? 'The app declares no themes.'
194
- : `The declared themes are ${list.join(', ')}.`
195
- throw new Error(
196
- `\`${guide?.id ?? 'a style guide'}\` states \`${pick.id}\`, and no theme \`${theme}\` is ` +
197
- `declared. ${known}`,
198
- )
199
- }
200
- }
201
-
202
- export function shifted(
203
- guide: StyleGuide | undefined,
204
- tokens: Readonly<Record<string, Token>>,
205
- mode: string | undefined,
206
- theme: string | undefined,
207
- themes: ReadonlySet<string>,
208
- ): Readonly<Record<string, Holds>> {
209
- const named = new Set((guide?.picks ?? []).map((pick) => pick.id))
210
- const made: Record<string, Holds> = {}
211
-
212
- for (const pick of guide?.picks ?? []) {
213
- tokenFor(guide, pick, tokens)
214
- themesFor(guide, pick, themes)
215
-
216
- const under = theme === undefined ? undefined : pick.themes?.[theme]
217
-
218
- const value =
219
- theme === undefined
220
- ? mode === undefined
221
- ? undefined
222
- : pick.modes?.[mode as ModeId]
223
- : mode === undefined
224
- ? under?.is
225
- : under?.modes?.[mode as ModeId]
226
-
227
- if (value !== undefined) made[pick.id] = held(named, suited(guide, pick, value), pick.id)
228
- }
229
-
230
- return made
231
- }
@@ -1,374 +0,0 @@
1
- import type { Threaded } from '@prestack/thread/core/thread'
2
-
3
- import type { StyleGuide } from './styleguide.ts'
4
- import type { Token } from './token.ts'
5
- import type { SHADOW } from './tokens/shadow.ts'
6
-
7
- const here = 'thread/ui/styleguides.ts'
8
-
9
- const said = [
10
- {
11
- on: '2026-08-24',
12
- said: 'The list of variables with default values is the style guide. Style guide says we have colour accent, colour background, colour that, and defines the values that are a pattern we can use. We have rows here too, kinda like tailwind.',
13
- },
14
- ]
15
-
16
- type Unsectioned = Omit<Threaded, 'at'> & StyleGuide['picks'][number]
17
-
18
- type Row = Unsectioned & { readonly section: string }
19
-
20
- const section = (name: string, rows: readonly Unsectioned[]): readonly Row[] =>
21
- rows.map((row) => ({ ...row, section: name }))
22
-
23
- const one = (
24
- id: string,
25
- token: Row['token'],
26
- is: string,
27
- describes: string,
28
- modes?: Row['modes'],
29
- ): Unsectioned => ({
30
- id,
31
- token,
32
- is,
33
- describes,
34
- version: 1,
35
- ...(modes === undefined ? {} : { modes }),
36
- })
37
-
38
- const made = <T extends 'shadow'>(
39
- id: string,
40
- token: T,
41
- asks: { readonly shadow: typeof SHADOW extends Token<infer Asks> ? Asks : never }[T],
42
- describes: string,
43
- ): Unsectioned => ({ id, token, asks, describes, version: 1 })
44
-
45
- export const holds: readonly Row[] = [
46
- ...section('grounds', [
47
- one('colour-page', 'colour', '#f8fbfa', 'What lies behind everything — the furthest surface back, which nothing sits under.', { dark: '#0d100f' }),
48
- one('colour-surface', 'colour', '#ffffff', 'The panel a thing is drawn on. One step forward from the page, and what a card or a sheet is painted in.', { dark: '#161a18' }),
49
- one('colour-raised', 'colour', '#ffffff', 'A surface lifted above its neighbours — a menu over a page, a dialog over a menu.', { dark: '#1f2322' }),
50
- one('colour-sunken', 'colour', '#f0f5f3', 'A surface pressed into the page — a well, an input trough, an inactive tab.', { dark: '#070a09' }),
51
- one('colour-clear', 'colour', 'transparent', 'No ground at all — whatever is behind shows through unchanged. Named rather than left unset, because a ghost button choosing to have no fill is a decision, and a material that sets nothing is one somebody forgot to finish.'),
52
- ]),
53
-
54
- ...section('ink', [
55
- one('colour-ink', 'colour', '#141716', 'What words are written in. The darkest thing on a light page, and the first colour a reader resolves.', { dark: '#f3f6f5' }),
56
- one('colour-ink-soft', 'colour', '#666e6b', 'Wording that supports rather than states — a caption, a hint, a label beside a value.', { dark: '#9ea7a3' }),
57
- one('colour-ink-faint', 'colour', '#9ea7a3', 'Wording that is present but not being read — a placeholder, a disabled control, a watermark.', { dark: '#6b7470' }),
58
- one('colour-on-accent', 'colour', '#141716', 'What words become when they sit on the accent. Never assumed to be white — it is chosen so the pair can be read.', { dark: '#0f1211' }),
59
- ]),
60
-
61
- ...section('accent', [
62
- one('colour-accent', 'colour', '#43e2b6', 'The one colour an app is recognised by, and the one that says which thing on a screen is being asked for.', { dark: '#47f5c5' }),
63
- one('colour-accent-hover', 'colour', 'color-mix(in oklab, var(--colour-accent) 92%, var(--colour-ink))', 'The accent colour with a little ink mixed in — enough that a surface being pointed at reads as reacting, not enough that it stops reading as the accent.'),
64
- one('colour-accent-press', 'colour', 'color-mix(in oklab, var(--colour-accent) 85%, var(--colour-ink))', 'The accent colour with more ink mixed in than its hover — enough that a surface being pressed as well as pointed at reads as pushed in, not enough that it stops reading as the accent.'),
65
- one('colour-positive', 'colour', '#09bb83', 'Something completed, granted or in good standing. Never the only signal, because a third of readers cannot separate it from negative. It sits in the accent’s family and is darker and greener than it, so a success reads as good news rather than as the thing being asked for.', { dark: '#48dba2' }),
66
- one('colour-negative', 'colour', '#dc2626', 'Something refused, failed or destructive. Reserved for consequence, so it keeps its force.'),
67
- one('colour-negative-hover', 'colour', 'color-mix(in oklab, var(--colour-negative) 92%, var(--colour-ink))', 'The negative colour with a little ink mixed in, so a destructive control being pointed at reads as reacting without stopping reading as destructive.'),
68
- one('colour-negative-press', 'colour', 'color-mix(in oklab, var(--colour-negative) 85%, var(--colour-ink))', 'The negative colour with more ink mixed in than its hover — a destructive control being pushed in.'),
69
- one('colour-on-negative', 'colour', '#ffffff', 'What words become when they sit on the negative colour. Chosen so the pair can be read, never assumed to be white for the same reason the accent one is not.'),
70
- one('colour-warning', 'colour', '#d97706', 'Something that will go wrong if left alone. Between positive and negative rather than a weaker negative.'),
71
- one('colour-info', 'colour', '#0284c7', 'Something worth knowing that asks for nothing. Distinct from accent so an explanation does not read as a request.'),
72
- ]),
73
-
74
- ...section('edges', [
75
- one('colour-edge', 'colour', '#dce3e0', 'The line that separates without drawing attention — between rows, around a quiet panel.', { dark: '#2a2f2d' }),
76
- one('colour-edge-faint', 'colour', 'color-mix(in oklab, var(--colour-ink) 12%, transparent)', 'The edge of a surface that is only just an edge — the ink of the page, thinned until it reads as a boundary rather than a line. It thins less in the dark, because a dark surface swallows a border that a light one would show.', { dark: 'color-mix(in oklab, var(--colour-ink) 22%, transparent)' }),
77
- one('colour-edge-glass', 'colour', 'color-mix(in oklab, var(--colour-on-accent) 55%, transparent)', 'The edge of something you can see through, caught by the light behind it rather than drawn on top.', { dark: 'color-mix(in oklab, var(--colour-ink) 28%, transparent)' }),
78
- one('colour-glass', 'colour', 'color-mix(in oklab, var(--colour-surface) 52%, transparent)', 'A surface you can see through — the surface colour, held back far enough for what is behind it to carry. It holds back less in the dark, because a dark backdrop shows through more readily.', { dark: 'color-mix(in oklab, var(--colour-surface) 64%, transparent)' }),
79
- one('colour-surface-lit', 'colour', 'color-mix(in oklab, var(--colour-surface) 91%, var(--colour-accent))', 'The surface colour with a trace of the accent mixed in — enough that a surface asking for attention reads as lit from within, not enough that it stops reading as the surface.'),
80
- one('colour-surface-alert', 'colour', 'color-mix(in oklab, var(--colour-surface) 91%, var(--colour-negative))', 'The surface colour with a trace of the negative mixed in — what appears under a pointer on something destructive that has no fill of its own, so the ground it gains still says what it will do.'),
81
- one('colour-edge-lit', 'colour', 'color-mix(in oklab, var(--colour-accent) 76%, transparent)', 'The edge of something lit rather than framed — mostly accent, thinned just enough to glow at the boundary instead of drawing a line across it.'),
82
- one('colour-edge-clay', 'colour', 'color-mix(in oklab, var(--colour-on-accent) 15%, transparent)', 'The faint rim of a surface moulded rather than cut — barely there, the way light catches the edge of something raised and rounded instead of framed.'),
83
- one('colour-edge-ceramic', 'colour', 'color-mix(in oklab, var(--colour-on-accent) 10%, transparent)', 'The faint rim of a glazed surface, thinner than a moulded edge because a glaze catches less light at the boundary than a raised surface does.'),
84
- one('colour-edge-strong', 'colour', '#d6d3d1', 'The line that is meant to be seen — a focused field, a chosen option, a boundary that matters.', { dark: '#3f3f3f' }),
85
- one('colour-scrim', 'colour', '#0a0a0a', 'The veil laid over a page while something in front of it is being answered.'),
86
- ]),
87
-
88
- ...section('space', [
89
- one('space-0', 'space', '0', 'No room at all. Named rather than written as zero so that removing space is a decision on the sheet.'),
90
- one('space-1', 'space', '4px', 'The smallest gap that still separates — between an icon and the word beside it.'),
91
- one('space-2', 'space', '8px', 'A gap inside one thing: the parts of a chip, a label above its field.'),
92
- one('space-3', 'space', '12px', 'The room inside something small and pressable — a button, a row of a list.'),
93
- one('space-4', 'space', '16px', 'The room inside an ordinary panel, and the gap between things that belong together.'),
94
- one('space-5', 'space', '24px', 'The gap between things that are related but separate — two cards in a column.'),
95
- one('space-6', 'space', '32px', 'The gap between sections of one screen.'),
96
- one('space-7', 'space', '48px', 'The gap between one part of a page and the next, where a reader should feel a break.'),
97
- one('space-8', 'space', '64px', 'The room around a whole region — the margin a page breathes in.'),
98
- one('space-9', 'space', '96px', 'The largest room on the scale, for the space above and below a hero.'),
99
- ]),
100
-
101
- ...section('radius', [
102
- one('radius-none', 'radius', '0', 'Square corners. Named so that a thing choosing to be square is a decision rather than an omission.'),
103
- one('radius-sm', 'radius', '6px', 'Barely rounded — enough to soften a hard edge without reading as a shape.'),
104
- one('radius-md', 'radius', '14px', 'The ordinary rounding of a panel, a field, a card.'),
105
- one('radius-lg', 'radius', '24px', 'Rounding that becomes part of the look — a sheet, a large surface, a modal.'),
106
- one('radius-xl', 'radius', '32px', 'The most rounded a rectangle gets before it stops reading as one.'),
107
- one('radius-pill', 'radius', '999px', 'Fully round ends, whatever the height. What makes a chip a chip.'),
108
- ]),
109
-
110
- ...section('borders', [
111
- one('edge-hair', 'edge', '0.5px', 'A line at the limit of what a screen can draw — present, and not a border anybody notices.'),
112
- one('edge-thin', 'edge', '1px', 'An ordinary border.'),
113
- one('edge-thick', 'edge', '2px', 'A border doing work — a focus ring, a chosen state, a deliberate frame.'),
114
- one('line-solid', 'line', 'solid', 'An unbroken line. The default way a border is drawn.'),
115
- one('line-dashed', 'line', 'dashed', 'A broken line, for a boundary that is provisional — a drop target, an empty slot.'),
116
- one('line-off', 'line', 'none', 'No line drawn, however wide the border was set. How a border is removed without removing the rule that put it there.'),
117
- ]),
118
-
119
- ...section('shadow', [
120
- one('shadow-0', 'shadow', 'none', 'Flat on the surface. Named so that being flat is a choice a variant makes.'),
121
- made('shadow-1', 'shadow', { y: 1, blur: 2, alpha: 0.06 }, 'Barely lifted — enough to separate a row from what is behind it.'),
122
- made('shadow-2', 'shadow', { y: 4, blur: 12, alpha: 0.1 }, 'Lifted — a card, a resting panel.'),
123
- made('shadow-3', 'shadow', { y: 12, blur: 28, alpha: 0.14 }, 'Well clear of the page — a menu, a popover.'),
124
- made('shadow-4', 'shadow', { y: 24, blur: 56, alpha: 0.2 }, 'Above everything — a dialog, a sheet dragged over the app.'),
125
- made('shadow-inset', 'shadow', { y: 2, blur: 4, alpha: 0.08, inset: 'inset' }, 'Pressed in rather than lifted out — a trough, a held button.'),
126
- one('shadow-glow', 'shadow', '0 0 24px rgba(14, 165, 233, 0.45)', 'Light thrown outward rather than a shadow cast — how a thing reads as lit.'),
127
- one('shadow-hard', 'shadow', '5px 5px 0 0 var(--colour-ink)', 'An offset with no blur at all — a second edge stacked behind the surface rather than a shadow it casts. What gives a bordered card its cut-paper look.'),
128
- one('shadow-moulded', 'shadow', '8px 8px 19px color-mix(in oklab, var(--colour-ink) 45%, transparent), -8px -8px 19px color-mix(in oklab, var(--colour-surface) 85%, transparent)', 'A shadow paired with its own highlight, dark on one side and pale on the other — how a surface reads as pressed from the page rather than laid on top of it.'),
129
- ]),
130
-
131
- ...section('blur', [
132
- one('blur-sm', 'blur', 'blur(8px) saturate(1.2)', 'What shows through is softened a little. Saturation rides along, because a blur without it reads grey.'),
133
- one('blur-md', 'blur', 'blur(17px) saturate(1.4)', 'The ordinary glass amount — a surface you can tell something is behind.'),
134
- one('blur-lg', 'blur', 'blur(32px) saturate(1.6)', 'What is behind becomes colour and light rather than shape.'),
135
- ]),
136
-
137
- ...section('motion', [
138
- one('motion-instant', 'motion', '0ms', 'No transition. Named so that a thing changing at once is a decision.'),
139
- one('motion-quick', 'motion', '150ms', 'A change that answers a press — fast enough to feel like the tap did it.'),
140
- one('motion-normal', 'motion', '250ms', 'An ordinary change of state, long enough to be followed by the eye.'),
141
- one('motion-slow', 'motion', '400ms', 'Something arriving or leaving, where the movement itself is telling you what happened.'),
142
- ]),
143
-
144
- ...section('easing', [
145
- one('ease-out', 'ease', 'cubic-bezier(0.4, 0, 0.2, 1)', 'Fast then settling. What almost everything entering a screen should use.'),
146
- one('ease-in', 'ease', 'cubic-bezier(0.4, 0, 1, 1)', 'Slow then away. For a thing leaving, where the exit should not be watched.'),
147
- one('ease-in-out', 'ease', 'cubic-bezier(0.65, 0, 0.35, 1)', 'Eased at both ends, for a change that starts and finishes on the screen.'),
148
- one('ease-steady', 'ease', 'cubic-bezier(0, 0, 1, 1)', 'Paced evenly from end to end. The one easing a repeating turn can use, because anything else stutters once every revolution.'),
149
- one('ease-spring', 'ease', 'cubic-bezier(0.32, 0.72, 0.28, 1.04)', 'Overshoots slightly and comes back, which reads as a physical thing rather than a value changing.'),
150
- ]),
151
-
152
- ...section('layer', [
153
- one('layer-base', 'layer', '0', 'The plane the page is laid out on.'),
154
- one('layer-raised', 'layer', '10', 'Above the page but part of it — a sticky header inside a panel, a hovered card.'),
155
- one('layer-sticky', 'layer', '20', 'Pinned while content moves beneath it.'),
156
- one('layer-overlay', 'layer', '30', 'A menu, a popover, a tooltip — above the app and dismissed by looking away.'),
157
- one('layer-modal', 'layer', '40', 'The thing that must be answered, and the veil that comes with it.'),
158
- one('layer-toast', 'layer', '50', 'Above even a dialog, because a message about the whole app outranks whatever is open.'),
159
- one('layer-carried', 'layer', '60', 'What is following a finger or a cursor right now, which nothing may cover while it is being carried. The top of this ladder, and the only thing that belongs there — the drag ghost wrote `2147483647` before this row existed, which is a shout rather than a decision.'),
160
- ]),
161
-
162
- ...section('control', [
163
- one('control-sm', 'control', '32px', 'The height of something pressable in a dense row.'),
164
- one('control-md', 'control', '40px', 'The ordinary height of a button or a field.'),
165
- one('control-lg', 'control', '48px', 'The height of a primary action, and the smallest a touch target should be on a phone.'),
166
- one('control-radius', 'radius', '14px', 'How far the corner of a pressable thing is turned. Every button takes this one row, so squaring the whole app is one change here and none anywhere else — which is why it is not `radius-md` written into fourteen variants.'),
167
- one('control-pad-x', 'space', '20px', 'The room to the left and right of what a control holds. Its own row rather than a step on the space scale, because the gap between two cards and the room inside a button are different decisions that happen to have landed on the same number.'),
168
- one('control-pad-y', 'space', '8px', 'The room above and below what a control holds. Less than the horizontal room, which is what stops a button reading as a square with a word in it. It is what `control-md` has left after `icon-md`, and that is not a coincidence to be maintained by hand: at 10px the three rows could not all hold — 40 less twice 10 leaves 20, and the symbol it was holding was 24 — so every icon in every button overflowed its own padding by 2px and sat closer to the edge than any row said. Measured 2026-09-03 in a running app, off the served sheet.'),
169
- one('control-pad-square', 'space', '8px', 'The room on every side of a control holding nothing but a symbol. One value on all four sides, so the thing comes out square whatever is in it — which at 10px it did not: 24 and twice 10 is 44, against a `control-md` of 40, so a square control could not also be the height of the buttons beside it. Same arithmetic as `control-pad-y` above and the same number, kept as its own row because the horizontal room is the decision that differs.'),
170
- one('control-gap', 'space', '8px', 'The distance between the parts of one control — a symbol and the word beside it.'),
171
- one('control-edge', 'edge', '1px', 'How thick the line around a control is drawn, where it is drawn at all.'),
172
- one('control-ring', 'edge', '2px', 'How thick the ring a keyboard draws around a control is. Thicker than the control’s own edge, because a ring that matches the border is a ring nobody sees.'),
173
- one('control-ring-offset', 'space', '4px', 'How far the keyboard’s ring sits outside the control, so it reads as around the thing rather than as part of it.'),
174
- ]),
175
-
176
- ...section('opacity', [
177
- one('opacity-none', 'opacity', '0', 'Not drawn at all, while still occupying its place. What a thing fades up from and down to.'),
178
- one('opacity-full', 'opacity', '1', 'Fully drawn. Named so that arriving at ordinary is a value an animation can name rather than a number it writes.'),
179
- one('opacity-disabled', 'opacity', '0.45', 'Faded far enough to read as unavailable, not so far that it cannot be read at all.'),
180
- one('opacity-muted', 'opacity', '0.65', 'Present and receded — a thing still live but not being attended to.'),
181
- ]),
182
-
183
- ...section('gradient', [
184
- one('gradient-sheen', 'gradient', 'linear-gradient(180deg, color-mix(in oklab, var(--colour-surface) 50%, var(--colour-raised)), color-mix(in oklab, var(--colour-surface) 92%, var(--colour-ink)))', 'Light falling down the face of a surface, brightest at the top. What makes a glazed thing read as glazed.'),
185
- one('gradient-fade', 'gradient', 'linear-gradient(180deg, transparent, var(--colour-page))', 'A surface dissolving into the page beneath it, for the bottom of a scrolling region.'),
186
- ]),
187
-
188
- ...section('texture', [
189
- one('texture-stripe', 'texture', 'repeating-linear-gradient(45deg, color-mix(in oklab, var(--colour-ink) 6%, transparent) 0 1px, transparent 1px 8px)', 'Fine diagonal ruling, the quietest way to say a surface is not blank.'),
190
- ]),
191
-
192
- ...section('noise', [
193
- one('noise-grain', 'noise', 'url("data:image/svg+xml,%3Csvg xmlns=%27http://www.w3.org/2000/svg%27%3E%3Cfilter id=%27n%27%3E%3CfeTurbulence type=%27fractalNoise%27 baseFrequency=%270.8%27/%3E%3C/filter%3E%3Crect width=%27100%25%27 height=%27100%25%27 filter=%27url(%23n)%27 opacity=%270.035%27/%3E%3C/svg%3E")', 'A faint scattered grain that stops a large flat area reading as dead. Generated rather than drawn, so it costs nothing to ship.'),
194
- ]),
195
-
196
- ...section('image', [
197
- one('img-none', 'img', 'none', 'No picture at all. Named so that clearing one is a decision rather than an omission.'),
198
- ]),
199
-
200
- ...section('ratio', [
201
- one('ratio-wide', 'ratio', '16 / 9', 'The shape video is shot in, and what a media block holds before anything has loaded.'),
202
- one('ratio-square', 'ratio', '1 / 1', 'Equal sides — an avatar, a tile, a thumbnail.'),
203
- one('ratio-portrait', 'ratio', '3 / 4', 'Taller than it is wide, for a photograph held the way a phone is held.'),
204
- ]),
205
-
206
- ...section('filter', [
207
- one('filter-dimmed', 'filter', 'brightness(0.75)', 'Drawn darker than it is, for something behind what is being attended to.'),
208
- one('filter-blurred', 'filter', 'blur(8px)', 'Softened past reading — where a thing sharpening into place starts, and where one dissolving ends.'),
209
- one('filter-sharp', 'filter', 'blur(0px)', 'In focus. Named so that arriving at sharp is a value an animation can name rather than the absence of one.'),
210
- one('filter-inert', 'filter', 'grayscale(1)', 'Drained of colour, for a thing present but out of play.'),
211
- ]),
212
-
213
- ...section('transform', [
214
- one('transform-rest', 'transform', 'scale(1)', 'Where a thing sits when nothing is happening to it. Named so that returning to rest is a value an animation can name rather than the absence of one.'),
215
- one('transform-press', 'transform', 'scale(0.97)', 'Shrunk very slightly, which is what makes a press feel like it landed.'),
216
- one('transform-lift', 'transform', 'translateY(-2px)', 'Raised a little off the page, for a thing being pointed at.'),
217
- one('transform-shrink', 'transform', 'scale(0.94)', 'Pulled in further than a press — small enough to read as leaving rather than as reacting.'),
218
- one('transform-below', 'transform', 'translateY(8px)', 'Held below where it belongs, which is where a thing rising into place starts and a thing falling out of it ends.'),
219
- one('transform-above', 'transform', 'translateY(-8px)', 'Held above where it belongs — where a thing dropping into place starts, and where a thing leaving upward ends.'),
220
- one('transform-far-left', 'transform', 'translateX(-24px)', 'Held off to one side, far enough to read as coming from off-stage rather than as a nudge.'),
221
- one('transform-far-right', 'transform', 'translateX(24px)', 'The same distance the other way.'),
222
- one('transform-swell', 'transform', 'scale(1.04)', 'Grown very slightly — the overshoot a thing passes through on its way to resting, and what a hover uses to say it is reachable.'),
223
- one('transform-grow', 'transform', 'scale(1.12)', 'Grown enough to read as arriving from nearer the reader. Where a zoom starts, and where one ends.'),
224
- one('transform-tilt', 'transform', 'rotate(-3deg)', 'Turned slightly one way. Half of a wobble, and too small to read as rotation on its own.'),
225
- one('transform-tilt-back', 'transform', 'rotate(3deg)', 'Turned slightly the other way. The other half.'),
226
- one('transform-turn', 'transform', 'rotate(360deg)', 'A full revolution, which lands exactly where it started and is therefore the only rotation that can repeat without a jump.'),
227
- one(
228
- 'transform-carried',
229
- 'transform',
230
- 'scale(0.96)',
231
- 'How big a thing is while it is being carried, composed after wherever the pointer has taken it. Smaller, because something lifted off the page is further from the reader than the page it left. Where it IS cannot be a row — that is the pointer’s to say — but how it looks while it is there is one, and changing this changes every drag in an app at once.',
232
- ),
233
- one(
234
- 'transform-carried-turn',
235
- 'transform',
236
- 'rotate(-1.5deg)',
237
- 'How far a carried thing hangs off square, composed after its size. It is a second row rather than part of the first because a `transform` row holds ONE function call — `scale(0.96) rotate(-1.5deg)` is refused by the kind, and refused loudly: the generator throws and no stylesheet is written at all, which is how a carry that had a size and a tilt ended up with neither and moved nowhere.',
238
- ),
239
- one('transform-left', 'transform', 'translateX(-4px)', 'Pushed slightly off centre one way. Half of a refusal.'),
240
- one('transform-right', 'transform', 'translateX(4px)', 'Pushed slightly off centre the other way. The other half.'),
241
- ]),
242
-
243
- ...section('animation', [
244
- one('animation-press', 'animation', 'press', 'What anything pressable plays when it is pressed.'),
245
- one('animation-appear', 'animation', 'appear', 'What something plays arriving in place, where it was always going to be.'),
246
- one('animation-dismiss', 'animation', 'dismiss', 'What something plays on its way out, where it is not coming back.'),
247
- one('animation-rise', 'animation', 'rise', 'What a sheet, a menu or a toast plays coming in from below.'),
248
- one('animation-fall', 'animation', 'fall', 'The exit that answers a rise.'),
249
- one('animation-waiting', 'animation', 'pulse', 'What something plays while it waits — repeated, and saying nothing about how long is left.'),
250
- one('animation-working', 'animation', 'spin', 'What something plays while it works and cannot say how far along it is.'),
251
- one('animation-refused', 'animation', 'shake', 'What a control plays when what was offered was not accepted.'),
252
- ]),
253
-
254
- ...section('animation', [
255
- one('animation-drop', 'animation', 'drop', 'What something plays arriving from above — the entrance that answers a rise.'),
256
- one('animation-slide-in-left', 'animation', 'slide-in-left', 'What something plays coming in from the left, for a panel or a step arriving from off-stage.'),
257
- one('animation-slide-in-right', 'animation', 'slide-in-right', 'What something plays coming in from the right.'),
258
- one('animation-slide-out-left', 'animation', 'slide-out-left', 'What something plays leaving to the left.'),
259
- one('animation-slide-out-right', 'animation', 'slide-out-right', 'What something plays leaving to the right.'),
260
- one('animation-zoom-in', 'animation', 'zoom-in', 'What something plays arriving from nearer the reader, shrinking back to its true size.'),
261
- one('animation-zoom-out', 'animation', 'zoom-out', 'What something plays leaving toward the reader as it fades.'),
262
- one('animation-pop-in', 'animation', 'pop-in', 'What something plays arriving with an overshoot — past its size and back, which reads as physical rather than as a value changing.'),
263
- one('animation-pop-out', 'animation', 'pop-out', 'What something plays leaving with a small swell first, the exit that answers a pop.'),
264
- one('animation-blur-in', 'animation', 'blur-in', 'What something plays sharpening into place out of softness.'),
265
- one('animation-blur-out', 'animation', 'blur-out', 'What something plays dissolving rather than moving.'),
266
- one('animation-hover-lift', 'animation', 'lift', 'What something pointed at plays to say it is reachable — raised a little off the page.'),
267
- one('animation-hover-swell', 'animation', 'swell', 'What something pointed at plays when lifting it would move its neighbours: it grows very slightly instead.'),
268
- one('animation-hover-nudge', 'animation', 'nudge', 'What a marker beside wording plays when the wording is pointed at — a chevron leaning the way it would take you.'),
269
- one('animation-attention', 'animation', 'wobble', 'What something plays to be noticed without being wrong — turned one way and back.'),
270
- one('animation-flash', 'animation', 'flash', 'What something plays when its value has just changed underneath the reader, so the change is not missed.'),
271
- one('animation-glow', 'animation', 'glow', 'What something plays to read as lit — the light around it swelling and settling.'),
272
- ]),
273
-
274
- ...section('cursor', [
275
- one('cursor-press', 'cursor', 'pointer', 'Over something that will act if pressed.'),
276
- one('cursor-drag', 'cursor', 'grab', 'Over something that can be picked up.'),
277
- one('cursor-carrying', 'cursor', 'grabbing', 'Over something currently being carried.'),
278
- one('cursor-refuse', 'cursor', 'not-allowed', 'Over something that will not take what is being offered.'),
279
- ]),
280
-
281
- ...section('layout', [
282
- one('layout-flex', 'layout', 'flex', 'Children laid along one axis, each taking the room it needs. The arrangement nearly everything uses.'),
283
- one('layout-block', 'layout', 'block', 'Children stacked as the page stacks paragraphs, each taking the full width. What a thing that is a box rather than an arrangement says — `hit-box` is the one that needs it, being a region a pointer is watched over rather than an arrangement of anything.'),
284
- one('layout-inline-block', 'layout', 'inline-block', 'A box that sits in a line rather than starting one, and is a block on the inside. What wording needs in order to be trimmed: `text-box-trim` acts on a block container’s first and last line boxes, and an inline box is not one — so a role carrying the trim reaches nothing when the thing wearing it is inline. Measured 2026-09-03 in a running app: 66 of 90 text elements were trimmed to their cap height exactly, and the 24 that were inline were untouched, every one of them the sole text child of a block parent. Inline-block rather than block because wording still belongs in a line beside other things when an app puts it there.'),
285
- ]),
286
-
287
- ...section('flow', [
288
- one('flow-across', 'flow', 'row', 'Children run left to right.'),
289
- one('flow-down', 'flow', 'column', 'Children run top to bottom.'),
290
- ]),
291
-
292
- ...section('align', [
293
- one('align-centre', 'align', 'center', 'Children centred across the flow. What a control does with its label and its icon.'),
294
- one('align-start', 'align', 'flex-start', 'Children gathered at the start of the cross axis, each keeping its own size rather than stretching.'),
295
- one('align-end', 'align', 'flex-end', 'Children gathered at the end of the cross axis.'),
296
- one('align-stretch', 'align', 'stretch', 'Children stretched to fill the cross axis, which is what makes a column of cards the same width.'),
297
- one('align-baseline', 'align', 'baseline', 'Children lined up on the baseline of their text, so wording of different sizes still reads as one line.'),
298
- one('align-between', 'align', 'space-between', 'Children pushed to both ends with what is left over shared between them. Along the flow rather than across it, which is the axis this kind also answers for.'),
299
- ]),
300
-
301
- ...section('wrap', [
302
- one('wrap-on', 'wrap', 'wrap', 'Children spill onto another line when they run out of room.'),
303
- one('wrap-off', 'wrap', 'nowrap', 'Children stay on one line and let it overflow.'),
304
- ]),
305
-
306
- ...section('position', [
307
- one('position-flow', 'position', 'static', 'Laid where the page put it, taking its own room. What nearly everything is, and what nothing has to say.'),
308
- one('position-nudged', 'position', 'relative', 'Moved from where the page put it while the room it left stays its own, so nothing around it shifts. Also what a thing says to become the frame its lifted children are placed against.'),
309
- one('position-lifted', 'position', 'absolute', 'Out of the flow and placed against the nearest thing that is not itself in flow. The page keeps no room for it, which is what lets it cover something.'),
310
- one('position-pinned', 'position', 'fixed', 'Held against the window rather than the page, so it stays where it is while everything scrolls past.'),
311
- one('position-clinging', 'position', 'sticky', 'In flow until scrolling brings it to its edge, and held there until its section is done.'),
312
- ]),
313
-
314
- ...section('visibility', [
315
- one('visibility-shown', 'visibility', 'visible', 'Rendered, as almost everything is.'),
316
- one('visibility-hidden', 'visibility', 'hidden', 'Not rendered, not reachable and not read aloud, while keeping every bit of the room it was given. What holds a layout still across something appearing and disappearing.'),
317
- one('visibility-collapsed', 'visibility', 'collapse', 'A row or a column of a table removed without the columns beside it moving.'),
318
- ]),
319
-
320
- ...section('reach', [
321
- one('reach-open', 'reach', 'auto', 'A press, a hover or a drag lands on it.'),
322
- one('reach-through', 'reach', 'none', 'A pointer passes through to whatever is behind. What anything drawn over a control needs, so the control underneath stays pressable.'),
323
- ]),
324
-
325
- ...section('picking', [
326
- one('picking-open', 'picking', 'auto', 'The wording inside can be selected, as prose should be.'),
327
- one('picking-off', 'picking', 'none', 'The wording cannot be selected. What a control wants, so a press that turns into a small drag does not leave a smear of highlight across its label.'),
328
- one('picking-words', 'picking', 'text', 'Selectable as text even where what holds it is not.'),
329
- one('picking-whole', 'picking', 'all', 'One click takes the whole of it rather than a word. For a code block or an address meant to be copied entire.'),
330
- ]),
331
-
332
- ...section('touch', [
333
- one('touch-page', 'touch', 'auto', 'A finger dragged across it scrolls the page, as it should nearly everywhere.'),
334
- one('touch-own', 'touch', 'none', 'The drag belongs to the thing itself and the page does not move. What anything draggable needs on a phone.'),
335
- one('touch-across', 'touch', 'pan-x', 'The page scrolls sideways under the finger and the thing keeps the up-and-down drag.'),
336
- one('touch-down', 'touch', 'pan-y', 'The page scrolls up and down under the finger and the thing keeps the sideways drag. What a carousel inside a scrolling page wants.'),
337
- one('touch-quick', 'touch', 'manipulation', 'Scrolling and pinching still work, and the wait for a possible double-tap is dropped — which is how a control stops feeling a third of a second late.'),
338
- ]),
339
-
340
- ...section('readying', [
341
- one('readying-nothing', 'readying', 'auto', 'No warning given, which is right until something is measurably late.'),
342
- one('readying-movement', 'readying', 'transform', 'Warns that it is about to move, turn or scale. What a thing being dragged says.'),
343
- one('readying-fade', 'readying', 'opacity', 'Warns that it is about to fade in or out.'),
344
- one('readying-contents', 'readying', 'contents', 'Warns that what is inside it is about to be replaced entirely.'),
345
- one('readying-scroll', 'readying', 'scroll-position', 'Warns that it is about to be scrolled somewhere far from where it is.'),
346
- ]),
347
-
348
- ...section('origin', [
349
- one('origin-centre', 'origin', 'center', 'Turns and scales about its own middle, which is what a thing does unless it says otherwise.'),
350
- one('origin-top', 'origin', 'top', 'Grows downward from its top edge. What a menu opened from something above it does.'),
351
- one('origin-bottom', 'origin', 'bottom', 'Grows upward from its bottom edge.'),
352
- one('origin-left', 'origin', 'left', 'Grows rightward from its left edge.'),
353
- one('origin-right', 'origin', 'right', 'Grows leftward from its right edge.'),
354
- ]),
355
-
356
- ...section('overflow', [
357
- one('overflow-shown', 'overflow', 'visible', 'What does not fit is drawn past the edge anyway.'),
358
- one('overflow-hidden', 'overflow', 'hidden', 'What does not fit is cut off at the edge and cannot be reached.'),
359
- one('overflow-clipped', 'overflow', 'clip', 'Cut off at the edge like hidden, and without becoming something that can be scrolled by a script. The stricter of the two.'),
360
- one('overflow-scrolls', 'overflow', 'scroll', 'Scrollable, and saying so with a bar whether or not there is anything past the edge.'),
361
- one('overflow-as-needed', 'overflow', 'auto', 'Scrollable only once something does not fit.'),
362
- ]),
363
- ]
364
-
365
- export const STYLEGUIDES: Threaded & { readonly holds: readonly Row[] } = {
366
- id: 'styleguide',
367
- at: here,
368
-
369
- describes:
370
- 'Every named value an app has, each saying which token it is one of and what it holds. This is where values live: a row is either the value itself or the arguments a token turns into one, and a row naming a token nobody declared is refused. A material and a variant reach for these names and never for a raw value. An app adds its own rows to the same list and may not remove what the framework put there.',
371
- version: 1,
372
- cites: said,
373
- holds,
374
- }