@mizumi25/cli 0.1.0 → 0.1.3

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 (4) hide show
  1. package/docs-generator.js +1302 -536
  2. package/index.js +2557 -316
  3. package/package.json +1 -1
  4. package/watcher.js +153 -40
package/docs-generator.js CHANGED
@@ -1,713 +1,1479 @@
1
1
  // packages/cli/docs-generator.js
2
- import fs from 'fs';
3
- import path from 'path';
4
-
2
+ import fs from 'fs'
3
+ import path from 'path'
5
4
 
6
5
  export class DocsGenerator {
7
6
  constructor(config) {
8
- this.config = config;
9
- this.tokens = config.tokens || {};
10
- this.patterns = config.patterns || {};
11
- this.animations = config.animations || {};
7
+ this.config = config
8
+ this.tokens = config.tokens || {}
9
+ this.patterns = config.patterns || {}
10
+ this.animations = config.animations || {}
11
+ this.rules = config.rules || {}
12
+ }
13
+
14
+ generate(outputDir = '.mizumi/docs') {
15
+ if (!fs.existsSync(outputDir)) {
16
+ fs.mkdirSync(outputDir, { recursive: true })
17
+ }
18
+ const html = this._buildHTML()
19
+ const outPath = path.join(outputDir, 'index.html')
20
+ fs.writeFileSync(outPath, html, 'utf8')
21
+ console.log(`✅ Docs: ${outPath}`)
22
+ return outPath
12
23
  }
13
24
 
14
- generate() {
25
+ // ── Token counts ──
26
+ countTokens() {
27
+ let count = 0
28
+ for (const [, v] of Object.entries(this.tokens)) {
29
+ if (typeof v === 'object') {
30
+ for (const [, val] of Object.entries(v)) {
31
+ if (typeof val === 'object') count += Object.keys(val).length
32
+ else count++
33
+ }
34
+ }
35
+ }
36
+ return count
37
+ }
38
+
39
+ // ── Utility class examples per capability ──
40
+ getUtilityExamples() {
41
+ const T = this.tokens
42
+ const examples = []
43
+
44
+ // Colors
45
+ if (T.colors) {
46
+ const colorKeys = Object.entries(T.colors).slice(0, 3)
47
+ for (const [key, val] of colorKeys) {
48
+ const name = typeof val === 'object' ? key : key
49
+ examples.push({ cap: 'ink', value: name, css: 'color', preview: 'color' })
50
+ examples.push({ cap: 'paint', value: name, css: 'background-color', preview: 'bg' })
51
+ examples.push({ cap: 'stroke-color', value: name, css: 'border-color', preview: 'border' })
52
+ }
53
+ }
54
+
55
+ // Spacing
56
+ if (T.spacing) {
57
+ const keys = Object.keys(T.spacing).slice(0, 4)
58
+ for (const k of keys) {
59
+ examples.push({ cap: 'pad', value: k, css: 'padding', preview: 'spacing' })
60
+ examples.push({ cap: 'mar', value: k, css: 'margin', preview: 'spacing' })
61
+ examples.push({ cap: 'gap', value: k, css: 'gap', preview: 'spacing' })
62
+ examples.push({ cap: 'pad-x', value: k, css: 'padding-inline', preview: 'spacing' })
63
+ examples.push({ cap: 'pad-y', value: k, css: 'padding-block', preview: 'spacing' })
64
+ }
65
+ }
66
+
67
+ // Typography
68
+ if (T.typography) {
69
+ for (const k of Object.keys(T.typography)) {
70
+ examples.push({ cap: 'text', value: k, css: 'font-size + weight + line-height', preview: 'text' })
71
+ examples.push({ cap: 'type-size', value: k, css: 'font-size', preview: 'text' })
72
+ examples.push({ cap: 'type-weight', value: k, css: 'font-weight', preview: 'text' })
73
+ }
74
+ }
75
+
76
+ // Fonts
77
+ if (T.fonts) {
78
+ for (const k of Object.keys(T.fonts)) {
79
+ examples.push({ cap: 'type-face', value: k, css: 'font-family', preview: 'font' })
80
+ }
81
+ }
82
+
83
+ // Radius
84
+ if (T.radius) {
85
+ for (const k of Object.keys(T.radius)) {
86
+ examples.push({ cap: 'curve', value: k, css: 'border-radius', preview: 'radius' })
87
+ }
88
+ }
89
+
90
+ // Shadows
91
+ if (T.shadows) {
92
+ for (const k of Object.keys(T.shadows)) {
93
+ examples.push({ cap: 'cast', value: k, css: 'box-shadow', preview: 'shadow' })
94
+ examples.push({ cap: 'cast-inner', value: k, css: 'box-shadow (inset)', preview: 'shadow' })
95
+ examples.push({ cap: 'cast-text', value: k, css: 'text-shadow', preview: 'shadow' })
96
+ }
97
+ }
98
+
99
+ // Opacity
100
+ if (T.opacity) {
101
+ for (const k of Object.keys(T.opacity)) {
102
+ examples.push({ cap: 'canvas-fade', value: k, css: 'opacity', preview: 'opacity' })
103
+ }
104
+ }
105
+
106
+ // Blur
107
+ if (T.blur) {
108
+ for (const k of Object.keys(T.blur)) {
109
+ examples.push({ cap: 'glass-blur', value: k, css: 'backdrop-filter: blur()', preview: 'blur' })
110
+ examples.push({ cap: 'glow-blur', value: k, css: 'filter: blur()', preview: 'blur' })
111
+ }
112
+ }
113
+
114
+ // Leading
115
+ if (T.leading) {
116
+ for (const k of Object.keys(T.leading)) {
117
+ examples.push({ cap: 'leading', value: k, css: 'line-height', preview: 'text' })
118
+ }
119
+ }
120
+
121
+ // Tracking
122
+ if (T.tracking) {
123
+ for (const k of Object.keys(T.tracking)) {
124
+ examples.push({ cap: 'tracking', value: k, css: 'letter-spacing', preview: 'text' })
125
+ }
126
+ }
127
+
128
+ // Z-index
129
+ if (T.zIndex) {
130
+ for (const k of Object.keys(T.zIndex)) {
131
+ examples.push({ cap: 'layer', value: k, css: 'z-index', preview: 'layer' })
132
+ }
133
+ }
134
+
135
+ // Easing
136
+ if (T.easing) {
137
+ for (const k of Object.keys(T.easing)) {
138
+ examples.push({ cap: 'ease-curve', value: k, css: 'transition-timing-function', preview: 'ease' })
139
+ }
140
+ }
141
+
142
+ // Duration
143
+ if (T.duration) {
144
+ for (const k of Object.keys(T.duration)) {
145
+ examples.push({ cap: 'ease-speed', value: k, css: 'transition-duration', preview: 'ease' })
146
+ examples.push({ cap: 'ease-wait', value: k, css: 'transition-delay', preview: 'ease' })
147
+ }
148
+ }
149
+
150
+ return examples
151
+ }
152
+
153
+ // ── Static utility categories ──
154
+ getStaticCategories() {
155
+ return [
156
+ {
157
+ name: 'Display', utilities: [
158
+ { cls: 'display:flex', css: 'display: flex' },
159
+ { cls: 'display:grid', css: 'display: grid' },
160
+ { cls: 'display:block', css: 'display: block' },
161
+ { cls: 'display:inline', css: 'display: inline' },
162
+ { cls: 'display:none', css: 'display: none' },
163
+ { cls: 'display:flex-inline', css: 'display: inline-flex' },
164
+ { cls: 'display:grid-inline', css: 'display: inline-grid' },
165
+ { cls: 'display:contents', css: 'display: contents' },
166
+ ]
167
+ },
168
+ {
169
+ name: 'Flex', utilities: [
170
+ { cls: 'flex-dir:row', css: 'flex-direction: row' },
171
+ { cls: 'flex-dir:col', css: 'flex-direction: column' },
172
+ { cls: 'flex-dir:row-rev', css: 'flex-direction: row-reverse' },
173
+ { cls: 'flex-dir:col-rev', css: 'flex-direction: column-reverse' },
174
+ { cls: 'flex-wrap:yes', css: 'flex-wrap: wrap' },
175
+ { cls: 'flex-wrap:no', css: 'flex-wrap: nowrap' },
176
+ ]
177
+ },
178
+ {
179
+ name: 'Alignment', utilities: [
180
+ { cls: 'align-x:center', css: 'justify-content: center' },
181
+ { cls: 'align-x:between', css: 'justify-content: space-between' },
182
+ { cls: 'align-x:start', css: 'justify-content: flex-start' },
183
+ { cls: 'align-x:end', css: 'justify-content: flex-end' },
184
+ { cls: 'align-yi:center', css: 'align-items: center' },
185
+ { cls: 'align-yi:start', css: 'align-items: flex-start' },
186
+ { cls: 'align-yi:end', css: 'align-items: flex-end' },
187
+ { cls: 'align-ys:center', css: 'align-self: center' },
188
+ { cls: 'place:center', css: 'place-content: center' },
189
+ ]
190
+ },
191
+ {
192
+ name: 'Position', utilities: [
193
+ { cls: 'pos:relative', css: 'position: relative' },
194
+ { cls: 'pos:absolute', css: 'position: absolute' },
195
+ { cls: 'pos:fixed', css: 'position: fixed' },
196
+ { cls: 'pos:sticky', css: 'position: sticky' },
197
+ { cls: 'pos:static', css: 'position: static' },
198
+ ]
199
+ },
200
+ {
201
+ name: 'Overflow', utilities: [
202
+ { cls: 'overflow:hidden', css: 'overflow: hidden' },
203
+ { cls: 'overflow:auto', css: 'overflow: auto' },
204
+ { cls: 'overflow:scroll', css: 'overflow: scroll' },
205
+ { cls: 'overflow:visible', css: 'overflow: visible' },
206
+ { cls: 'overflow:clip', css: 'overflow: clip' },
207
+ ]
208
+ },
209
+ {
210
+ name: 'Sizing', utilities: [
211
+ { cls: 'canvas-w:full', css: 'width: 100%' },
212
+ { cls: 'canvas-w:screen', css: 'width: 100vw' },
213
+ { cls: 'canvas-w:auto', css: 'width: auto' },
214
+ { cls: 'canvas-h:full', css: 'height: 100%' },
215
+ { cls: 'canvas-h:screen', css: 'height: 100vh' },
216
+ { cls: 'canvas-h:auto', css: 'height: auto' },
217
+ { cls: 'mar-x:auto', css: 'margin-inline: auto' },
218
+ ]
219
+ },
220
+ {
221
+ name: 'Typography', utilities: [
222
+ { cls: 'text-align:center', css: 'text-align: center' },
223
+ { cls: 'text-align:left', css: 'text-align: left' },
224
+ { cls: 'text-align:right', css: 'text-align: right' },
225
+ { cls: 'text-case:upper', css: 'text-transform: uppercase' },
226
+ { cls: 'text-case:lower', css: 'text-transform: lowercase' },
227
+ { cls: 'text-case:capital', css: 'text-transform: capitalize' },
228
+ { cls: 'type-weight:bold', css: 'font-weight: bold' },
229
+ { cls: 'type-weight:semi', css: 'font-weight: 600' },
230
+ { cls: 'type-weight:medium',css: 'font-weight: 500' },
231
+ { cls: 'type-style:italic', css: 'font-style: italic' },
232
+ ]
233
+ },
234
+ {
235
+ name: 'Cursor & Interaction', utilities: [
236
+ { cls: 'cursor:pointer', css: 'cursor: pointer' },
237
+ { cls: 'cursor:default', css: 'cursor: default' },
238
+ { cls: 'cursor:none', css: 'cursor: none' },
239
+ { cls: 'cursor:grab', css: 'cursor: grab' },
240
+ { cls: 'select:none', css: 'user-select: none' },
241
+ { cls: 'events:none', css: 'pointer-events: none' },
242
+ { cls: 'events:all', css: 'pointer-events: all' },
243
+ ]
244
+ },
245
+ {
246
+ name: 'Border Style', utilities: [
247
+ { cls: 'stroke-style:solid', css: 'border-style: solid' },
248
+ { cls: 'stroke-style:dashed', css: 'border-style: dashed' },
249
+ { cls: 'stroke-style:dotted', css: 'border-style: dotted' },
250
+ { cls: 'stroke-style:none', css: 'border-style: none' },
251
+ ]
252
+ },
253
+ ]
254
+ }
255
+
256
+ // ── Animation type badge ──
257
+ getAnimType(config) {
258
+ if (config.hover) return { label: 'hover', color: '#a78bfa' }
259
+ if (config.active) return { label: 'click', color: '#f87171' }
260
+ if (config.targets === 'children') return { label: 'stagger', color: '#fbbf24' }
261
+ if (config.scrollTrigger) return { label: 'scroll', color: '#38bdf8' }
262
+ if (config.repeat === -1) return { label: 'loop', color: '#e879f9' }
263
+ return { label: 'entrance', color: '#34d399' }
264
+ }
265
+
266
+ // ── Arbitrary value examples ──
267
+ getArbitraryExamples() {
268
+ return [
269
+ { cls: 'pad:clamp(1rem,4vw,3rem)', desc: 'Fluid padding using CSS clamp()' },
270
+ { cls: 'type-size:clamp(2rem,5vw,5rem)', desc: 'Fluid font size' },
271
+ { cls: 'canvas-w:min(100%,1200px)', desc: 'Responsive max-width container' },
272
+ { cls: 'curve:24px', desc: 'Arbitrary border radius' },
273
+ { cls: 'cast:0_8px_32px_rgba(0,0,0,0.4)', desc: 'Custom box shadow (use _ for spaces)' },
274
+ { cls: 'ink:#ff6b6b', desc: 'Arbitrary hex color' },
275
+ { cls: 'paint:rgba(0,0,0,0.5)', desc: 'Arbitrary rgba background' },
276
+ { cls: 'tracking:-0.04em', desc: 'Arbitrary letter spacing' },
277
+ { cls: 'leading:0.9', desc: 'Arbitrary line height' },
278
+ { cls: 'canvas-w:calc(100%-2rem)', desc: 'CSS calc() value' },
279
+ ]
280
+ }
281
+
282
+ _buildHTML() {
283
+ const tokenCount = this.countTokens()
284
+ const patternCount = Object.keys(this.patterns).length
285
+ const animCount = Object.keys(this.animations).length
286
+ const utilExamples = this.getUtilityExamples()
287
+ const staticCats = this.getStaticCategories()
288
+ const arbitrary = this.getArbitraryExamples()
289
+ const bpCount = Object.keys(this.rules.breakpoints || {}).length
290
+
15
291
  return `<!DOCTYPE html>
16
292
  <html lang="en">
17
293
  <head>
18
294
  <meta charset="UTF-8">
19
295
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
20
- <title>Mizumi Docs 🌊</title>
296
+ <title>Mizumi Docs 💮</title>
21
297
  <style>
22
- * { margin:0; padding:0; box-sizing:border-box; }
298
+ @import url('https://fonts.googleapis.com/css2?family=IM+Fell+English:ital@0;1&family=DM+Mono:ital,wght@0,300;0,400;0,500;1,300&display=swap');
299
+
300
+ *, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }
301
+
302
+ :root {
303
+ --ink: #e8e4dc;
304
+ --ink-muted: #7a7568;
305
+ --ink-dim: #3d3a34;
306
+ --surface: #0e0d0b;
307
+ --surface-2: #141310;
308
+ --surface-3: #1a1916;
309
+ --border: #2a2823;
310
+ --accent: #c9a96e;
311
+ --accent-2: #8b6f47;
312
+ --serif: 'IM Fell English', Georgia, serif;
313
+ --mono: 'DM Mono', monospace;
314
+ --sans: system-ui, sans-serif;
315
+ }
316
+
317
+ html { scroll-behavior: smooth; }
23
318
 
24
319
  body {
25
- font-family: system-ui, sans-serif;
26
- background: #0f172a;
27
- color: #e2e8f0;
320
+ background: var(--surface);
321
+ color: var(--ink);
322
+ font-family: var(--sans);
323
+ font-size: 15px;
324
+ line-height: 1.6;
28
325
  min-height: 100vh;
29
326
  }
30
327
 
31
- /* Layout */
328
+ /* ── LAYOUT ── */
32
329
  .sidebar {
33
330
  position: fixed;
34
331
  top: 0; left: 0;
35
- width: 240px;
332
+ width: 220px;
36
333
  height: 100vh;
37
- background: #1e293b;
38
- border-right: 1px solid #334155;
334
+ background: var(--surface-2);
335
+ border-right: 1px solid var(--border);
39
336
  overflow-y: auto;
40
- padding: 24px 0;
337
+ padding: 0;
338
+ z-index: 100;
41
339
  }
42
340
 
43
341
  .main {
44
- margin-left: 240px;
45
- padding: 40px;
46
- max-width: 1000px;
342
+ margin-left: 220px;
343
+ max-width: 1100px;
344
+ padding: 0 48px 120px;
47
345
  }
48
346
 
49
- /* Sidebar */
50
- .sidebar-logo {
51
- padding: 0 20px 24px;
52
- border-bottom: 1px solid #334155;
53
- margin-bottom: 16px;
347
+ /* ── SIDEBAR ── */
348
+ .sidebar-header {
349
+ padding: 32px 24px 24px;
350
+ border-bottom: 1px solid var(--border);
54
351
  }
55
352
 
56
- .sidebar-logo h1 {
57
- font-size: 20px;
58
- font-weight: 800;
59
- color: #38bdf8;
60
- letter-spacing: -0.5px;
353
+ .sidebar-logo {
354
+ font-family: var(--serif);
355
+ font-size: 22px;
356
+ color: var(--ink);
357
+ letter-spacing: -0.02em;
358
+ margin-bottom: 4px;
61
359
  }
62
360
 
63
- .sidebar-logo span {
64
- font-size: 12px;
65
- color: #64748b;
361
+ .sidebar-version {
362
+ font-family: var(--mono);
363
+ font-size: 11px;
364
+ color: var(--ink-muted);
365
+ letter-spacing: 0.1em;
66
366
  }
67
367
 
68
- .nav-section {
69
- padding: 8px 20px 4px;
70
- font-size: 11px;
71
- font-weight: 700;
368
+ .nav-group { padding: 20px 0 4px; }
369
+
370
+ .nav-label {
371
+ padding: 0 24px 8px;
372
+ font-family: var(--mono);
373
+ font-size: 10px;
374
+ letter-spacing: 0.15em;
72
375
  text-transform: uppercase;
73
- letter-spacing: 1px;
74
- color: #475569;
376
+ color: var(--ink-dim);
75
377
  }
76
378
 
77
379
  .nav-link {
78
380
  display: block;
79
- padding: 8px 20px;
80
- color: #94a3b8;
381
+ padding: 7px 24px;
382
+ font-family: var(--mono);
383
+ font-size: 12px;
384
+ color: var(--ink-muted);
81
385
  text-decoration: none;
82
- font-size: 14px;
386
+ border-left: 2px solid transparent;
83
387
  transition: all 0.15s;
84
- border-left: 3px solid transparent;
85
388
  }
86
389
 
87
390
  .nav-link:hover {
88
- color: #e2e8f0;
89
- background: #334155;
90
- border-left-color: #38bdf8;
391
+ color: var(--ink);
392
+ border-left-color: var(--accent);
393
+ background: rgba(201,169,110,0.04);
91
394
  }
92
395
 
93
- /* Sections */
94
- .section {
95
- margin-bottom: 64px;
396
+ /* ── SECTIONS ── */
397
+ .hero {
398
+ padding: 80px 0 64px;
399
+ border-bottom: 1px solid var(--border);
400
+ margin-bottom: 80px;
96
401
  }
97
402
 
98
- .section-title {
99
- font-size: 28px;
100
- font-weight: 700;
101
- color: #f1f5f9;
102
- margin-bottom: 8px;
103
- padding-bottom: 16px;
104
- border-bottom: 1px solid #334155;
105
- letter-spacing: -0.5px;
403
+ .hero-eyebrow {
404
+ font-family: var(--mono);
405
+ font-size: 11px;
406
+ letter-spacing: 0.2em;
407
+ text-transform: uppercase;
408
+ color: var(--accent);
409
+ margin-bottom: 20px;
106
410
  }
107
411
 
108
- .section-sub {
109
- font-size: 18px;
110
- font-weight: 600;
111
- color: #cbd5e1;
112
- margin: 32px 0 16px;
412
+ .hero-title {
413
+ font-family: var(--serif);
414
+ font-size: clamp(3rem, 6vw, 5.5rem);
415
+ line-height: 0.95;
416
+ letter-spacing: -0.03em;
417
+ color: var(--ink);
418
+ margin-bottom: 24px;
113
419
  }
114
420
 
115
- /* Cards */
116
- .card-grid {
117
- display: grid;
118
- grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
119
- gap: 12px;
120
- margin-bottom: 24px;
421
+ .hero-title em {
422
+ color: var(--ink-muted);
423
+ font-style: italic;
121
424
  }
122
425
 
123
- .doc-card {
124
- background: #1e293b;
125
- border: 1px solid #334155;
126
- border-radius: 10px;
127
- padding: 16px;
128
- transition: border-color 0.15s;
426
+ .hero-sub {
427
+ font-family: var(--mono);
428
+ font-size: 13px;
429
+ color: var(--ink-muted);
430
+ max-width: 480px;
431
+ line-height: 1.7;
432
+ margin-bottom: 40px;
129
433
  }
130
434
 
131
- .doc-card:hover { border-color: #38bdf8; }
435
+ .stats {
436
+ display: flex;
437
+ gap: 40px;
438
+ flex-wrap: wrap;
439
+ }
440
+
441
+ .stat-item { display: flex; flex-direction: column; gap: 4px; }
442
+
443
+ .stat-num {
444
+ font-family: var(--serif);
445
+ font-size: 2.5rem;
446
+ line-height: 1;
447
+ color: var(--accent);
448
+ letter-spacing: -0.03em;
449
+ }
450
+
451
+ .stat-label {
452
+ font-family: var(--mono);
453
+ font-size: 10px;
454
+ letter-spacing: 0.15em;
455
+ text-transform: uppercase;
456
+ color: var(--ink-muted);
457
+ }
458
+
459
+ /* ── SECTION HEADERS ── */
460
+ .section { margin-bottom: 80px; }
132
461
 
133
- .doc-card-name {
134
- font-family: monospace;
462
+ .section-header {
463
+ display: flex;
464
+ align-items: flex-end;
465
+ justify-content: space-between;
466
+ padding-bottom: 20px;
467
+ border-bottom: 1px solid var(--border);
468
+ margin-bottom: 40px;
469
+ }
470
+
471
+ .section-title {
472
+ font-family: var(--serif);
473
+ font-size: clamp(1.8rem, 3vw, 2.8rem);
474
+ line-height: 1;
475
+ letter-spacing: -0.03em;
476
+ color: var(--ink);
477
+ }
478
+
479
+ .section-count {
480
+ font-family: var(--mono);
481
+ font-size: 11px;
482
+ letter-spacing: 0.1em;
483
+ color: var(--ink-muted);
484
+ padding-bottom: 4px;
485
+ }
486
+
487
+ .subsection-title {
488
+ font-family: var(--mono);
489
+ font-size: 11px;
490
+ letter-spacing: 0.15em;
491
+ text-transform: uppercase;
492
+ color: var(--ink-muted);
493
+ margin: 40px 0 16px;
494
+ padding-bottom: 8px;
495
+ border-bottom: 1px solid var(--border);
496
+ }
497
+
498
+ /* ── SEARCH ── */
499
+ .search-wrap {
500
+ position: relative;
501
+ margin-bottom: 48px;
502
+ }
503
+
504
+ .search {
505
+ width: 100%;
506
+ background: var(--surface-2);
507
+ border: 1px solid var(--border);
508
+ border-radius: 4px;
509
+ padding: 14px 20px;
510
+ color: var(--ink);
511
+ font-family: var(--mono);
135
512
  font-size: 13px;
136
- color: #38bdf8;
137
- margin-bottom: 8px;
138
- font-weight: 600;
513
+ outline: none;
514
+ transition: border-color 0.2s;
515
+ }
516
+
517
+ .search:focus { border-color: var(--accent); }
518
+ .search::placeholder { color: var(--ink-dim); }
519
+
520
+ /* ── TOKEN TABLES ── */
521
+ .token-table { width: 100%; border-collapse: collapse; margin-bottom: 32px; }
522
+
523
+ .token-table th {
524
+ font-family: var(--mono);
525
+ font-size: 10px;
526
+ letter-spacing: 0.15em;
527
+ text-transform: uppercase;
528
+ color: var(--ink-muted);
529
+ text-align: left;
530
+ padding: 10px 16px;
531
+ border-bottom: 1px solid var(--border);
139
532
  }
140
533
 
141
- .doc-card-value {
534
+ .token-table td {
535
+ padding: 12px 16px;
536
+ border-bottom: 1px solid var(--border);
537
+ font-family: var(--mono);
142
538
  font-size: 12px;
143
- color: #64748b;
144
- word-break: break-all;
539
+ vertical-align: middle;
145
540
  }
146
541
 
147
- /* Color swatches */
148
- .color-swatch {
149
- width: 100%;
150
- height: 40px;
151
- border-radius: 6px;
152
- margin-bottom: 8px;
153
- border: 1px solid rgba(255,255,255,0.1);
542
+ .token-table tr:hover td { background: var(--surface-2); }
543
+
544
+ .token-name { color: var(--accent); }
545
+ .token-var { color: var(--ink-muted); font-size: 11px; }
546
+ .token-val { color: var(--ink-dim); }
547
+
548
+ .color-chip {
549
+ width: 28px; height: 28px;
550
+ border-radius: 4px;
551
+ border: 1px solid rgba(255,255,255,0.08);
552
+ display: inline-block;
553
+ vertical-align: middle;
154
554
  }
155
555
 
156
- /* Pattern rows */
556
+ .radius-chip {
557
+ width: 28px; height: 28px;
558
+ background: var(--accent-2);
559
+ display: inline-block;
560
+ vertical-align: middle;
561
+ opacity: 0.7;
562
+ }
563
+
564
+ /* ── UTILITY ROWS ── */
565
+ .utility-row {
566
+ display: flex;
567
+ align-items: center;
568
+ gap: 16px;
569
+ padding: 10px 16px;
570
+ border-bottom: 1px solid var(--border);
571
+ transition: background 0.1s;
572
+ }
573
+
574
+ .utility-row:hover { background: var(--surface-2); }
575
+
576
+ .utility-cls {
577
+ font-family: var(--mono);
578
+ font-size: 12px;
579
+ color: var(--accent);
580
+ min-width: 260px;
581
+ flex-shrink: 0;
582
+ }
583
+
584
+ .utility-arrow {
585
+ color: var(--ink-dim);
586
+ font-size: 11px;
587
+ flex-shrink: 0;
588
+ }
589
+
590
+ .utility-css {
591
+ font-family: var(--mono);
592
+ font-size: 11px;
593
+ color: var(--ink-muted);
594
+ flex: 1;
595
+ }
596
+
597
+ /* ── PATTERN ROWS ── */
157
598
  .pattern-row {
158
- background: #1e293b;
159
- border: 1px solid #334155;
160
- border-radius: 10px;
161
- padding: 16px 20px;
599
+ border: 1px solid var(--border);
600
+ border-radius: 4px;
162
601
  margin-bottom: 8px;
602
+ overflow: hidden;
603
+ transition: border-color 0.15s;
604
+ }
605
+
606
+ .pattern-row:hover { border-color: var(--accent-2); }
607
+
608
+ .pattern-header {
163
609
  display: flex;
164
- align-items: flex-start;
165
- gap: 16px;
166
- flex-wrap: wrap;
610
+ align-items: center;
611
+ justify-content: space-between;
612
+ padding: 14px 20px;
613
+ background: var(--surface-2);
614
+ cursor: pointer;
167
615
  }
168
616
 
169
617
  .pattern-name {
170
- font-family: monospace;
171
- font-size: 14px;
172
- color: #38bdf8;
173
- font-weight: 600;
174
- min-width: 160px;
618
+ font-family: var(--mono);
619
+ font-size: 13px;
620
+ color: var(--accent);
175
621
  }
176
622
 
177
- .pattern-arrow {
178
- color: #475569;
179
- font-size: 14px;
623
+ .pattern-toggle {
624
+ font-family: var(--mono);
625
+ font-size: 10px;
626
+ color: var(--ink-dim);
627
+ letter-spacing: 0.1em;
628
+ }
629
+
630
+ .pattern-body {
631
+ padding: 16px 20px;
632
+ border-top: 1px solid var(--border);
633
+ display: none;
180
634
  }
181
635
 
636
+ .pattern-body.open { display: block; }
637
+
182
638
  .pattern-value {
183
- font-family: monospace;
184
- font-size: 13px;
185
- color: #94a3b8;
186
- flex: 1;
639
+ font-family: var(--mono);
640
+ font-size: 12px;
641
+ color: var(--ink-muted);
642
+ line-height: 1.8;
643
+ margin-bottom: 16px;
644
+ word-break: break-all;
187
645
  }
188
646
 
189
- /* Animation rows */
647
+ .pattern-usage {
648
+ background: var(--surface);
649
+ border: 1px solid var(--border);
650
+ border-radius: 3px;
651
+ padding: 10px 14px;
652
+ font-family: var(--mono);
653
+ font-size: 12px;
654
+ color: #86efac;
655
+ }
656
+
657
+ /* ── ANIMATION ROWS ── */
190
658
  .anim-row {
191
- background: #1e293b;
192
- border: 1px solid #334155;
193
- border-radius: 10px;
194
- padding: 16px 20px;
659
+ border: 1px solid var(--border);
660
+ border-radius: 4px;
195
661
  margin-bottom: 8px;
662
+ overflow: hidden;
196
663
  }
197
664
 
198
665
  .anim-header {
199
666
  display: flex;
200
667
  align-items: center;
201
668
  gap: 12px;
202
- margin-bottom: 8px;
669
+ padding: 14px 20px;
670
+ background: var(--surface-2);
203
671
  }
204
672
 
205
673
  .anim-name {
206
- font-family: monospace;
207
- font-size: 14px;
208
- color: #38bdf8;
209
- font-weight: 600;
674
+ font-family: var(--mono);
675
+ font-size: 13px;
676
+ color: var(--accent);
677
+ flex: 1;
210
678
  }
211
679
 
212
- .anim-type {
213
- font-size: 11px;
214
- font-weight: 700;
215
- padding: 2px 8px;
680
+ .anim-badge {
681
+ font-family: var(--mono);
682
+ font-size: 10px;
683
+ font-weight: 500;
684
+ padding: 3px 10px;
216
685
  border-radius: 999px;
686
+ letter-spacing: 0.1em;
217
687
  text-transform: uppercase;
218
- letter-spacing: 0.5px;
219
688
  }
220
689
 
221
- .type-entrance { background: #0d3b26; color: #34d399; }
222
- .type-hover { background: #1e1a3b; color: #a78bfa; }
223
- .type-click { background: #3b1a1a; color: #f87171; }
224
- .type-scroll { background: #1a2e3b; color: #38bdf8; }
225
- .type-stagger { background: #2e2a0d; color: #fbbf24; }
226
- .type-loop { background: #2e1a3b; color: #e879f9; }
690
+ .anim-body {
691
+ padding: 16px 20px;
692
+ border-top: 1px solid var(--border);
693
+ }
227
694
 
228
695
  .anim-config {
229
- font-family: monospace;
230
- font-size: 12px;
231
- color: #64748b;
232
- line-height: 1.6;
696
+ font-family: var(--mono);
697
+ font-size: 11px;
698
+ color: var(--ink-muted);
699
+ line-height: 1.7;
700
+ white-space: pre-wrap;
701
+ margin-bottom: 12px;
233
702
  }
234
703
 
235
- /* Usage box */
236
- .usage-box {
237
- background: #0f172a;
238
- border: 1px solid #334155;
239
- border-radius: 6px;
704
+ .anim-usage {
705
+ background: var(--surface);
706
+ border: 1px solid var(--border);
707
+ border-radius: 3px;
240
708
  padding: 10px 14px;
241
- margin-top: 8px;
242
- font-family: monospace;
709
+ font-family: var(--mono);
243
710
  font-size: 12px;
244
711
  color: #86efac;
245
712
  }
246
713
 
247
- /* Spacing preview */
248
- .spacing-preview {
714
+ /* ── ARBITRARY SECTION ── */
715
+ .arbitrary-row {
249
716
  display: flex;
250
- align-items: center;
251
- gap: 12px;
717
+ flex-direction: column;
718
+ gap: 6px;
719
+ padding: 14px 16px;
720
+ border-bottom: 1px solid var(--border);
721
+ transition: background 0.1s;
252
722
  }
253
723
 
254
- .spacing-bar {
255
- background: #38bdf8;
256
- height: 16px;
257
- border-radius: 3px;
258
- opacity: 0.7;
259
- }
724
+ .arbitrary-row:hover { background: var(--surface-2); }
260
725
 
261
- /* Search */
262
- .search-bar {
263
- width: 100%;
264
- background: #1e293b;
265
- border: 1px solid #334155;
266
- border-radius: 10px;
267
- padding: 12px 16px;
268
- color: #e2e8f0;
269
- font-size: 14px;
270
- margin-bottom: 32px;
271
- outline: none;
272
- transition: border-color 0.15s;
726
+ .arbitrary-cls {
727
+ font-family: var(--mono);
728
+ font-size: 12px;
729
+ color: var(--accent);
273
730
  }
274
731
 
275
- .search-bar:focus { border-color: #38bdf8; }
276
- .search-bar::placeholder { color: #475569; }
277
-
278
- /* Stats bar */
279
- .stats-bar {
280
- display: flex;
281
- gap: 24px;
282
- margin-bottom: 48px;
283
- flex-wrap: wrap;
732
+ .arbitrary-desc {
733
+ font-family: var(--mono);
734
+ font-size: 11px;
735
+ color: var(--ink-muted);
284
736
  }
285
737
 
286
- .stat {
287
- background: #1e293b;
288
- border: 1px solid #334155;
289
- border-radius: 10px;
290
- padding: 16px 24px;
291
- text-align: center;
738
+ /* ── MIZU SYNTAX ── */
739
+ .code-block {
740
+ background: var(--surface-2);
741
+ border: 1px solid var(--border);
742
+ border-radius: 4px;
743
+ padding: 24px;
744
+ font-family: var(--mono);
745
+ font-size: 12px;
746
+ line-height: 1.8;
747
+ color: var(--ink-muted);
748
+ white-space: pre;
749
+ overflow-x: auto;
750
+ margin-bottom: 24px;
292
751
  }
293
752
 
294
- .stat-number {
295
- font-size: 28px;
296
- font-weight: 800;
297
- color: #38bdf8;
298
- }
753
+ .code-keyword { color: #38bdf8; }
754
+ .code-name { color: var(--accent); }
755
+ .code-value { color: #86efac; }
756
+ .code-comment { color: var(--ink-dim); }
299
757
 
300
- .stat-label {
301
- font-size: 12px;
302
- color: #64748b;
303
- margin-top: 2px;
758
+ /* ── MODIFIER GRID ── */
759
+ .modifier-grid {
760
+ display: grid;
761
+ grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
762
+ gap: 8px;
763
+ margin-bottom: 24px;
304
764
  }
305
765
 
306
- /* Header */
307
- .docs-header {
308
- margin-bottom: 48px;
766
+ .modifier-card {
767
+ background: var(--surface-2);
768
+ border: 1px solid var(--border);
769
+ border-radius: 4px;
770
+ padding: 12px 14px;
309
771
  }
310
772
 
311
- .docs-header h1 {
312
- font-size: 40px;
313
- font-weight: 800;
314
- color: #f1f5f9;
315
- letter-spacing: -1px;
316
- margin-bottom: 8px;
773
+ .modifier-cls {
774
+ font-family: var(--mono);
775
+ font-size: 11px;
776
+ color: var(--accent);
777
+ margin-bottom: 4px;
317
778
  }
318
779
 
319
- .docs-header p {
320
- color: #64748b;
321
- font-size: 16px;
780
+ .modifier-val {
781
+ font-family: var(--mono);
782
+ font-size: 10px;
783
+ color: var(--ink-muted);
322
784
  }
323
785
 
324
- .badge {
325
- display: inline-block;
326
- background: #0c2a1a;
327
- color: #34d399;
328
- border: 1px solid #34d399;
786
+ /* ── SCROLLBAR ── */
787
+ ::-webkit-scrollbar { width: 4px; height: 4px; }
788
+ ::-webkit-scrollbar-track { background: var(--surface); }
789
+ ::-webkit-scrollbar-thumb { background: var(--border); border-radius: 2px; }
790
+
791
+ /* ── COPY BUTTON ── */
792
+ .copy-btn {
793
+ background: none;
794
+ border: 1px solid var(--border);
795
+ border-radius: 3px;
329
796
  padding: 3px 10px;
330
- border-radius: 999px;
331
- font-size: 12px;
332
- font-weight: 600;
333
- margin-left: 8px;
334
- vertical-align: middle;
797
+ font-family: var(--mono);
798
+ font-size: 10px;
799
+ color: var(--ink-muted);
800
+ cursor: pointer;
801
+ transition: all 0.15s;
802
+ letter-spacing: 0.05em;
335
803
  }
336
804
 
337
- /* Scrollbar */
338
- ::-webkit-scrollbar { width: 6px; }
339
- ::-webkit-scrollbar-track { background: #0f172a; }
340
- ::-webkit-scrollbar-thumb { background: #334155; border-radius: 3px; }
805
+ .copy-btn:hover { border-color: var(--accent); color: var(--accent); }
806
+
807
+ /* ── RESPONSIVE ── */
808
+ @media (max-width: 768px) {
809
+ .sidebar { display: none; }
810
+ .main { margin-left: 0; padding: 0 20px 80px; }
811
+ }
812
+
813
+ /* ── HIDDEN ── */
814
+ .hidden { display: none !important; }
341
815
  </style>
342
816
  </head>
343
817
  <body>
344
818
 
345
- <!-- SIDEBAR -->
346
- <nav class="sidebar">
347
- <div class="sidebar-logo">
348
- <h1>🌊 Mizumi</h1>
349
- <span>v0.1.0 Docs</span>
350
- </div>
819
+ <!-- SIDEBAR -->
820
+ <nav class="sidebar">
821
+ <div class="sidebar-header">
822
+ <div class="sidebar-logo">Mizumi 💮</div>
823
+ <div class="sidebar-version">v0.1.0 — Auto-generated</div>
824
+ </div>
351
825
 
352
- <div class="nav-section">Getting Started</div>
826
+ <div class="nav-group">
827
+ <div class="nav-label">Overview</div>
353
828
  <a href="#overview" class="nav-link">Overview</a>
354
-
355
- <div class="nav-section">Design Tokens</div>
356
- <a href="#colors" class="nav-link">Colors</a>
357
- <a href="#spacing" class="nav-link">Spacing</a>
358
- <a href="#typography" class="nav-link">Typography</a>
359
- <a href="#radius" class="nav-link">Border Radius</a>
360
- <a href="#shadows" class="nav-link">Shadows</a>
361
-
362
- <div class="nav-section">Patterns</div>
829
+ <a href="#arbitrary" class="nav-link">Arbitrary Values</a>
830
+ <a href="#mizu-syntax" class="nav-link">.mizu Syntax</a>
831
+ </div>
832
+
833
+ <div class="nav-group">
834
+ <div class="nav-label">Design Tokens</div>
835
+ ${this.tokens.colors ? '<a href="#colors" class="nav-link">Colors</a>' : ''}
836
+ ${this.tokens.spacing ? '<a href="#spacing" class="nav-link">Spacing</a>' : ''}
837
+ ${this.tokens.typography ? '<a href="#typography" class="nav-link">Typography</a>' : ''}
838
+ ${this.tokens.fonts ? '<a href="#fonts" class="nav-link">Fonts</a>' : ''}
839
+ ${this.tokens.radius ? '<a href="#radius" class="nav-link">Radius</a>' : ''}
840
+ ${this.tokens.shadows ? '<a href="#shadows" class="nav-link">Shadows</a>' : ''}
841
+ ${this.tokens.easing ? '<a href="#easing" class="nav-link">Easing</a>' : ''}
842
+ ${this.tokens.duration ? '<a href="#duration" class="nav-link">Duration</a>' : ''}
843
+ ${this.tokens.blur ? '<a href="#blur" class="nav-link">Blur</a>' : ''}
844
+ ${this.tokens.opacity ? '<a href="#opacity" class="nav-link">Opacity</a>' : ''}
845
+ ${this.tokens.zIndex ? '<a href="#zindex" class="nav-link">Z-Index</a>' : ''}
846
+ ${this.tokens.leading ? '<a href="#leading" class="nav-link">Leading</a>' : ''}
847
+ ${this.tokens.tracking ? '<a href="#tracking" class="nav-link">Tracking</a>' : ''}
848
+ </div>
849
+
850
+ <div class="nav-group">
851
+ <div class="nav-label">Utilities</div>
852
+ <a href="#token-utilities" class="nav-link">Token Utilities</a>
853
+ <a href="#static-utilities" class="nav-link">Static Utilities</a>
854
+ </div>
855
+
856
+ ${patternCount > 0 ? `
857
+ <div class="nav-group">
858
+ <div class="nav-label">Patterns</div>
363
859
  <a href="#patterns" class="nav-link">All Patterns</a>
860
+ </div>` : ''}
364
861
 
365
- <div class="nav-section">Animations</div>
862
+ ${animCount > 0 ? `
863
+ <div class="nav-group">
864
+ <div class="nav-label">Animations</div>
366
865
  <a href="#animations" class="nav-link">All Animations</a>
367
866
  <a href="#modifiers" class="nav-link">Modifiers</a>
368
- </nav>
369
-
370
- <!-- MAIN -->
371
- <main class="main">
372
-
373
- <!-- Header -->
374
- <div class="docs-header">
375
- <h1>Mizumi Docs <span class="badge">v0.1.0</span></h1>
376
- <p>Auto-generated from your mizumi.config.js</p>
377
- </div>
378
-
379
- <!-- Search -->
380
- <input
381
- class="search-bar"
382
- type="text"
383
- placeholder="Search tokens, patterns, animations..."
384
- oninput="handleSearch(this.value)"
385
- >
386
-
387
- <!-- Stats -->
388
- <div class="stats-bar">
389
- <div class="stat">
390
- <div class="stat-number">${this.countTokens()}</div>
391
- <div class="stat-label">Tokens</div>
867
+ </div>` : ''}
868
+ </nav>
869
+
870
+ <!-- MAIN -->
871
+ <main class="main">
872
+
873
+ <!-- SEARCH -->
874
+ <div class="search-wrap" style="padding-top: 40px;">
875
+ <input class="search" type="text" placeholder="Search tokens, utilities, patterns..." oninput="handleSearch(this.value)">
876
+ </div>
877
+
878
+ <!-- HERO -->
879
+ <section class="hero" id="overview">
880
+ <div class="hero-eyebrow">Documentation</div>
881
+ <h1 class="hero-title">Mizumi<br><em>Design System</em></h1>
882
+ <p class="hero-sub">
883
+ A designer-first CSS framework. Token-based. Colon-syntax.
884
+ Arbitrary values. GSAP animations — all from a single config.
885
+ Auto-generated from your <code style="color:var(--accent);font-family:var(--mono)">mizumi.config.js</code>.
886
+ </p>
887
+ <div class="stats">
888
+ <div class="stat-item">
889
+ <span class="stat-num">${tokenCount}</span>
890
+ <span class="stat-label">Tokens</span>
392
891
  </div>
393
- <div class="stat">
394
- <div class="stat-number">${Object.keys(this.patterns).length}</div>
395
- <div class="stat-label">Patterns</div>
892
+ <div class="stat-item">
893
+ <span class="stat-num">${patternCount}</span>
894
+ <span class="stat-label">Patterns</span>
396
895
  </div>
397
- <div class="stat">
398
- <div class="stat-number">${Object.keys(this.animations).length}</div>
399
- <div class="stat-label">Animations</div>
896
+ <div class="stat-item">
897
+ <span class="stat-num">${animCount}</span>
898
+ <span class="stat-label">Animations</span>
400
899
  </div>
401
- <div class="stat">
402
- <div class="stat-number">${Object.keys(this.config.rules?.breakpoints || {}).length}</div>
403
- <div class="stat-label">Breakpoints</div>
900
+ <div class="stat-item">
901
+ <span class="stat-num">${bpCount}</span>
902
+ <span class="stat-label">Breakpoints</span>
404
903
  </div>
405
904
  </div>
905
+ </section>
406
906
 
407
- <!-- COLORS -->
408
- <section class="section" id="colors">
907
+ <!-- COLORS -->
908
+ ${this.tokens.colors ? `
909
+ <section class="section" id="colors">
910
+ <div class="section-header">
409
911
  <h2 class="section-title">Colors</h2>
410
- <div class="card-grid">
411
- ${this.generateColorCards()}
412
- </div>
413
- </section>
414
-
415
- <!-- SPACING -->
416
- <section class="section" id="spacing">
912
+ <span class="section-count">${Object.keys(this.tokens.colors).length} defined</span>
913
+ </div>
914
+ <table class="token-table">
915
+ <thead>
916
+ <tr>
917
+ <th></th>
918
+ <th>Token</th>
919
+ <th>CSS Variable</th>
920
+ <th>Value</th>
921
+ <th>Utilities</th>
922
+ </tr>
923
+ </thead>
924
+ <tbody>
925
+ ${this._colorRows()}
926
+ </tbody>
927
+ </table>
928
+ </section>` : ''}
929
+
930
+ <!-- SPACING -->
931
+ ${this.tokens.spacing ? `
932
+ <section class="section" id="spacing">
933
+ <div class="section-header">
417
934
  <h2 class="section-title">Spacing</h2>
418
- <div style="display:flex; flex-direction:column; gap:8px;">
419
- ${this.generateSpacingRows()}
420
- </div>
421
- </section>
422
-
423
- <!-- TYPOGRAPHY -->
424
- <section class="section" id="typography">
935
+ <span class="section-count">${Object.keys(this.tokens.spacing).length} defined</span>
936
+ </div>
937
+ <table class="token-table">
938
+ <thead>
939
+ <tr><th>Token</th><th>Variable</th><th>Value</th><th>Preview</th></tr>
940
+ </thead>
941
+ <tbody>
942
+ ${Object.entries(this.tokens.spacing).map(([k, v]) => `
943
+ <tr class="searchable">
944
+ <td class="token-name">pad:${k} · mar:${k} · gap:${k}</td>
945
+ <td class="token-var">--spacing-${k}</td>
946
+ <td class="token-val">${v}</td>
947
+ <td><div style="width:${Math.min(parseInt(v)*1.5, 200)}px;height:12px;background:var(--accent-2);border-radius:2px;opacity:0.6;"></div></td>
948
+ </tr>
949
+ `).join('')}
950
+ </tbody>
951
+ </table>
952
+ </section>` : ''}
953
+
954
+ <!-- TYPOGRAPHY -->
955
+ ${this.tokens.typography ? `
956
+ <section class="section" id="typography">
957
+ <div class="section-header">
425
958
  <h2 class="section-title">Typography</h2>
426
- <div style="display:flex; flex-direction:column; gap:12px;">
427
- ${this.generateTypographyRows()}
428
- </div>
429
- </section>
430
-
431
- <!-- RADIUS -->
432
- <section class="section" id="radius">
959
+ <span class="section-count">${Object.keys(this.tokens.typography).length} scales</span>
960
+ </div>
961
+ <table class="token-table">
962
+ <thead>
963
+ <tr><th>Token</th><th>Size</th><th>Weight</th><th>Line Height</th><th>Preview</th></tr>
964
+ </thead>
965
+ <tbody>
966
+ ${Object.entries(this.tokens.typography).map(([k, v]) => `
967
+ <tr class="searchable">
968
+ <td class="token-name">text:${k}</td>
969
+ <td class="token-val">${v.size || '—'}</td>
970
+ <td class="token-val">${v.weight || '—'}</td>
971
+ <td class="token-val">${v.line || '—'}</td>
972
+ <td style="font-size:${v.size};font-weight:${v.weight};line-height:${v.line};color:var(--ink);max-width:200px;overflow:hidden;white-space:nowrap;">Aa</td>
973
+ </tr>
974
+ `).join('')}
975
+ </tbody>
976
+ </table>
977
+ </section>` : ''}
978
+
979
+ <!-- FONTS -->
980
+ ${this.tokens.fonts ? `
981
+ <section class="section" id="fonts">
982
+ <div class="section-header">
983
+ <h2 class="section-title">Fonts</h2>
984
+ <span class="section-count">${Object.keys(this.tokens.fonts).length} faces</span>
985
+ </div>
986
+ <table class="token-table">
987
+ <thead><tr><th>Token</th><th>Variable</th><th>Stack</th><th>Preview</th></tr></thead>
988
+ <tbody>
989
+ ${Object.entries(this.tokens.fonts).map(([k, v]) => `
990
+ <tr class="searchable">
991
+ <td class="token-name">type-face:${k}</td>
992
+ <td class="token-var">--font-${k}</td>
993
+ <td class="token-val" style="font-size:11px;">${v}</td>
994
+ <td style="font-family:${v};font-size:18px;color:var(--ink);">The quick brown fox</td>
995
+ </tr>
996
+ `).join('')}
997
+ </tbody>
998
+ </table>
999
+ </section>` : ''}
1000
+
1001
+ <!-- RADIUS -->
1002
+ ${this.tokens.radius ? `
1003
+ <section class="section" id="radius">
1004
+ <div class="section-header">
433
1005
  <h2 class="section-title">Border Radius</h2>
434
- <div class="card-grid">
435
- ${this.generateRadiusCards()}
436
- </div>
437
- </section>
438
-
439
- <!-- SHADOWS -->
440
- <section class="section" id="shadows">
1006
+ <span class="section-count">${Object.keys(this.tokens.radius).length} values</span>
1007
+ </div>
1008
+ <table class="token-table">
1009
+ <thead><tr><th>Preview</th><th>Token</th><th>Variable</th><th>Value</th></tr></thead>
1010
+ <tbody>
1011
+ ${Object.entries(this.tokens.radius).map(([k, v]) => `
1012
+ <tr class="searchable">
1013
+ <td><div style="width:36px;height:36px;background:var(--accent-2);border-radius:${v};opacity:0.7;"></div></td>
1014
+ <td class="token-name">curve:${k}</td>
1015
+ <td class="token-var">--radius-${k}</td>
1016
+ <td class="token-val">${v}</td>
1017
+ </tr>
1018
+ `).join('')}
1019
+ </tbody>
1020
+ </table>
1021
+ </section>` : ''}
1022
+
1023
+ <!-- SHADOWS -->
1024
+ ${this.tokens.shadows ? `
1025
+ <section class="section" id="shadows">
1026
+ <div class="section-header">
441
1027
  <h2 class="section-title">Shadows</h2>
442
- <div style="display:flex; flex-direction:column; gap:12px;">
443
- ${this.generateShadowCards()}
1028
+ <span class="section-count">${Object.keys(this.tokens.shadows).length} levels</span>
1029
+ </div>
1030
+ <table class="token-table">
1031
+ <thead><tr><th>Preview</th><th>Token</th><th>Variable</th><th>Value</th></tr></thead>
1032
+ <tbody>
1033
+ ${Object.entries(this.tokens.shadows).map(([k, v]) => `
1034
+ <tr class="searchable">
1035
+ <td><div style="width:40px;height:28px;background:var(--surface-3);border-radius:4px;box-shadow:${v};"></div></td>
1036
+ <td class="token-name">cast:${k}</td>
1037
+ <td class="token-var">--shadow-${k}</td>
1038
+ <td class="token-val" style="font-size:10px;">${v}</td>
1039
+ </tr>
1040
+ `).join('')}
1041
+ </tbody>
1042
+ </table>
1043
+ </section>` : ''}
1044
+
1045
+ <!-- EASING -->
1046
+ ${this.tokens.easing ? `
1047
+ <section class="section" id="easing">
1048
+ <div class="section-header">
1049
+ <h2 class="section-title">Easing</h2>
1050
+ <span class="section-count">${Object.keys(this.tokens.easing).length} curves</span>
1051
+ </div>
1052
+ <table class="token-table">
1053
+ <thead><tr><th>Token</th><th>Variable</th><th>Value</th></tr></thead>
1054
+ <tbody>
1055
+ ${Object.entries(this.tokens.easing).map(([k, v]) => `
1056
+ <tr class="searchable">
1057
+ <td class="token-name">ease-curve:${k}</td>
1058
+ <td class="token-var">--ease-${k}</td>
1059
+ <td class="token-val">${v}</td>
1060
+ </tr>
1061
+ `).join('')}
1062
+ </tbody>
1063
+ </table>
1064
+ </section>` : ''}
1065
+
1066
+ <!-- DURATION -->
1067
+ ${this.tokens.duration ? `
1068
+ <section class="section" id="duration">
1069
+ <div class="section-header">
1070
+ <h2 class="section-title">Duration</h2>
1071
+ <span class="section-count">${Object.keys(this.tokens.duration).length} speeds</span>
1072
+ </div>
1073
+ <table class="token-table">
1074
+ <thead><tr><th>Token</th><th>Variable</th><th>Value</th></tr></thead>
1075
+ <tbody>
1076
+ ${Object.entries(this.tokens.duration).map(([k, v]) => `
1077
+ <tr class="searchable">
1078
+ <td class="token-name">ease-speed:${k} · ease-wait:${k}</td>
1079
+ <td class="token-var">--duration-${k}</td>
1080
+ <td class="token-val">${v}</td>
1081
+ </tr>
1082
+ `).join('')}
1083
+ </tbody>
1084
+ </table>
1085
+ </section>` : ''}
1086
+
1087
+ <!-- BLUR -->
1088
+ ${this.tokens.blur ? `
1089
+ <section class="section" id="blur">
1090
+ <div class="section-header">
1091
+ <h2 class="section-title">Blur</h2>
1092
+ <span class="section-count">${Object.keys(this.tokens.blur).length} levels</span>
1093
+ </div>
1094
+ <table class="token-table">
1095
+ <thead><tr><th>Token</th><th>Variable</th><th>Value</th></tr></thead>
1096
+ <tbody>
1097
+ ${Object.entries(this.tokens.blur).map(([k, v]) => `
1098
+ <tr class="searchable">
1099
+ <td class="token-name">glass-blur:${k} · glow-blur:${k}</td>
1100
+ <td class="token-var">--blur-${k}</td>
1101
+ <td class="token-val">${v}</td>
1102
+ </tr>
1103
+ `).join('')}
1104
+ </tbody>
1105
+ </table>
1106
+ </section>` : ''}
1107
+
1108
+ <!-- OPACITY -->
1109
+ ${this.tokens.opacity ? `
1110
+ <section class="section" id="opacity">
1111
+ <div class="section-header">
1112
+ <h2 class="section-title">Opacity</h2>
1113
+ <span class="section-count">${Object.keys(this.tokens.opacity).length} levels</span>
1114
+ </div>
1115
+ <table class="token-table">
1116
+ <thead><tr><th>Preview</th><th>Token</th><th>Variable</th><th>Value</th></tr></thead>
1117
+ <tbody>
1118
+ ${Object.entries(this.tokens.opacity).map(([k, v]) => `
1119
+ <tr class="searchable">
1120
+ <td><div style="width:40px;height:20px;background:var(--accent);border-radius:3px;opacity:${v};"></div></td>
1121
+ <td class="token-name">canvas-fade:${k}</td>
1122
+ <td class="token-var">--opacity-${k}</td>
1123
+ <td class="token-val">${v}</td>
1124
+ </tr>
1125
+ `).join('')}
1126
+ </tbody>
1127
+ </table>
1128
+ </section>` : ''}
1129
+
1130
+ <!-- Z-INDEX -->
1131
+ ${this.tokens.zIndex ? `
1132
+ <section class="section" id="zindex">
1133
+ <div class="section-header">
1134
+ <h2 class="section-title">Z-Index</h2>
1135
+ <span class="section-count">${Object.keys(this.tokens.zIndex).length} layers</span>
1136
+ </div>
1137
+ <table class="token-table">
1138
+ <thead><tr><th>Token</th><th>Variable</th><th>Value</th></tr></thead>
1139
+ <tbody>
1140
+ ${Object.entries(this.tokens.zIndex).map(([k, v]) => `
1141
+ <tr class="searchable">
1142
+ <td class="token-name">layer:${k}</td>
1143
+ <td class="token-var">--z-${k}</td>
1144
+ <td class="token-val">${v}</td>
1145
+ </tr>
1146
+ `).join('')}
1147
+ </tbody>
1148
+ </table>
1149
+ </section>` : ''}
1150
+
1151
+ <!-- LEADING -->
1152
+ ${this.tokens.leading ? `
1153
+ <section class="section" id="leading">
1154
+ <div class="section-header">
1155
+ <h2 class="section-title">Leading</h2>
1156
+ <span class="section-count">${Object.keys(this.tokens.leading).length} values</span>
1157
+ </div>
1158
+ <table class="token-table">
1159
+ <thead><tr><th>Token</th><th>Variable</th><th>Value</th><th>Preview</th></tr></thead>
1160
+ <tbody>
1161
+ ${Object.entries(this.tokens.leading).map(([k, v]) => `
1162
+ <tr class="searchable">
1163
+ <td class="token-name">leading:${k}</td>
1164
+ <td class="token-var">--leading-${k}</td>
1165
+ <td class="token-val">${v}</td>
1166
+ <td style="font-size:13px;line-height:${v};color:var(--ink-muted);max-width:160px;">Line one<br>Line two<br>Line three</td>
1167
+ </tr>
1168
+ `).join('')}
1169
+ </tbody>
1170
+ </table>
1171
+ </section>` : ''}
1172
+
1173
+ <!-- TRACKING -->
1174
+ ${this.tokens.tracking ? `
1175
+ <section class="section" id="tracking">
1176
+ <div class="section-header">
1177
+ <h2 class="section-title">Tracking</h2>
1178
+ <span class="section-count">${Object.keys(this.tokens.tracking).length} values</span>
1179
+ </div>
1180
+ <table class="token-table">
1181
+ <thead><tr><th>Token</th><th>Variable</th><th>Value</th><th>Preview</th></tr></thead>
1182
+ <tbody>
1183
+ ${Object.entries(this.tokens.tracking).map(([k, v]) => `
1184
+ <tr class="searchable">
1185
+ <td class="token-name">tracking:${k}</td>
1186
+ <td class="token-var">--tracking-${k}</td>
1187
+ <td class="token-val">${v}</td>
1188
+ <td style="font-size:12px;letter-spacing:${v};color:var(--ink-muted);font-family:var(--mono);">MIZUMI</td>
1189
+ </tr>
1190
+ `).join('')}
1191
+ </tbody>
1192
+ </table>
1193
+ </section>` : ''}
1194
+
1195
+ <!-- TOKEN UTILITIES -->
1196
+ <section class="section" id="token-utilities">
1197
+ <div class="section-header">
1198
+ <h2 class="section-title">Token Utilities</h2>
1199
+ <span class="section-count">Generated from your tokens</span>
1200
+ </div>
1201
+ <p style="font-family:var(--mono);font-size:12px;color:var(--ink-muted);margin-bottom:24px;line-height:1.7;">
1202
+ Every token automatically generates utility classes. Syntax: <span style="color:var(--accent)">capability:token-name</span>
1203
+ </p>
1204
+ ${utilExamples.slice(0, 40).map(u => `
1205
+ <div class="utility-row searchable">
1206
+ <span class="utility-cls">.${u.cap}:${u.value}</span>
1207
+ <span class="utility-arrow">→</span>
1208
+ <span class="utility-css">${u.css}: var(--...)</span>
1209
+ </div>
1210
+ `).join('')}
1211
+ </section>
1212
+
1213
+ <!-- STATIC UTILITIES -->
1214
+ <section class="section" id="static-utilities">
1215
+ <div class="section-header">
1216
+ <h2 class="section-title">Static Utilities</h2>
1217
+ <span class="section-count">Always available</span>
1218
+ </div>
1219
+ ${staticCats.map(cat => `
1220
+ <div class="subsection-title">${cat.name}</div>
1221
+ ${cat.utilities.map(u => `
1222
+ <div class="utility-row searchable">
1223
+ <span class="utility-cls">.${u.cls}</span>
1224
+ <span class="utility-arrow">→</span>
1225
+ <span class="utility-css">${u.css}</span>
1226
+ </div>
1227
+ `).join('')}
1228
+ `).join('')}
1229
+ </section>
1230
+
1231
+ <!-- ARBITRARY VALUES -->
1232
+ <section class="section" id="arbitrary">
1233
+ <div class="section-header">
1234
+ <h2 class="section-title">Arbitrary Values</h2>
1235
+ <span class="section-count">Any valid CSS value</span>
1236
+ </div>
1237
+ <p style="font-family:var(--mono);font-size:12px;color:var(--ink-muted);margin-bottom:24px;line-height:1.8;">
1238
+ Any capability accepts raw CSS values. Use <span style="color:var(--accent)">_</span> for spaces in values.
1239
+ The scanner picks these up at build time and generates the exact CSS rule needed.
1240
+ </p>
1241
+ ${arbitrary.map(a => `
1242
+ <div class="arbitrary-row searchable">
1243
+ <span class="arbitrary-cls">.${a.cls}</span>
1244
+ <span class="arbitrary-desc">${a.desc}</span>
444
1245
  </div>
445
- </section>
1246
+ `).join('')}
1247
+ </section>
446
1248
 
447
- <!-- PATTERNS -->
448
- <section class="section" id="patterns">
1249
+ <!-- PATTERNS -->
1250
+ ${patternCount > 0 ? `
1251
+ <section class="section" id="patterns">
1252
+ <div class="section-header">
449
1253
  <h2 class="section-title">Patterns</h2>
450
- <div id="patterns-list">
451
- ${this.generatePatternRows()}
1254
+ <span class="section-count">${patternCount} defined</span>
1255
+ </div>
1256
+ <p style="font-family:var(--mono);font-size:12px;color:var(--ink-muted);margin-bottom:24px;line-height:1.7;">
1257
+ Reusable utility compositions. Use them like any other class.
1258
+ </p>
1259
+ ${Object.entries(this.patterns).map(([name, value]) => `
1260
+ <div class="pattern-row searchable" onclick="togglePattern(this)">
1261
+ <div class="pattern-header">
1262
+ <span class="pattern-name">.${name}</span>
1263
+ <span class="pattern-toggle">expand ↓</span>
1264
+ </div>
1265
+ <div class="pattern-body">
1266
+ <div class="pattern-value">${value}</div>
1267
+ <div class="pattern-usage">&lt;div class="${name}"&gt;...&lt;/div&gt;</div>
1268
+ </div>
452
1269
  </div>
453
- </section>
1270
+ `).join('')}
1271
+ </section>` : ''}
454
1272
 
455
- <!-- ANIMATIONS -->
456
- <section class="section" id="animations">
1273
+ <!-- ANIMATIONS -->
1274
+ ${animCount > 0 ? `
1275
+ <section class="section" id="animations">
1276
+ <div class="section-header">
457
1277
  <h2 class="section-title">Animations</h2>
458
- <div id="animations-list">
459
- ${this.generateAnimationRows()}
460
- </div>
461
- </section>
1278
+ <span class="section-count">${animCount} defined</span>
1279
+ </div>
1280
+ <p style="font-family:var(--mono);font-size:12px;color:var(--ink-muted);margin-bottom:24px;line-height:1.7;">
1281
+ GSAP-powered animations. Add the class name to any element.
1282
+ Stack modifiers like <span style="color:var(--accent)">duration-300 ease-bouncy delay-200</span> to customize.
1283
+ </p>
1284
+ ${Object.entries(this.animations).map(([name, config]) => {
1285
+ const type = this.getAnimType(config)
1286
+ return `
1287
+ <div class="anim-row searchable">
1288
+ <div class="anim-header">
1289
+ <span class="anim-name">.${name}</span>
1290
+ <span class="anim-badge" style="background:${type.color}18;color:${type.color};border:1px solid ${type.color}44;">${type.label}</span>
1291
+ </div>
1292
+ <div class="anim-body">
1293
+ <div class="anim-config">${JSON.stringify(config, null, 2)}</div>
1294
+ <div class="anim-usage">&lt;div class="${name}"&gt;...&lt;/div&gt;</div>
1295
+ </div>
1296
+ </div>`
1297
+ }).join('')}
1298
+ </section>
462
1299
 
463
- <!-- MODIFIERS -->
464
- <section class="section" id="modifiers">
1300
+ <!-- MODIFIERS -->
1301
+ <section class="section" id="modifiers">
1302
+ <div class="section-header">
465
1303
  <h2 class="section-title">Animation Modifiers</h2>
466
- <p style="color:#64748b; margin-bottom:24px; font-size:14px;">
467
- Stack these onto any animation class to customize behavior.
468
- </p>
469
-
470
- <h3 class="section-sub">Duration</h3>
471
- <div class="card-grid">
472
- ${[100,150,200,300,500,800,1000].map(d => `
473
- <div class="doc-card">
474
- <div class="doc-card-name">duration-${d}</div>
475
- <div class="doc-card-value">${d}ms → ${d/1000}s</div>
476
- </div>
477
- `).join('')}
478
- ${['fast','normal','slow','slower'].map(n => `
479
- <div class="doc-card">
480
- <div class="doc-card-name">duration-${n}</div>
481
- <div class="doc-card-value">Token-based</div>
482
- </div>
483
- `).join('')}
484
- </div>
485
-
486
- <h3 class="section-sub">Delay</h3>
487
- <div class="card-grid">
488
- ${[0,100,150,200,300,500,1000].map(d => `
489
- <div class="doc-card">
490
- <div class="doc-card-name">delay-${d}</div>
491
- <div class="doc-card-value">${d}ms → ${d/1000}s</div>
492
- </div>
493
- `).join('')}
494
- </div>
1304
+ <span class="section-count">Stack onto any animation</span>
1305
+ </div>
495
1306
 
496
- <h3 class="section-sub">Easing</h3>
497
- <div class="card-grid">
498
- ${['smooth','bouncy','sharp','back','linear'].map(e => `
499
- <div class="doc-card">
500
- <div class="doc-card-name">ease-${e}</div>
501
- <div class="doc-card-value">${this.easeToGSAP(e)}</div>
502
- </div>
503
- `).join('')}
504
- </div>
1307
+ <div class="subsection-title">Duration</div>
1308
+ <div class="modifier-grid">
1309
+ ${[100,150,200,300,500,800,1000].map(d => `
1310
+ <div class="modifier-card">
1311
+ <div class="modifier-cls">duration-${d}</div>
1312
+ <div class="modifier-val">${d}ms</div>
1313
+ </div>
1314
+ `).join('')}
1315
+ ${['fast','normal','slow','slower'].map(n => `
1316
+ <div class="modifier-card">
1317
+ <div class="modifier-cls">duration-${n}</div>
1318
+ <div class="modifier-val">token-based</div>
1319
+ </div>
1320
+ `).join('')}
1321
+ </div>
505
1322
 
506
- <h3 class="section-sub">Data Attribute Overrides</h3>
507
- <div class="card-grid">
508
- ${['data-gsap-duration','data-gsap-delay','data-gsap-ease','data-gsap-y','data-gsap-x','data-gsap-scale','data-gsap-opacity'].map(a => `
509
- <div class="doc-card">
510
- <div class="doc-card-name">${a}</div>
511
- <div class="doc-card-value">Highest priority override</div>
512
- </div>
513
- `).join('')}
514
- </div>
515
- </section>
516
-
517
- </main>
518
-
519
- <script>
520
- // Search functionality
521
- function handleSearch(query) {
522
- const q = query.toLowerCase().trim();
523
- const allRows = document.querySelectorAll(
524
- '.doc-card, .pattern-row, .anim-row'
525
- );
526
-
527
- allRows.forEach(row => {
528
- const text = row.textContent.toLowerCase();
529
- row.style.display = !q || text.includes(q) ? '' : 'none';
530
- });
531
- }
532
-
533
- // Smooth scroll for nav links
534
- document.querySelectorAll('.nav-link').forEach(link => {
535
- link.addEventListener('click', e => {
536
- e.preventDefault();
537
- const target = document.querySelector(link.getAttribute('href'));
538
- if (target) target.scrollIntoView({ behavior: 'smooth' });
539
- });
540
- });
541
- </script>
1323
+ <div class="subsection-title">Delay</div>
1324
+ <div class="modifier-grid">
1325
+ ${[0,100,150,200,300,500,1000].map(d => `
1326
+ <div class="modifier-card">
1327
+ <div class="modifier-cls">delay-${d}</div>
1328
+ <div class="modifier-val">${d}ms</div>
1329
+ </div>
1330
+ `).join('')}
1331
+ </div>
542
1332
 
543
- </body>
544
- </html>`;
545
- }
1333
+ <div class="subsection-title">Easing</div>
1334
+ <div class="modifier-grid">
1335
+ ${['smooth','bouncy','sharp','back','linear'].map(e => `
1336
+ <div class="modifier-card">
1337
+ <div class="modifier-cls">ease-${e}</div>
1338
+ <div class="modifier-val">GSAP curve</div>
1339
+ </div>
1340
+ `).join('')}
1341
+ </div>
546
1342
 
547
- generateColorCards() {
548
- const cards = [];
549
- if (!this.tokens.colors) return '';
1343
+ <div class="subsection-title">Prop Overrides (inline)</div>
1344
+ <div class="modifier-grid">
1345
+ ${['data-gsap-duration','data-gsap-delay','data-gsap-ease','data-gsap-y','data-gsap-x','data-gsap-scale','data-gsap-opacity'].map(a => `
1346
+ <div class="modifier-card">
1347
+ <div class="modifier-cls" style="font-size:9px;">${a}</div>
1348
+ <div class="modifier-val">highest priority</div>
1349
+ </div>
1350
+ `).join('')}
1351
+ </div>
1352
+ </section>` : ''}
550
1353
 
551
- for (const [k, v] of Object.entries(this.tokens.colors)) {
552
- if (typeof v === 'object') {
553
- for (const [shade, color] of Object.entries(v)) {
554
- const name = shade === 'DEFAULT' ? k : `${k}-${shade}`;
555
- cards.push(`
556
- <div class="doc-card">
557
- <div class="color-swatch" style="background:${color};"></div>
558
- <div class="doc-card-name">${name}</div>
559
- <div class="doc-card-value">${color}</div>
560
- <div class="doc-card-value" style="margin-top:4px;">
561
- bg-${name} · color-${name}
562
- </div>
563
- </div>
564
- `);
565
- }
566
- } else {
567
- cards.push(`
568
- <div class="doc-card">
569
- <div class="color-swatch" style="background:${v};"></div>
570
- <div class="doc-card-name">${k}</div>
571
- <div class="doc-card-value">${v}</div>
572
- <div class="doc-card-value" style="margin-top:4px;">
573
- bg-${k} · color-${k}
574
- </div>
575
- </div>
576
- `);
577
- }
578
- }
579
- return cards.join('');
1354
+ <!-- .MIZU SYNTAX -->
1355
+ <section class="section" id="mizu-syntax">
1356
+ <div class="section-header">
1357
+ <h2 class="section-title">.mizu Syntax</h2>
1358
+ <span class="section-count">CSS-style config</span>
1359
+ </div>
1360
+ <p style="font-family:var(--mono);font-size:12px;color:var(--ink-muted);margin-bottom:24px;line-height:1.7;">
1361
+ An alternative to <span style="color:var(--accent)">mizumi.config.js</span>.
1362
+ Create any <span style="color:var(--accent)">*.mizu</span> file in your project —
1363
+ it's auto-discovered and merged. No imports needed.
1364
+ </p>
1365
+ <div class="code-block"><span class="code-comment">/* styles.mizu — auto-discovered, no imports needed */</span>
1366
+
1367
+ <span class="code-keyword">@token</span> {
1368
+ colors {
1369
+ <span class="code-name">brand</span>: <span class="code-value">#ff6b6b</span>;
1370
+ <span class="code-name">ocean</span>: <span class="code-value">#006994</span>;
580
1371
  }
581
-
582
- generateSpacingRows() {
583
- if (!this.tokens.spacing) return '';
584
- return Object.entries(this.tokens.spacing).map(([k, v]) => {
585
- const px = parseInt(v);
586
- return `
587
- <div class="pattern-row">
588
- <div class="pattern-name">pad-${k} · mar-${k} · gap-${k}</div>
589
- <div class="spacing-preview">
590
- <div class="spacing-bar" style="width:${Math.min(px * 2, 200)}px;"></div>
591
- <span style="color:#64748b; font-size:13px;">${v}</span>
592
- </div>
593
- </div>
594
- `;
595
- }).join('');
1372
+ spacing {
1373
+ <span class="code-name">hero</span>: <span class="code-value">120px</span>;
1374
+ <span class="code-name">section</span>: <span class="code-value">80px</span>;
596
1375
  }
1376
+ }
597
1377
 
598
- generateTypographyRows() {
599
- if (!this.tokens.typography) return '';
600
- return Object.entries(this.tokens.typography).map(([k, v]) => `
601
- <div class="pattern-row">
602
- <div class="pattern-name">text-${k}</div>
603
- <div style="flex:1;">
604
- <div style="font-size:${v.size}; font-weight:${v.weight}; line-height:${v.line}; color:#e2e8f0; margin-bottom:4px;">
605
- The quick brown fox
606
- </div>
607
- <div style="font-size:12px; color:#64748b;">
608
- ${v.size} · weight ${v.weight} · line ${v.line}
609
- </div>
610
- </div>
611
- </div>
612
- `).join('');
613
- }
1378
+ <span class="code-keyword">@pattern</span> hero-box {
1379
+ <span class="code-name">pad</span>: <span class="code-value">hero</span>;
1380
+ <span class="code-name">paint</span>: <span class="code-value">brand</span>;
1381
+ <span class="code-name">ink</span>: <span class="code-value">white</span>;
1382
+ <span class="code-name">curve</span>: <span class="code-value">xl</span>;
1383
+ }
614
1384
 
615
- generateRadiusCards() {
616
- if (!this.tokens.radius) return '';
617
- return Object.entries(this.tokens.radius).map(([k, v]) => `
618
- <div class="doc-card" style="text-align:center;">
619
- <div style="
620
- width:48px; height:48px;
621
- background:#38bdf8;
622
- border-radius:${v};
623
- margin:0 auto 12px;
624
- opacity:0.7;
625
- "></div>
626
- <div class="doc-card-name">rounded-${k}</div>
627
- <div class="doc-card-value">${v}</div>
628
- </div>
629
- `).join('');
630
- }
1385
+ <span class="code-keyword">@pattern</span> btn-brand <span class="code-keyword">extends</span> button {
1386
+ <span class="code-name">paint</span>: <span class="code-value">brand</span>;
1387
+ <span class="code-name">ink</span>: <span class="code-value">white</span>;
1388
+ }
631
1389
 
632
- generateShadowCards() {
633
- if (!this.tokens.shadows) return '';
634
- return Object.entries(this.tokens.shadows).map(([k, v]) => `
635
- <div class="pattern-row">
636
- <div class="pattern-name">shadow-${k}</div>
637
- <div style="
638
- width:60px; height:32px;
639
- background:#1e293b;
640
- border-radius:6px;
641
- box-shadow:${v};
642
- flex-shrink:0;
643
- "></div>
644
- <div class="doc-card-value" style="font-family:monospace; font-size:12px;">${v}</div>
645
- </div>
646
- `).join('');
1390
+ <span class="code-keyword">@animate</span> hover-brand {
1391
+ hover {
1392
+ <span class="code-name">move-y</span>: <span class="code-value">-6px</span>;
1393
+ <span class="code-name">ease-speed</span>: <span class="code-value">fast</span>;
647
1394
  }
1395
+ }
648
1396
 
649
- generatePatternRows() {
650
- return Object.entries(this.patterns).map(([name, value]) => `
651
- <div class="pattern-row">
652
- <div class="pattern-name">.${name}</div>
653
- <div class="pattern-arrow">→</div>
654
- <div class="pattern-value">${value}</div>
655
- <div class="usage-box" style="width:100%;">
656
- &lt;div class="${name}"&gt;...&lt;/div&gt;
657
- </div>
658
- </div>
659
- `).join('');
1397
+ <span class="code-keyword">@rule</span> {
1398
+ <span class="code-name">responsive</span>: <span class="code-value">true</span>;
1399
+ <span class="code-name">darkMode</span>: <span class="code-value">class</span>;
1400
+ }</div>
1401
+ </section>
1402
+
1403
+ </main>
1404
+
1405
+ <script>
1406
+ // ── Search ──
1407
+ function handleSearch(query) {
1408
+ const q = query.toLowerCase().trim()
1409
+ document.querySelectorAll('.searchable').forEach(el => {
1410
+ const text = el.textContent.toLowerCase()
1411
+ el.classList.toggle('hidden', q !== '' && !text.includes(q))
1412
+ })
660
1413
  }
661
1414
 
662
- generateAnimationRows() {
663
- return Object.entries(this.animations).map(([name, config]) => {
664
- const type = this.getAnimType(config);
665
- return `
666
- <div class="anim-row">
667
- <div class="anim-header">
668
- <div class="anim-name">.${name}</div>
669
- <span class="anim-type type-${type}">${type}</span>
670
- </div>
671
- <div class="anim-config">${JSON.stringify(config, null, 2)}</div>
672
- <div class="usage-box">
673
- &lt;div class="${name}"&gt;...&lt;/div&gt;
674
- </div>
675
- </div>
676
- `;
677
- }).join('');
1415
+ // ── Pattern toggle ──
1416
+ function togglePattern(el) {
1417
+ const body = el.querySelector('.pattern-body')
1418
+ const toggle = el.querySelector('.pattern-toggle')
1419
+ const open = body.classList.toggle('open')
1420
+ toggle.textContent = open ? 'collapse ↑' : 'expand ↓'
678
1421
  }
679
1422
 
680
- getAnimType(config) {
681
- if (config.hover) return 'hover';
682
- if (config.active) return 'click';
683
- if (config.targets === 'children') return 'stagger';
684
- if (config.scrollTrigger) return 'scroll';
685
- if (config.repeat === -1) return 'loop';
686
- return 'entrance';
687
- }
1423
+ // ── Smooth scroll ──
1424
+ document.querySelectorAll('.nav-link').forEach(link => {
1425
+ link.addEventListener('click', e => {
1426
+ e.preventDefault()
1427
+ const target = document.querySelector(link.getAttribute('href'))
1428
+ if (target) target.scrollIntoView({ behavior: 'smooth', block: 'start' })
1429
+ })
1430
+ })
1431
+
1432
+ // ── Copy on click ──
1433
+ document.querySelectorAll('.utility-cls, .arbitrary-cls, .pattern-name, .anim-name').forEach(el => {
1434
+ el.style.cursor = 'pointer'
1435
+ el.title = 'Click to copy'
1436
+ el.addEventListener('click', () => {
1437
+ const text = el.textContent.replace(/^\./, '')
1438
+ navigator.clipboard?.writeText(text)
1439
+ const orig = el.textContent
1440
+ el.textContent = '✓ copied'
1441
+ setTimeout(() => el.textContent = orig, 1000)
1442
+ })
1443
+ })
1444
+ </script>
688
1445
 
689
- easeToGSAP(name) {
690
- const map = {
691
- smooth: 'power2.out',
692
- bouncy: 'elastic.out(1, 0.5)',
693
- sharp: 'power4.inOut',
694
- back: 'back.out(1.7)',
695
- linear: 'none'
696
- };
697
- return map[name] || name;
1446
+ </body>
1447
+ </html>`
698
1448
  }
699
1449
 
700
- countTokens() {
701
- let count = 0;
702
- for (const [, v] of Object.entries(this.tokens)) {
703
- if (typeof v === 'object') {
704
- for (const [, val] of Object.entries(v)) {
705
- if (typeof val === 'object') count += Object.keys(val).length;
706
- else count++;
1450
+ // ── Color rows helper ──
1451
+ _colorRows() {
1452
+ const rows = []
1453
+ for (const [key, val] of Object.entries(this.tokens.colors)) {
1454
+ if (typeof val === 'object') {
1455
+ for (const [shade, color] of Object.entries(val)) {
1456
+ const name = shade === 'DEFAULT' ? key : `${key}-${shade}`
1457
+ rows.push(`
1458
+ <tr class="searchable">
1459
+ <td><div class="color-chip" style="background:${color};"></div></td>
1460
+ <td class="token-name">ink:${name} · paint:${name}</td>
1461
+ <td class="token-var">--color-${name}</td>
1462
+ <td class="token-val">${color}</td>
1463
+ <td class="token-val" style="font-size:10px;">ink · paint · stroke-color · ring-color</td>
1464
+ </tr>`)
707
1465
  }
1466
+ } else {
1467
+ rows.push(`
1468
+ <tr class="searchable">
1469
+ <td><div class="color-chip" style="background:${val};"></div></td>
1470
+ <td class="token-name">ink:${key} · paint:${key}</td>
1471
+ <td class="token-var">--color-${key}</td>
1472
+ <td class="token-val">${val}</td>
1473
+ <td class="token-val" style="font-size:10px;">ink · paint · stroke-color · ring-color</td>
1474
+ </tr>`)
708
1475
  }
709
1476
  }
710
- return count;
1477
+ return rows.join('')
711
1478
  }
712
- }
713
-
1479
+ }