@profpowell/vb-design-system 0.1.0

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 (104) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +122 -0
  3. package/dist/vb-design-system.css +993 -0
  4. package/dist/vb-design-system.js +4482 -0
  5. package/dist/vb-design-system.min.css +1 -0
  6. package/dist/vb-design-system.min.js +513 -0
  7. package/package.json +78 -0
  8. package/src/index.css +21 -0
  9. package/src/index.js +24 -0
  10. package/src/lib/bundle-registry.js +156 -0
  11. package/src/lib/vb-element.js +96 -0
  12. package/src/patterns/do-dont.css +86 -0
  13. package/src/patterns/token-table.css +67 -0
  14. package/src/utils/copy-init.js +287 -0
  15. package/src/web-components/accessibility-specimen/api.json +51 -0
  16. package/src/web-components/accessibility-specimen/logic.js +232 -0
  17. package/src/web-components/accessibility-specimen/static.html +41 -0
  18. package/src/web-components/accessibility-specimen/styles.css +156 -0
  19. package/src/web-components/brand-specimen/api.json +37 -0
  20. package/src/web-components/brand-specimen/logic.js +61 -0
  21. package/src/web-components/brand-specimen/static.html +77 -0
  22. package/src/web-components/brand-specimen/styles.css +10 -0
  23. package/src/web-components/breakpoint-specimen/api.json +44 -0
  24. package/src/web-components/breakpoint-specimen/logic.js +186 -0
  25. package/src/web-components/breakpoint-specimen/static.html +38 -0
  26. package/src/web-components/breakpoint-specimen/styles.css +180 -0
  27. package/src/web-components/color-palette/api.json +73 -0
  28. package/src/web-components/color-palette/logic.js +280 -0
  29. package/src/web-components/color-palette/static.html +54 -0
  30. package/src/web-components/color-palette/styles.css +113 -0
  31. package/src/web-components/color-picker/_color-utils.js +210 -0
  32. package/src/web-components/component-sampler/api.json +15 -0
  33. package/src/web-components/component-sampler/logic.js +120 -0
  34. package/src/web-components/component-sampler/static.html +47 -0
  35. package/src/web-components/component-sampler/styles.css +9 -0
  36. package/src/web-components/font-pairer/_font-data.js +95 -0
  37. package/src/web-components/font-pairer/api.json +56 -0
  38. package/src/web-components/font-pairer/logic.js +401 -0
  39. package/src/web-components/font-pairer/static.html +9 -0
  40. package/src/web-components/font-pairer/styles.css +10 -0
  41. package/src/web-components/gradient-builder/_gradient-utils.js +54 -0
  42. package/src/web-components/gradient-builder/api.json +63 -0
  43. package/src/web-components/gradient-builder/logic.js +261 -0
  44. package/src/web-components/gradient-builder/static.html +9 -0
  45. package/src/web-components/gradient-builder/styles.css +10 -0
  46. package/src/web-components/icon-set/api.json +39 -0
  47. package/src/web-components/icon-set/logic.js +133 -0
  48. package/src/web-components/icon-set/static.html +36 -0
  49. package/src/web-components/icon-set/styles.css +82 -0
  50. package/src/web-components/icon-specimen/api.json +46 -0
  51. package/src/web-components/icon-specimen/logic.js +67 -0
  52. package/src/web-components/icon-specimen/static.html +39 -0
  53. package/src/web-components/icon-specimen/styles.css +38 -0
  54. package/src/web-components/layout-specimen/api.json +12 -0
  55. package/src/web-components/layout-specimen/logic.js +219 -0
  56. package/src/web-components/layout-specimen/static.html +22 -0
  57. package/src/web-components/layout-specimen/styles.css +95 -0
  58. package/src/web-components/motion-specimen/api.json +54 -0
  59. package/src/web-components/motion-specimen/logic.js +146 -0
  60. package/src/web-components/motion-specimen/static.html +13 -0
  61. package/src/web-components/motion-specimen/styles.css +29 -0
  62. package/src/web-components/palette-generator/_palette-utils.js +133 -0
  63. package/src/web-components/palette-generator/api.json +79 -0
  64. package/src/web-components/palette-generator/logic.js +288 -0
  65. package/src/web-components/palette-generator/static.html +47 -0
  66. package/src/web-components/palette-generator/styles.css +36 -0
  67. package/src/web-components/semantic-palette/_color-math.js +149 -0
  68. package/src/web-components/semantic-palette/api.json +41 -0
  69. package/src/web-components/semantic-palette/logic.js +400 -0
  70. package/src/web-components/semantic-palette/static.html +42 -0
  71. package/src/web-components/semantic-palette/styles.css +20 -0
  72. package/src/web-components/spacing-specimen/api.json +63 -0
  73. package/src/web-components/spacing-specimen/logic.js +132 -0
  74. package/src/web-components/spacing-specimen/static.html +71 -0
  75. package/src/web-components/spacing-specimen/styles.css +58 -0
  76. package/src/web-components/theme-catalog/api.json +14 -0
  77. package/src/web-components/theme-catalog/logic.js +228 -0
  78. package/src/web-components/theme-catalog/static.html +15 -0
  79. package/src/web-components/theme-export/api.json +18 -0
  80. package/src/web-components/theme-export/dtcg-serialize.js +417 -0
  81. package/src/web-components/theme-export/logic.js +201 -0
  82. package/src/web-components/theme-export/static.html +12 -0
  83. package/src/web-components/theme-export/theme-css-parse.js +93 -0
  84. package/src/web-components/theme-import/api.json +14 -0
  85. package/src/web-components/theme-import/catalog/atlassian.tokens.json +303 -0
  86. package/src/web-components/theme-import/catalog/bootstrap.tokens.json +304 -0
  87. package/src/web-components/theme-import/catalog/carbon.tokens.json +301 -0
  88. package/src/web-components/theme-import/catalog/catppuccin-mocha.tokens.json +301 -0
  89. package/src/web-components/theme-import/catalog/govuk.tokens.json +301 -0
  90. package/src/web-components/theme-import/catalog/manifest.json +132 -0
  91. package/src/web-components/theme-import/catalog/material.tokens.json +289 -0
  92. package/src/web-components/theme-import/catalog/salesforce.tokens.json +301 -0
  93. package/src/web-components/theme-import/catalog/tailwind.tokens.json +302 -0
  94. package/src/web-components/theme-import/dtcg-deserialize.js +322 -0
  95. package/src/web-components/theme-import/logic.js +263 -0
  96. package/src/web-components/theme-import/static.html +15 -0
  97. package/src/web-components/token-specimen/api.json +24 -0
  98. package/src/web-components/token-specimen/logic.js +256 -0
  99. package/src/web-components/token-specimen/static.html +47 -0
  100. package/src/web-components/token-specimen/styles.css +10 -0
  101. package/src/web-components/type-specimen/api.json +91 -0
  102. package/src/web-components/type-specimen/logic.js +156 -0
  103. package/src/web-components/type-specimen/static.html +84 -0
  104. package/src/web-components/type-specimen/styles.css +131 -0
@@ -0,0 +1,67 @@
1
+ /**
2
+ * icon-specimen: Curated icon-language specimen
3
+ *
4
+ * Presents a fixed, curated set of icon names across the project's sizing
5
+ * scale — a design specimen (not a searchable browser like <icon-set>).
6
+ * Each cell renders the icon with the `[data-icon]` primitive itself (a
7
+ * currentColor CSS mask sized in `em`), so the specimen documents the same
8
+ * terse authoring pattern it is teaching.
9
+ *
10
+ * @attr {string} set - Icon set directory (default: "lucide")
11
+ * @attr {string} names - Space/comma-separated list of icon names (required)
12
+ * @attr {string} data-sizes - Space-separated size scale (default: "1rem 1.5rem 2rem")
13
+ *
14
+ * @example
15
+ * <icon-specimen names="check x star settings" data-sizes="1rem 1.5rem 2rem"></icon-specimen>
16
+ */
17
+
18
+ import { registerComponent } from '../../lib/bundle-registry.js';
19
+ import { VBElement } from '../../lib/vb-element.js';
20
+
21
+ class IconSpecimen extends VBElement {
22
+ static observedAttributes = ['set', 'names', 'data-sizes'];
23
+
24
+ setup() {
25
+ this.#render();
26
+ }
27
+
28
+ attributeChangedCallback() {
29
+ if (this.isConnected) this.#render();
30
+ }
31
+
32
+ get set() {
33
+ return this.getAttribute('set') || 'lucide';
34
+ }
35
+
36
+ get names() {
37
+ return (this.getAttribute('names') || '').split(/[\s,]+/).filter(Boolean);
38
+ }
39
+
40
+ get sizes() {
41
+ return (this.getAttribute('data-sizes') || '1rem 1.5rem 2rem').trim().split(/\s+/);
42
+ }
43
+
44
+ #render() {
45
+ const { set, names, sizes } = this;
46
+ const esc = (s) => String(s).replace(/&/g, '&amp;').replace(/</g, '&lt;')
47
+ .replace(/>/g, '&gt;').replace(/"/g, '&quot;');
48
+ const cell = (n) => sizes.map((sz) => `
49
+ <td><i data-icon="${esc(n)}" data-icon-set="${set}" style="font-size:${sz}" aria-hidden="true"></i></td>`).join('');
50
+ this.innerHTML = `
51
+ <table class="icon-specimen">
52
+ <thead>
53
+ <tr>
54
+ <th scope="col">Icon</th>
55
+ ${sizes.map((sz) => `<th scope="col">${sz}</th>`).join('')}
56
+ </tr>
57
+ </thead>
58
+ <tbody>
59
+ ${names.map((n) => `<tr><th scope="row"><code>${n}</code></th>${cell(n)}</tr>`).join('')}
60
+ </tbody>
61
+ </table>`;
62
+ }
63
+ }
64
+
65
+ registerComponent('icon-specimen', IconSpecimen);
66
+
67
+ export { IconSpecimen };
@@ -0,0 +1,39 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>Icon Specimen — Static Fallback</title>
7
+ <style>
8
+ body { font-family: system-ui, sans-serif; padding: 2rem; }
9
+ table { border-collapse: collapse; }
10
+ th, td { text-align: left; padding: 0.5rem 1rem; border-bottom: 1px solid #d4d4d8; }
11
+ </style>
12
+ </head>
13
+ <body>
14
+ <!--
15
+ Static fallback for <icon-specimen>.
16
+
17
+ Upgrade relationship:
18
+ - Without JS: a plain table listing each curated icon name across the
19
+ size scale remains readable, so the specimen's contents are never
20
+ hidden from no-JS readers.
21
+ - With JS: the component re-renders each cell with the real icon via
22
+ <icon-wc>, sized per the `data-sizes` scale.
23
+ -->
24
+
25
+ <icon-specimen names="check x star settings" data-sizes="1rem 1.5rem 2rem">
26
+ <table>
27
+ <thead>
28
+ <tr><th>Icon</th><th>1rem</th><th>1.5rem</th><th>2rem</th></tr>
29
+ </thead>
30
+ <tbody>
31
+ <tr><th scope="row"><code>check</code></th><td>check</td><td>check</td><td>check</td></tr>
32
+ <tr><th scope="row"><code>x</code></th><td>x</td><td>x</td><td>x</td></tr>
33
+ <tr><th scope="row"><code>star</code></th><td>star</td><td>star</td><td>star</td></tr>
34
+ <tr><th scope="row"><code>settings</code></th><td>settings</td><td>settings</td><td>settings</td></tr>
35
+ </tbody>
36
+ </table>
37
+ </icon-specimen>
38
+ </body>
39
+ </html>
@@ -0,0 +1,38 @@
1
+ /* icon-specimen: curated icon-language specimen across the sizing scale */
2
+
3
+ icon-specimen:not(:defined) {
4
+ display: block;
5
+ }
6
+
7
+ icon-specimen {
8
+ display: block;
9
+ font-family: var(--font-sans, system-ui);
10
+ color: var(--color-text, #1a1a1a);
11
+ }
12
+
13
+ @scope (icon-specimen) {
14
+ .icon-specimen {
15
+ inline-size: 100%;
16
+ border-collapse: collapse;
17
+ }
18
+
19
+ .icon-specimen th,
20
+ .icon-specimen td {
21
+ text-align: start;
22
+ padding: var(--size-s, 0.75rem);
23
+ border-block-end: 1px solid var(--color-border, #d4d4d8);
24
+ }
25
+
26
+ .icon-specimen thead th {
27
+ font-size: var(--font-size-xs, 0.75rem);
28
+ font-weight: 700;
29
+ text-transform: uppercase;
30
+ letter-spacing: 0.05em;
31
+ color: var(--color-text-muted, #71717a);
32
+ }
33
+
34
+ .icon-specimen td [data-icon] {
35
+ color: inherit;
36
+ vertical-align: middle;
37
+ }
38
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "$schema": "../../../schemas/api.schema.json",
3
+ "element": "layout-specimen",
4
+ "type": "web-component",
5
+ "description": "Visual specimen of VB layout primitives for design-system docs. Renders each primitive with a labeled mini-example and the canonical HTML snippet.",
6
+ "attributes": [
7
+ { "name": "data-only", "kind": "data", "purpose": "config", "type": "string", "description": "Comma-separated subset of primitive names to render (e.g. \"cluster,grid,stack\"; with or without the \"layout-\" prefix). Default: all 14 primitives." }
8
+ ],
9
+ "events": [],
10
+ "childAttributes": [],
11
+ "structure": []
12
+ }
@@ -0,0 +1,219 @@
1
+ /**
2
+ * layout-specimen: Visual specimen of VB layout primitives for design-system docs.
3
+ *
4
+ * Renders each layout custom element with a labeled mini-example and the
5
+ * canonical HTML snippet. Lets docs readers see the pattern + copy it.
6
+ *
7
+ * @attr {string} data-only - Comma-separated subset (e.g. "cluster,grid,stack").
8
+ * Defaults to all primitives below.
9
+ *
10
+ * Adjacent: spacing-specimen, type-specimen, motion-specimen, breakpoint-specimen,
11
+ * token-specimen — each visualizes a different design-system axis.
12
+ */
13
+
14
+ import { registerComponent } from '../../lib/bundle-registry.js';
15
+ import { VBElement } from '../../lib/vb-element.js';
16
+
17
+ /**
18
+ * Each entry: { name, desc, snippet, render(box) }
19
+ * - snippet is the source HTML to display in the code block
20
+ * - render() returns a DOM tree for the live mini-example
21
+ */
22
+ const PRIMITIVES = [
23
+ {
24
+ name: 'layout-stack',
25
+ desc: 'Vertical stack with consistent gap between children.',
26
+ snippet: `<layout-stack data-layout-gap="m">
27
+ <p>One</p>
28
+ <p>Two</p>
29
+ <p>Three</p>
30
+ </layout-stack>`,
31
+ render: () => el('layout-stack', { 'data-layout-gap': 's' }, [box('One'), box('Two'), box('Three')]),
32
+ },
33
+ {
34
+ name: 'layout-cluster',
35
+ desc: 'Horizontal cluster that wraps; for tag rows, button groups.',
36
+ snippet: `<layout-cluster data-layout-gap="s">
37
+ <button>One</button> <button>Two</button> <button>Three</button>
38
+ </layout-cluster>`,
39
+ render: () => el('layout-cluster', { 'data-layout-gap': 's' }, [chip('One'), chip('Two'), chip('Three'), chip('Four')]),
40
+ },
41
+ {
42
+ name: 'layout-grid',
43
+ desc: 'Auto-fit responsive grid; columns flow based on min-item-size.',
44
+ snippet: `<layout-grid data-layout-min="12rem">
45
+ <div>1</div> <div>2</div> <div>3</div> <div>4</div>
46
+ </layout-grid>`,
47
+ render: () => el('layout-grid', { 'data-layout-min': '6rem', 'data-layout-gap': 's' }, [box('1'), box('2'), box('3'), box('4'), box('5'), box('6')]),
48
+ },
49
+ {
50
+ name: 'layout-center',
51
+ desc: 'Center a block horizontally with optional max-width.',
52
+ snippet: `<layout-center data-layout-max="readable">
53
+ <p>Centered prose.</p>
54
+ </layout-center>`,
55
+ render: () => el('layout-center', { 'data-layout-max': 'narrow', style: 'border: 1px dashed var(--color-border); padding: var(--size-2xs)' }, [box('Centered')]),
56
+ },
57
+ {
58
+ name: 'layout-cover',
59
+ desc: 'Vertical cover layout: optional header + main area + optional footer, with main filling available space.',
60
+ snippet: `<layout-cover style="block-size: 12rem">
61
+ <header>Top</header>
62
+ <main>Center</main>
63
+ <footer>Bottom</footer>
64
+ </layout-cover>`,
65
+ render: () => el('layout-cover', { style: 'block-size: 9rem; border: 1px dashed var(--color-border); padding: var(--size-2xs)' }, [
66
+ el('header', {}, [box('Top')]),
67
+ el('main', {}, [box('Center', 'main')]),
68
+ el('footer', {}, [box('Bottom')]),
69
+ ]),
70
+ },
71
+ {
72
+ name: 'layout-imposter',
73
+ desc: 'Absolute centering — child sits in the dead center of the parent.',
74
+ snippet: `<div style="position:relative; block-size:8rem">
75
+ <layout-imposter><span>Centered</span></layout-imposter>
76
+ </div>`,
77
+ render: () => el('div', { style: 'position:relative; block-size: 6rem; border: 1px dashed var(--color-border)' }, [
78
+ el('layout-imposter', {}, [box('Imposter')]),
79
+ ]),
80
+ },
81
+ {
82
+ name: 'layout-columns',
83
+ desc: 'Multi-column text layout (CSS columns); good for newspapery prose.',
84
+ snippet: `<layout-columns data-layout-columns="3">
85
+ <p>Long prose flows across columns…</p>
86
+ </layout-columns>`,
87
+ render: () => el('layout-columns', { 'data-layout-columns': '3' }, [
88
+ el('p', { style: 'margin: 0; font-size: 0.85em' }, ['One two three four five six seven eight nine ten eleven twelve thirteen fourteen fifteen.']),
89
+ ]),
90
+ },
91
+ {
92
+ name: 'layout-card',
93
+ desc: 'Stack of header + body + actions with card chrome (border + padding).',
94
+ snippet: `<layout-card>
95
+ <h3>Title</h3>
96
+ <p>Body text.</p>
97
+ <div data-layout-card-actions><button>OK</button></div>
98
+ </layout-card>`,
99
+ render: () => el('layout-card', {}, [
100
+ el('h3', { style: 'margin: 0; font-size: 0.9em' }, ['Card title']),
101
+ el('p', { style: 'margin: 0; font-size: 0.85em' }, ['Card body content.']),
102
+ ]),
103
+ },
104
+ {
105
+ name: 'layout-canvas',
106
+ desc: 'Full-bleed canvas section that breaks the centered max-width.',
107
+ snippet: `<layout-canvas>
108
+ <p>Edge-to-edge content.</p>
109
+ </layout-canvas>`,
110
+ render: () => el('layout-canvas', { style: 'background: var(--color-surface-sunken, oklch(94% 0 0)); padding: var(--size-2xs)' }, [box('Full-bleed')]),
111
+ },
112
+ {
113
+ name: 'layout-badge',
114
+ desc: 'Tiny inline badge / pill for inline labels.',
115
+ snippet: `<layout-badge>NEW</layout-badge>`,
116
+ render: () => el('layout-cluster', { 'data-layout-gap': 'xs' }, [
117
+ el('layout-badge', {}, ['NEW']),
118
+ el('layout-badge', {}, ['BETA']),
119
+ el('layout-badge', {}, ['v2.0']),
120
+ ]),
121
+ },
122
+ {
123
+ name: 'layout-reel',
124
+ desc: 'Horizontal scrollable reel with snap points.',
125
+ snippet: `<layout-reel>
126
+ <article>1</article> <article>2</article> <article>3</article>
127
+ </layout-reel>`,
128
+ render: () => el('layout-reel', {}, [box('1', 'reel'), box('2', 'reel'), box('3', 'reel'), box('4', 'reel'), box('5', 'reel'), box('6', 'reel')]),
129
+ },
130
+ {
131
+ name: 'layout-sidebar',
132
+ desc: 'Sidebar + main split that collapses to a stack on narrow viewports.',
133
+ snippet: `<layout-sidebar>
134
+ <aside>Side</aside>
135
+ <main>Main</main>
136
+ </layout-sidebar>`,
137
+ render: () => el('layout-sidebar', {}, [
138
+ el('aside', { style: 'background: var(--color-surface-sunken, oklch(94% 0 0)); padding: var(--size-2xs)' }, ['Side']),
139
+ el('main', {}, [box('Main')]),
140
+ ]),
141
+ },
142
+ {
143
+ name: 'layout-switcher',
144
+ desc: 'Container that switches from a row to a stack at a configurable threshold.',
145
+ snippet: `<layout-switcher data-layout-threshold="30rem">
146
+ <div>One</div> <div>Two</div>
147
+ </layout-switcher>`,
148
+ render: () => el('layout-switcher', { 'data-layout-threshold': '20rem' }, [box('One'), box('Two')]),
149
+ },
150
+ {
151
+ name: 'layout-text',
152
+ desc: 'Constrains line length to a readable measure (~60ch).',
153
+ snippet: `<layout-text>
154
+ <p>Constrained-measure prose for comfortable reading.</p>
155
+ </layout-text>`,
156
+ render: () => el('layout-text', {}, [
157
+ el('p', { style: 'margin: 0; font-size: 0.85em' }, ['This paragraph is constrained to a comfortable reading measure (~60ch) regardless of the parent width.']),
158
+ ]),
159
+ },
160
+ ];
161
+
162
+ // ── Tiny element-builder helper (no JSX, no template literal HTML parsing) ────
163
+
164
+ function el(tag, attrs = {}, children = []) {
165
+ const node = document.createElement(tag);
166
+ for (const [k, v] of Object.entries(attrs)) {
167
+ if (v == null) continue;
168
+ if (k === 'style') node.setAttribute('style', v);
169
+ else node.setAttribute(k, v === true ? '' : String(v));
170
+ }
171
+ for (const c of children) {
172
+ if (typeof c === 'string') node.appendChild(document.createTextNode(c));
173
+ else if (c) node.appendChild(c);
174
+ }
175
+ return node;
176
+ }
177
+
178
+ function box(label, variant) {
179
+ const b = el('div', { class: `ls-box${variant ? ` ls-box-${variant}` : ''}` }, [label]);
180
+ return b;
181
+ }
182
+
183
+ function chip(label) {
184
+ return el('span', { class: 'ls-chip' }, [label]);
185
+ }
186
+
187
+ class LayoutSpecimen extends VBElement {
188
+ setup() {
189
+ const only = this.getAttribute('data-only');
190
+ const allowed = only ? new Set(only.split(',').map((s) => s.trim())) : null;
191
+ const list = PRIMITIVES.filter((p) => !allowed || allowed.has(p.name) || allowed.has(p.name.replace(/^layout-/, '')));
192
+
193
+ this.replaceChildren();
194
+ for (const prim of list) {
195
+ this.appendChild(this.#renderEntry(prim));
196
+ }
197
+ }
198
+
199
+ #renderEntry(prim) {
200
+ const section = el('section', { class: 'ls-entry' });
201
+
202
+ const head = el('header', { class: 'ls-entry-head' }, [
203
+ el('h3', { class: 'ls-entry-name' }, [prim.name]),
204
+ el('p', { class: 'ls-entry-desc' }, [prim.desc]),
205
+ ]);
206
+
207
+ const live = el('div', { class: 'ls-live' });
208
+ live.appendChild(prim.render());
209
+
210
+ const code = el('pre', { class: 'ls-snippet' }, [el('code', {}, [prim.snippet])]);
211
+
212
+ section.append(head, live, code);
213
+ return section;
214
+ }
215
+ }
216
+
217
+ registerComponent('layout-specimen', LayoutSpecimen);
218
+
219
+ export { LayoutSpecimen };
@@ -0,0 +1,22 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>layout-specimen demo</title>
7
+ <link rel="stylesheet" href="/src/main.css">
8
+ <style>
9
+ body { font-family: system-ui, sans-serif; max-inline-size: 56rem; margin-inline: auto; padding: 2rem 1rem; line-height: 1.5; }
10
+ </style>
11
+ </head>
12
+ <body>
13
+ <h1>layout-specimen</h1>
14
+ <p>All 14 VB layout primitives at a glance.</p>
15
+ <layout-specimen></layout-specimen>
16
+
17
+ <h2>Subset (cluster + grid + stack only)</h2>
18
+ <layout-specimen data-only="cluster,grid,stack"></layout-specimen>
19
+
20
+ <script type="module" src="/src/main.js"></script>
21
+ </body>
22
+ </html>
@@ -0,0 +1,95 @@
1
+ /* --------------------------------------------------------------------------
2
+ layout-specimen: visual showcase of VB layout primitives for docs.
3
+ -------------------------------------------------------------------------- */
4
+
5
+ layout-specimen {
6
+ display: flex;
7
+ flex-direction: column;
8
+ gap: var(--size-l, 1.5rem);
9
+ font: inherit;
10
+ }
11
+
12
+ layout-specimen .ls-entry {
13
+ display: grid;
14
+ grid-template-columns: 1fr;
15
+ gap: var(--size-s, 0.75rem);
16
+ padding: var(--size-s, 0.75rem);
17
+ border: var(--border-width-thin, 1px) solid var(--color-border, #ddd);
18
+ border-radius: var(--radius-m, 0.5rem);
19
+ background: var(--color-surface, #fff);
20
+ }
21
+
22
+ layout-specimen .ls-entry-head {
23
+ display: flex;
24
+ flex-direction: column;
25
+ gap: var(--size-3xs, 0.125rem);
26
+ }
27
+
28
+ layout-specimen .ls-entry-name {
29
+ margin: 0;
30
+ font-size: 1em;
31
+ font-family: var(--font-mono, ui-monospace, monospace);
32
+ color: var(--color-text, #1a1a1a);
33
+ }
34
+
35
+ layout-specimen .ls-entry-desc {
36
+ margin: 0;
37
+ font-size: 0.875em;
38
+ color: var(--color-text-muted, currentColor);
39
+ }
40
+
41
+ layout-specimen .ls-live {
42
+ padding: var(--size-s, 0.75rem);
43
+ background: var(--color-surface-sunken, oklch(94% 0 0));
44
+ border-radius: var(--radius-s, 0.25rem);
45
+ min-block-size: 4rem;
46
+ }
47
+
48
+ layout-specimen .ls-snippet {
49
+ margin: 0;
50
+ padding: var(--size-2xs, 0.375rem) var(--size-s, 0.5rem);
51
+ background: var(--color-surface-sunken, oklch(94% 0 0));
52
+ border-radius: var(--radius-s, 0.25rem);
53
+ font-size: 0.8em;
54
+ line-height: 1.4;
55
+ font-family: var(--font-mono, ui-monospace, monospace);
56
+ color: var(--color-text, #1a1a1a);
57
+ overflow-x: auto;
58
+ white-space: pre;
59
+ }
60
+
61
+ /* -------- Mini-example placeholder boxes -------- */
62
+
63
+ layout-specimen .ls-box {
64
+ display: inline-flex;
65
+ align-items: center;
66
+ justify-content: center;
67
+ padding: var(--size-xs, 0.5rem);
68
+ background: var(--color-action-subtle, color-mix(in oklch, var(--color-interactive, oklch(55% 0.2 260)) 15%, transparent));
69
+ color: var(--color-text, #1a1a1a);
70
+ border: var(--border-width-thin, 1px) solid var(--color-interactive, currentColor);
71
+ border-radius: var(--radius-s, 0.25rem);
72
+ font-size: 0.85em;
73
+ font-weight: 500;
74
+ min-block-size: 2rem;
75
+ }
76
+
77
+ layout-specimen .ls-box-main {
78
+ background: var(--color-surface, #fff);
79
+ border-style: dashed;
80
+ }
81
+
82
+ layout-specimen .ls-box-reel {
83
+ inline-size: 5rem;
84
+ flex-shrink: 0;
85
+ }
86
+
87
+ layout-specimen .ls-chip {
88
+ display: inline-flex;
89
+ align-items: center;
90
+ padding: var(--size-3xs, 0.125rem) var(--size-2xs, 0.375rem);
91
+ background: var(--color-surface-raised, var(--color-surface, #fff));
92
+ border: var(--border-width-thin, 1px) solid var(--color-border, #ddd);
93
+ border-radius: var(--radius-full, 999px);
94
+ font-size: 0.85em;
95
+ }
@@ -0,0 +1,54 @@
1
+ {
2
+ "$schema": "../../../schemas/api.schema.json",
3
+ "element": "motion-specimen",
4
+ "type": "web-component",
5
+ "description": "Design token motion scale display. Shows easing curves with animated preview dots and/or durations as bars.",
6
+ "attributes": [
7
+ {
8
+ "name": "type",
9
+ "kind": "host-api",
10
+ "purpose": "config",
11
+ "type": "enum",
12
+ "values": ["easing", "duration", "both"],
13
+ "default": "easing",
14
+ "description": "Which motion tokens to display."
15
+ },
16
+ {
17
+ "name": "tokens",
18
+ "kind": "host-api",
19
+ "purpose": "config",
20
+ "type": "string",
21
+ "description": "Comma-separated token names (defaults vary by type)."
22
+ },
23
+ {
24
+ "name": "prefix",
25
+ "kind": "host-api",
26
+ "purpose": "config",
27
+ "type": "string",
28
+ "description": "CSS variable prefix. Auto-set to --ease- or --duration- from type."
29
+ },
30
+ {
31
+ "name": "duration",
32
+ "kind": "host-api",
33
+ "purpose": "config",
34
+ "type": "string",
35
+ "default": "1.2s",
36
+ "description": "Animation duration for easing previews (only applies to easing rows)."
37
+ },
38
+ {
39
+ "name": "show-values",
40
+ "kind": "host-api",
41
+ "purpose": "config",
42
+ "type": "boolean",
43
+ "default": "true",
44
+ "description": "Show computed token values."
45
+ },
46
+ {
47
+ "name": "label",
48
+ "kind": "host-api",
49
+ "purpose": "config",
50
+ "type": "string",
51
+ "description": "Optional heading label."
52
+ }
53
+ ]
54
+ }