@typecad/ui 1.0.0-alpha.11 → 1.0.0-alpha.13

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 (135) hide show
  1. package/README.md +21 -1
  2. package/assets/fonts/dejavu/DejaVuSans-Bold.ttf +0 -0
  3. package/assets/fonts/dejavu/DejaVuSans-ExtraLight.ttf +0 -0
  4. package/assets/fonts/dejavu/DejaVuSans-Oblique.ttf +0 -0
  5. package/assets/fonts/dejavu/DejaVuSans.ttf +0 -0
  6. package/assets/fonts/dejavu/DejaVuSansMono-Bold.ttf +0 -0
  7. package/assets/fonts/dejavu/DejaVuSansMono.ttf +0 -0
  8. package/assets/fonts/dejavu/LICENSE +187 -0
  9. package/assets/fonts/dejavu/README.md +49 -0
  10. package/dist/cli.d.ts +2 -0
  11. package/dist/cli.js +74 -0
  12. package/dist/engine-index.js +2 -0
  13. package/dist/preview/build-program.d.ts +7 -1
  14. package/dist/preview/build-program.js +94 -15
  15. package/dist/preview/host-gfx.d.ts +3 -0
  16. package/dist/preview/host-gfx.js +30 -3
  17. package/dist/preview/host-ui-runtime.d.ts +115 -0
  18. package/dist/preview/host-ui-runtime.js +928 -46
  19. package/dist/ui-engine/block-layout.js +25 -11
  20. package/dist/ui-engine/color.d.ts +6 -0
  21. package/dist/ui-engine/color.js +80 -2
  22. package/dist/ui-engine/compat-report.d.ts +8 -0
  23. package/dist/ui-engine/compat-report.js +124 -0
  24. package/dist/ui-engine/css-imports.d.ts +4 -0
  25. package/dist/ui-engine/css-imports.js +70 -0
  26. package/dist/ui-engine/css-parser.js +58 -14
  27. package/dist/ui-engine/default-font.d.ts +25 -0
  28. package/dist/ui-engine/default-font.js +80 -0
  29. package/dist/ui-engine/font-assets.d.ts +2 -2
  30. package/dist/ui-engine/font-assets.js +22 -5
  31. package/dist/ui-engine/html-parser.d.ts +4 -0
  32. package/dist/ui-engine/html-parser.js +241 -26
  33. package/dist/ui-engine/image-assets.d.ts +8 -1
  34. package/dist/ui-engine/image-assets.js +35 -2
  35. package/dist/ui-engine/image-decode.d.ts +22 -0
  36. package/dist/ui-engine/image-decode.js +194 -0
  37. package/dist/ui-engine/inline-parser.js +1 -1
  38. package/dist/ui-engine/layout-engine.d.ts +19 -0
  39. package/dist/ui-engine/layout-engine.js +59 -3
  40. package/dist/ui-engine/model.d.ts +18 -0
  41. package/dist/ui-engine/model.js +76 -8
  42. package/dist/ui-engine/runtime-header/blend-bodies.js +29 -9
  43. package/dist/ui-engine/runtime-header/canvas-helpers.js +5 -2
  44. package/dist/ui-engine/runtime-header/canvas-scrollbar.js +115 -111
  45. package/dist/ui-engine/runtime-header/cuttlefish-gfx.js +21 -0
  46. package/dist/ui-engine/runtime-header/dirty-scroll-mutators.js +182 -179
  47. package/dist/ui-engine/runtime-header/forward-decls.js +215 -167
  48. package/dist/ui-engine/runtime-header/init-press-input.js +138 -135
  49. package/dist/ui-engine/runtime-header/keyboard.js +682 -380
  50. package/dist/ui-engine/runtime-header/node-draw-body.js +1656 -1377
  51. package/dist/ui-engine/runtime-header/paint-order-coords.js +259 -214
  52. package/dist/ui-engine/runtime-header/paint-rects-repair.js +736 -568
  53. package/dist/ui-engine/runtime-header/scroll-physics.js +25 -1
  54. package/dist/ui-engine/runtime-header/state-bindings-nav.js +9 -0
  55. package/dist/ui-engine/runtime-header/structs.js +228 -216
  56. package/dist/ui-engine/runtime-header/text-rendering.js +888 -770
  57. package/dist/ui-engine/runtime-header/tick/bindings-phase.js +118 -116
  58. package/dist/ui-engine/runtime-header/tick/dirty-draw-phase.js +897 -633
  59. package/dist/ui-engine/runtime-header/tick/scroll-canvas-phase.js +24 -25
  60. package/dist/ui-engine/runtime-header/touch-keyboard-fwd.js +79 -23
  61. package/dist/ui-engine/runtime-header/types-defines.js +110 -93
  62. package/dist/ui-engine/shadcn-kit.d.ts +1 -0
  63. package/dist/ui-engine/shadcn-kit.js +502 -0
  64. package/dist/ui-engine/style-resolver.d.ts +4 -0
  65. package/dist/ui-engine/style-resolver.js +58 -6
  66. package/dist/ui-engine/transpile-ui.js +12 -5
  67. package/dist/ui-engine/ua-stylesheet.d.ts +22 -1
  68. package/dist/ui-engine/ua-stylesheet.js +119 -32
  69. package/dist/ui-engine/ui-lowering.js +37 -3
  70. package/dist/ui-engine/ui-registry.js +32 -4
  71. package/dist/ui-engine/yoga-layout.js +57 -52
  72. package/dist/wizard/config-writer.d.ts +49 -0
  73. package/dist/wizard/config-writer.js +366 -0
  74. package/dist/wizard/display-catalog.d.ts +75 -0
  75. package/dist/wizard/display-catalog.js +207 -0
  76. package/dist/wizard/index.d.ts +7 -0
  77. package/dist/wizard/index.js +10 -0
  78. package/dist/wizard/integration-wizard.d.ts +15 -0
  79. package/dist/wizard/integration-wizard.js +486 -0
  80. package/dist/wizard/prompts.d.ts +32 -0
  81. package/dist/wizard/prompts.js +95 -0
  82. package/dist/wizard/starter-ui.d.ts +1 -0
  83. package/dist/wizard/starter-ui.js +56 -0
  84. package/package.json +18 -5
  85. package/src/cli.ts +87 -0
  86. package/src/engine-index.ts +53 -51
  87. package/src/preview/build-program.ts +810 -734
  88. package/src/preview/host-gfx.ts +30 -3
  89. package/src/preview/host-ui-runtime.ts +4133 -3289
  90. package/src/ui-engine/block-layout.ts +24 -11
  91. package/src/ui-engine/color.ts +77 -2
  92. package/src/ui-engine/compat-report.ts +139 -0
  93. package/src/ui-engine/css-imports.ts +69 -0
  94. package/src/ui-engine/css-parser.ts +64 -15
  95. package/src/ui-engine/default-font.ts +95 -0
  96. package/src/ui-engine/font-assets.ts +545 -525
  97. package/src/ui-engine/html-parser.ts +615 -397
  98. package/src/ui-engine/image-assets.ts +37 -2
  99. package/src/ui-engine/image-decode.ts +244 -0
  100. package/src/ui-engine/inline-parser.ts +1 -1
  101. package/src/ui-engine/layout-engine.ts +61 -3
  102. package/src/ui-engine/model.ts +1317 -1230
  103. package/src/ui-engine/runtime-header/blend-bodies.ts +29 -9
  104. package/src/ui-engine/runtime-header/canvas-helpers.ts +5 -2
  105. package/src/ui-engine/runtime-header/canvas-scrollbar.ts +121 -117
  106. package/src/ui-engine/runtime-header/cuttlefish-gfx.ts +21 -0
  107. package/src/ui-engine/runtime-header/dirty-scroll-mutators.ts +188 -185
  108. package/src/ui-engine/runtime-header/forward-decls.ts +227 -179
  109. package/src/ui-engine/runtime-header/init-press-input.ts +144 -141
  110. package/src/ui-engine/runtime-header/keyboard.ts +689 -386
  111. package/src/ui-engine/runtime-header/node-draw-body.ts +1681 -1402
  112. package/src/ui-engine/runtime-header/paint-order-coords.ts +265 -220
  113. package/src/ui-engine/runtime-header/paint-rects-repair.ts +742 -574
  114. package/src/ui-engine/runtime-header/scroll-physics.ts +25 -1
  115. package/src/ui-engine/runtime-header/state-bindings-nav.ts +9 -0
  116. package/src/ui-engine/runtime-header/structs.ts +234 -222
  117. package/src/ui-engine/runtime-header/text-rendering.ts +894 -776
  118. package/src/ui-engine/runtime-header/tick/bindings-phase.ts +124 -122
  119. package/src/ui-engine/runtime-header/tick/dirty-draw-phase.ts +902 -638
  120. package/src/ui-engine/runtime-header/tick/scroll-canvas-phase.ts +30 -31
  121. package/src/ui-engine/runtime-header/touch-keyboard-fwd.ts +79 -23
  122. package/src/ui-engine/runtime-header/types-defines.ts +116 -99
  123. package/src/ui-engine/shadcn-kit.ts +503 -0
  124. package/src/ui-engine/style-resolver.ts +471 -416
  125. package/src/ui-engine/transpile-ui.ts +12 -5
  126. package/src/ui-engine/ua-stylesheet.ts +137 -31
  127. package/src/ui-engine/ui-lowering.ts +486 -452
  128. package/src/ui-engine/ui-registry.ts +556 -524
  129. package/src/ui-engine/yoga-layout.ts +307 -309
  130. package/src/wizard/config-writer.ts +404 -0
  131. package/src/wizard/display-catalog.ts +273 -0
  132. package/src/wizard/index.ts +38 -0
  133. package/src/wizard/integration-wizard.ts +619 -0
  134. package/src/wizard/prompts.ts +145 -0
  135. package/src/wizard/starter-ui.ts +58 -0
@@ -0,0 +1,503 @@
1
+ // ---------------------------------------------------------------------------
2
+ // The built-in shadcn-style kit stylesheet (tokens + class recipes). Always
3
+ // prepended to the CSS chain by BOTH pipelines (ui-registry for device
4
+ // builds, buildPreviewSnapshot for the preview) ahead of the user's CSS, so
5
+ // user rules and theme token blocks override it by cascade order. Themes are
6
+ // plain CSS files the user @imports — no registry, no splicing.
7
+ //
8
+ // Source of truth: this string. Edit here; the demos rely on it verbatim.
9
+ // ---------------------------------------------------------------------------
10
+
11
+ export const SHADCN_KIT_CSS = `/* ---------------------------------------------------------------------------
12
+ * shadcn-style component kit — BUILT IN and always included.
13
+ *
14
+ * These tokens + class recipes are prepended to every build and preview
15
+ * automatically, BEFORE your stylesheets, so anything you write overrides
16
+ * them by normal cascade order. No scaffolding, no imports needed: put the
17
+ * classes on native elements and they work.
18
+ *
19
+ * THEMES: a theme is any CSS file. Pre-packaged ones ship with @typecad/ui —
20
+ * import by bare specifier from a <style> block (or the sidecar .ui.css):
21
+ *
22
+ * @import "@typecad/ui/themes/blue.css"; (zinc slate stone gray neutral
23
+ * blue green red)
24
+ *
25
+ * Your own: save a ui.shadcn.com / tweakcn export into the project and
26
+ * @import it by path — its :root/.dark token blocks override the kit
27
+ * defaults (later definitions win). Both dialects parse — classic HSL channel triplets and
28
+ * Tailwind-v4 oklch(); alpha in colors is ignored (no blending on bare
29
+ * metal). Dark mode activates with themeClass: 'dark' in the config's
30
+ * display block.
31
+ *
32
+ * Overriding recipes: redefine any class in your own stylesheet; your
33
+ * definition wins.
34
+ *
35
+ * Components (classes over native elements):
36
+ * Button <button class="btn btn-primary">Save</button>
37
+ * variants: -secondary -outline -ghost -destructive
38
+ * sizes: .btn-sm .btn-lg full width: .btn-block
39
+ * Badge <text class="badge badge-secondary">new</text>
40
+ * variants: -default(omit) -destructive -outline
41
+ * Card <view class="card">
42
+ * <view class="card-header">
43
+ * <text class="card-title">Title</text>
44
+ * <text class="card-description">Subtitle</text>
45
+ * </view>
46
+ * <view class="card-content">...</view>
47
+ * <view class="card-footer">...buttons...</view>
48
+ * </view>
49
+ * Input <input class="input" placeholder="..."/>
50
+ * Label <label class="form-label">Name</label>
51
+ * Row (kit) <view class="row"> ... </view> (flex row + gap, for composing)
52
+ * Validation .input-error (destructive border) + .field-error hint;
53
+ * runtime-driven: bind borderColor/visible to a signal
54
+ * Separator <hr class="separator"/> (horizontal) or
55
+ * <hr class="vseparator"/> (vertical; stretches to the row height)
56
+ * Alert <view class="alert alert-destructive">
57
+ * <text class="alert-title">...</text>
58
+ * <text class="alert-description">...</text>
59
+ * </view>
60
+ * Skeleton <view class="skeleton"/> (pulse while loading)
61
+ * Spinner <view class="spinner"><view class="spinner-dot"/></view>
62
+ * (indeterminate loading: a dot orbiting a ring via pure
63
+ * transform keyframes — translate lerps smoothly, unlike
64
+ * rotate which only renders exact quarter turns)
65
+ * Dialog <dialog id="d"><view class="dialog-scrim" on:click={...}/>
66
+ * <view class="dialog-card"> ... <view class="dialog-footer">
67
+ * </view></dialog>
68
+ * (centered modal: programmatic ui.dialog.open(id), scrim tap
69
+ * closes; same slot machinery as the drawer)
70
+ * Toast <toast id="t" side="bottom" duration="2500" class="toast">
71
+ * ...</toast> (ui.toast(id) shows it; auto-closes after the
72
+ * duration; stacks as authored siblings)
73
+ * Progress <progress class="progress" value="40"/>
74
+ * Avatar <img class="avatar" src="face.bmp"/>
75
+ * Switch <check class="switch"/> (pill container; the
76
+ * 16px indicator is runtime-drawn at the top-left — best-effort)
77
+ * Tabs <view class="tabs-list"><button class="tabs-trigger">..</button>..
78
+ * <view class="tabs-content-area"><view class="tabs-content">..
79
+ * (panes toggle via ui.bind(x, 'visible', ...) on a signal)
80
+ * Accordion <view class="accordion"><view class="accordion-item">..
81
+ * (content toggles via ui.bind visible; chevron via text bind)
82
+ * Table <table> ... </table> (UA styles apply; see the
83
+ * html-table-approximation build note)
84
+ * ------------------------------------------------------------------------- */
85
+
86
+ :root {
87
+ --background: #ffffff;
88
+ --foreground: #09090b;
89
+ --card: #ffffff;
90
+ --card-foreground: #09090b;
91
+ --primary: #18181b;
92
+ --primary-foreground: #fafafa;
93
+ --secondary: #f4f4f5;
94
+ --secondary-foreground: #18181b;
95
+ --muted: #f4f4f5;
96
+ --muted-foreground: #71717a;
97
+ --accent: #f4f4f5;
98
+ --accent-foreground: #18181b;
99
+ --destructive: #dc2626;
100
+ --destructive-foreground: #fafafa;
101
+ --destructive-background: #fee2e2;
102
+ --border: #e4e4e7;
103
+ --input: #e4e4e7;
104
+ --radius: 8px;
105
+ --shadow-sm: 0 1px 3px rgb(0 0 0 / 0.1);
106
+ /* Solid scrim for dialogs — alpha blending needs a canvas underneath, so
107
+ the kit uses an opaque near-black that reads as a dimmed backdrop. */
108
+ --scrim: #101014;
109
+ }
110
+
111
+ .dark {
112
+ --background: #09090b;
113
+ --foreground: #fafafa;
114
+ --card: #18181b;
115
+ --card-foreground: #fafafa;
116
+ --primary: #fafafa;
117
+ --primary-foreground: #18181b;
118
+ --secondary: #27272a;
119
+ --secondary-foreground: #fafafa;
120
+ --muted: #27272a;
121
+ --muted-foreground: #a1a1aa;
122
+ --accent: #27272a;
123
+ --accent-foreground: #fafafa;
124
+ --destructive: #ef4444;
125
+ --destructive-foreground: #09090b;
126
+ --destructive-background: #451a1a;
127
+ --border: #27272a;
128
+ --input: #3f3f46;
129
+ --radius: 8px;
130
+ }
131
+
132
+ /* ---- Button ---------------------------------------------------------------- */
133
+
134
+ .btn {
135
+ border-radius: var(--radius);
136
+ padding: 10px 16px;
137
+ font-weight: bold;
138
+ transition: background 80ms;
139
+ }
140
+ .btn:pressed { transform: translateY(1px); }
141
+ .btn-primary { background: var(--primary); color: var(--primary-foreground); }
142
+ .btn-secondary { background: var(--secondary); color: var(--secondary-foreground); }
143
+ .btn-outline { background: var(--background); color: var(--foreground); border: 1px solid var(--border); }
144
+ .btn-ghost { color: var(--foreground); }
145
+ .btn-destructive { background: var(--destructive); color: var(--destructive-foreground); }
146
+ .btn-sm { padding: 6px 10px; min-height: 32px; font-size: 12px; }
147
+ .btn-lg { padding: 14px 22px; min-height: 52px; font-size: 18px; }
148
+ .btn-block { align-self: stretch; }
149
+
150
+ /* ---- Badge ------------------------------------------------------------------ */
151
+
152
+ .badge {
153
+ background: var(--primary);
154
+ color: var(--primary-foreground);
155
+ padding: 2px 8px;
156
+ border-radius: 999px;
157
+ font-size: 12px;
158
+ text-align: center;
159
+ }
160
+ .badge-secondary { background: var(--secondary); color: var(--secondary-foreground); }
161
+ .badge-destructive { background: var(--destructive); color: var(--destructive-foreground); }
162
+ .badge-outline { background: var(--background); color: var(--foreground); border: 1px solid var(--border); }
163
+
164
+ /* ---- Layout utility -------------------------------------------------------------
165
+ Not a shadcn component — a kit convenience for composing recipes. */
166
+
167
+ .row { flex-direction: row; align-items: center; gap: 8px; }
168
+
169
+ /* ---- Card ------------------------------------------------------------------- */
170
+
171
+ .card {
172
+ background: var(--card);
173
+ color: var(--card-foreground);
174
+ border: 1px solid var(--border);
175
+ border-radius: var(--radius);
176
+ box-shadow: var(--shadow-sm);
177
+ padding: 16px;
178
+ gap: 8px;
179
+ align-self: stretch;
180
+ }
181
+ .card-header { gap: 4px; }
182
+ .card-title { font-size: 18px; font-weight: bold; }
183
+ .card-description { font-size: 14px; color: var(--muted-foreground); }
184
+ .card-content { gap: 8px; }
185
+ .card-footer { flex-direction: row; gap: 8px; }
186
+
187
+ /* ---- Tabs ---------------------------------------------------------------- */
188
+
189
+ /* Segmented trigger row (shadcn TabsList). Active-trigger styling is driven
190
+ at runtime by ui.bind background/color bindings in author code — runtime
191
+ color swaps are literal hex (compile-time tokens), so pin them to the
192
+ active theme the way native_demo accents do. */
193
+ .tabs-list {
194
+ flex-direction: row;
195
+ gap: 4px;
196
+ align-self: stretch;
197
+ background: var(--muted);
198
+ border-radius: var(--radius);
199
+ padding: 4px;
200
+ }
201
+ .tabs-trigger {
202
+ flex-grow: 1;
203
+ border-radius: calc(var(--radius) - 2px);
204
+ padding: 6px 10px;
205
+ min-height: 32px;
206
+ font-size: 13px;
207
+ text-align: center;
208
+ }
209
+ /* Fixed-height content region: panes are absolutely stacked inside it and
210
+ toggled via ui.bind(x, 'visible', ...) — layout keeps every pane's box,
211
+ so switching never re-flows. */
212
+ .tabs-content-area {
213
+ position: relative;
214
+ align-self: stretch;
215
+ height: 150px;
216
+ }
217
+ .tabs-content {
218
+ position: absolute;
219
+ left: 0;
220
+ top: 0;
221
+ right: 0;
222
+ bottom: 0;
223
+ gap: 8px;
224
+ }
225
+
226
+ /* ---- Accordion (single-open) --------------------------------------------- */
227
+
228
+ /* Stacked collapsible sections. The trigger is a button + chevron row; the
229
+ content pane toggles via ui.bind(x, 'visible', () => signal === i) — the
230
+ same signal pattern as Tabs. The chevron swaps v/^ via a text binding. */
231
+ .accordion {
232
+ gap: 8px;
233
+ align-self: stretch;
234
+ }
235
+ .accordion-item {
236
+ background: var(--card);
237
+ border: 1px solid var(--border);
238
+ border-radius: var(--radius);
239
+ }
240
+ .accordion-trigger-row {
241
+ flex-direction: row;
242
+ align-items: center;
243
+ gap: 8px;
244
+ padding: 10px 12px;
245
+ }
246
+ .accordion-trigger {
247
+ flex-grow: 1;
248
+ text-align: left;
249
+ font-weight: bold;
250
+ padding: 0;
251
+ min-height: 20px;
252
+ /* The UA sheet gives every button a 1px border (border: 1px solid, colored
253
+ by the foreground token). A trigger is a plain text row on the card —
254
+ cancel it or each one renders with a bright outline. */
255
+ border: none;
256
+ }
257
+ .accordion-chevron {
258
+ color: var(--muted-foreground);
259
+ font-size: 14px;
260
+ /* Wide enough for the '^' glyph (advance 12px at 14px size) — a single
261
+ char wider than the box wraps to nothing. */
262
+ width: 16px;
263
+ text-align: center;
264
+ }
265
+ .accordion-content {
266
+ padding: 0 12px 10px 12px;
267
+ gap: 8px;
268
+ }
269
+
270
+ /* ---- Form controls ----------------------------------------------------------- */
271
+
272
+ .input {
273
+ background: var(--background);
274
+ color: var(--foreground);
275
+ border: 1px solid var(--input);
276
+ border-radius: var(--radius);
277
+ }
278
+ .form-label { color: var(--foreground); font-size: 14px; }
279
+
280
+ /* Switch: pill container. The runtime draws a 16px circular knob that
281
+ slides with state — hollow at the left when off, solid at the right when
282
+ on (a static jump, no travel animation). min-height overrides the UA
283
+ touch target so the pill stays 26px tall. */
284
+ .switch {
285
+ width: 44px;
286
+ height: 26px;
287
+ /* Override the UA's touch-target min-height (42px on small panels) — the
288
+ pill must stay 26px tall to read as a switch, not a checkbox. */
289
+ min-height: 26px;
290
+ border-radius: 999px;
291
+ border: 1px solid var(--border);
292
+ /* --card, not --muted: on a bare (near-black) page background a --muted
293
+ track reads as a stray gray box; --card keeps the pill affordance while
294
+ staying quiet. Swap back to var(--muted) inside lighter containers. */
295
+ background: var(--card);
296
+ }
297
+
298
+ /* ---- Checked-state pairs (shadcn data-[state=checked]) ------------------------
299
+ The ON control carries the primary pair: the switch's TRACK turns primary
300
+ with a primary-foreground knob, the checkbox face and the radio's selected
301
+ ring do the same. The select's pair themes the OPTION LIST's selected row
302
+ with the accent pair (shadcn's SelectItem selected state). These bake at
303
+ build time from :checked rules (a separate style bucket, like :pressed);
304
+ the device runtime swaps the pair onto the indicator when the value flips,
305
+ and the preview draws through the same fields. */
306
+ .switch:checked {
307
+ background: var(--primary);
308
+ border-color: var(--primary);
309
+ color: var(--primary-foreground);
310
+ }
311
+ check:checked {
312
+ background: var(--primary);
313
+ color: var(--primary-foreground);
314
+ }
315
+ radio:checked {
316
+ background: var(--primary);
317
+ color: var(--primary-foreground);
318
+ }
319
+ .select:checked {
320
+ background: var(--accent);
321
+ color: var(--accent-foreground);
322
+ }
323
+
324
+ /* ---- Form validation states -------------------------------------------------- */
325
+
326
+ /* Static hook: always-invalid styling for hardcoded markup. Runtime-driven
327
+ validation (recommended) binds the input's borderColor + a .field-error
328
+ hint's visible/text to a signal — see the demo's Forms screen. */
329
+ .input-error {
330
+ border-color: var(--destructive);
331
+ }
332
+ .field-error {
333
+ color: var(--destructive);
334
+ font-size: 12px;
335
+ }
336
+ .field-success {
337
+ color: var(--muted-foreground);
338
+ font-size: 12px;
339
+ }
340
+
341
+ /* ---- Separator ---------------------------------------------------------------- */
342
+
343
+ .separator {
344
+ height: 1px;
345
+ background: var(--border);
346
+ margin: 8px 0;
347
+ }
348
+ /* Vertical: 1px wide, stretches to the row's cross height (shadcn's
349
+ <Separator orientation="vertical" /> — flex column + align-self: stretch). */
350
+ .vseparator {
351
+ width: 1px;
352
+ height: 100%;
353
+ align-self: stretch;
354
+ background: var(--border);
355
+ margin: 0 8px;
356
+ }
357
+
358
+ /* ---- Alert -------------------------------------------------------------------- */
359
+
360
+ .alert {
361
+ background: var(--background);
362
+ color: var(--foreground);
363
+ border: 1px solid var(--border);
364
+ border-radius: var(--radius);
365
+ padding: 12px;
366
+ gap: 4px;
367
+ align-self: stretch;
368
+ }
369
+ .alert-destructive {
370
+ background: var(--destructive-background);
371
+ border: 1px solid var(--destructive);
372
+ color: var(--destructive);
373
+ }
374
+ .alert-title { font-weight: bold; }
375
+ .alert-description { font-size: 14px; color: var(--muted-foreground); }
376
+
377
+ /* ---- Skeleton (loading placeholder) --------------------------------------------- */
378
+
379
+ /* Opacity pulse. If the target display's keyframe support skips opacity,
380
+ the block still renders as a static muted placeholder. */
381
+ .skeleton {
382
+ background: var(--muted);
383
+ border-radius: var(--radius);
384
+ height: 16px;
385
+ align-self: stretch;
386
+ animation: ui-skeleton-pulse 1.2s ease-in-out infinite;
387
+ }
388
+ @keyframes ui-skeleton-pulse {
389
+ 0% { opacity: 1; }
390
+ 50% { opacity: 0.55; }
391
+ 100% { opacity: 1; }
392
+ }
393
+
394
+ /* ---- Spinner ---------------------------------------------------------------- */
395
+
396
+ /* Indeterminate loading indicator: a dot orbiting inside a ring, pure
397
+ transform keyframes. translate() lerps CONTINUOUSLY between stops (unlike
398
+ rotate(), which only renders exact quarter turns), so the orbit is smooth.
399
+ The dot is absolute + out of flow; the ring is a fixed square so border
400
+ clipping stays symmetric. Sizes: change .spinner's width/height and keep
401
+ the dot inset consistent (orbit travel = inner - dot). */
402
+ .spinner {
403
+ position: relative;
404
+ width: 22px;
405
+ height: 22px;
406
+ border: 2px solid var(--muted);
407
+ border-radius: 999px;
408
+ }
409
+ .spinner-dot {
410
+ position: absolute;
411
+ /* The dot's BASE position is the orbit's TOP-LEFT corner, not the ring
412
+ center: the keyframes translate 0..8px from here, and the path only
413
+ centers when base + travel/2 == ring center (with the 2px border,
414
+ top/left 2px + border lands the 6px dot at 4,4; its center travels
415
+ 7..15 around the 22px ring's center at 11,11). */
416
+ top: 2px;
417
+ left: 2px;
418
+ width: 6px;
419
+ height: 6px;
420
+ background: var(--primary);
421
+ border-radius: 999px;
422
+ animation: ui-spinner-orbit 1000ms linear infinite;
423
+ }
424
+ @keyframes ui-spinner-orbit {
425
+ 0% { transform: translate(0px, 0px); }
426
+ 25% { transform: translate(8px, 0px); }
427
+ 50% { transform: translate(8px, 8px); }
428
+ 75% { transform: translate(0px, 8px); }
429
+ 100% { transform: translate(0px, 0px); }
430
+ }
431
+ /* ---- Dialog ---------------------------------------------------------------- */
432
+
433
+ /* Centered modal: <dialog> is a centered drawer (side-free) — programmatic
434
+ open/close via ui.dialog.open(id)/close(id?), hidden while closed by the
435
+ runtime (visibility gate for centered panels; offsets can't hide them).
436
+ The scrim is part of the markup: an absolute full-area view whose tap
437
+ closes the dialog; the card sits above it. z-index keeps both over page
438
+ content. */
439
+ .dialog-scrim {
440
+ position: absolute;
441
+ left: 0;
442
+ right: 0;
443
+ top: 0;
444
+ bottom: 0;
445
+ background: var(--scrim);
446
+ z-index: 30;
447
+ }
448
+ .dialog-card {
449
+ position: absolute;
450
+ left: 24px;
451
+ right: 24px;
452
+ top: 64px;
453
+ background: var(--card);
454
+ color: var(--card-foreground);
455
+ border: 1px solid var(--border);
456
+ border-radius: var(--radius);
457
+ box-shadow: var(--shadow-lg);
458
+ padding: 16px;
459
+ gap: 8px;
460
+ z-index: 31;
461
+ }
462
+ .dialog-footer {
463
+ flex-direction: row;
464
+ justify-content: flex-end;
465
+ gap: 8px;
466
+ }
467
+
468
+ /* ---- Toast ----------------------------------------------------------------- */
469
+
470
+ /* Transient notification: <toast side="bottom" duration="2500"> slides from
471
+ the bottom edge (author-positioned, flush to the edge so the slide fully
472
+ hides it) and auto-closes after duration ms. Show with ui.toast(id).
473
+ Sibling toasts stack by author layout; each closes on its own timer. */
474
+ .toast {
475
+ position: absolute;
476
+ left: 12px;
477
+ right: 12px;
478
+ bottom: 0;
479
+ background: var(--foreground);
480
+ color: var(--background);
481
+ border-radius: var(--radius);
482
+ padding: 12px;
483
+ gap: 4px;
484
+ z-index: 40;
485
+ }
486
+ .toast-title { font-weight: bold; font-size: 14px; }
487
+ .toast-description { font-size: 12px; color: var(--muted-foreground); }
488
+
489
+
490
+
491
+ /* ---- Progress / Avatar ----------------------------------------------------------- */
492
+
493
+ .progress {
494
+ height: 12px;
495
+ border-radius: 999px;
496
+ background: var(--secondary);
497
+ border: 1px solid var(--border);
498
+ color: var(--primary);
499
+ }
500
+ /* Avatar: plain image element. The runtime draws images rectangular — no
501
+ rounded clipping — so no border/radius here (a border would just draw
502
+ over the image). Wrap in a sized view if you want a frame. */
503
+ `;