@the-portland-company/canvas-editor 0.2.0 → 0.3.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 (39) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/README.md +12 -0
  3. package/dist/canvas.css +486 -0
  4. package/dist/chunk-6XOUU7UE.js +727 -0
  5. package/dist/chunk-6XOUU7UE.js.map +1 -0
  6. package/dist/chunk-XQ4AURVN.js +637 -0
  7. package/dist/chunk-XQ4AURVN.js.map +1 -0
  8. package/dist/editor.cjs +3000 -550
  9. package/dist/editor.cjs.map +1 -1
  10. package/dist/editor.d.cts +214 -7
  11. package/dist/editor.d.ts +214 -7
  12. package/dist/editor.js +2415 -414
  13. package/dist/editor.js.map +1 -1
  14. package/dist/index-Cxk71DVp.d.ts +114 -0
  15. package/dist/index-S2hjr-C4.d.cts +114 -0
  16. package/dist/index.cjs +683 -34
  17. package/dist/index.cjs.map +1 -1
  18. package/dist/index.d.cts +66 -138
  19. package/dist/index.d.ts +66 -138
  20. package/dist/index.js +2 -3
  21. package/dist/index.js.map +1 -1
  22. package/dist/registry-BO8AfpUv.d.ts +302 -0
  23. package/dist/registry-B_eYgKbr.d.cts +302 -0
  24. package/dist/renderer.cjs +299 -30
  25. package/dist/renderer.cjs.map +1 -1
  26. package/dist/renderer.d.cts +2 -22
  27. package/dist/renderer.d.ts +2 -22
  28. package/dist/renderer.js +1 -2
  29. package/dist/{types-BnMorrOZ.d.cts → types-GmfMPpfA.d.cts} +11 -4
  30. package/dist/{types-BnMorrOZ.d.ts → types-GmfMPpfA.d.ts} +11 -4
  31. package/package.json +21 -1
  32. package/dist/adapters-BNNgdXGf.d.ts +0 -131
  33. package/dist/adapters-Fge5XZ5R.d.cts +0 -131
  34. package/dist/chunk-F4XERUOO.js +0 -252
  35. package/dist/chunk-F4XERUOO.js.map +0 -1
  36. package/dist/chunk-RQHKLW3X.js +0 -211
  37. package/dist/chunk-RQHKLW3X.js.map +0 -1
  38. package/dist/chunk-XE353SZL.js +0 -279
  39. package/dist/chunk-XE353SZL.js.map +0 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,44 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.0 — feature + styling parity with the main VRM app's email editor
4
+
5
+ Per Spencer's 2026-09-11 direction: bring the shared editor to parity with the
6
+ app.politogyvrm.com email editor. Gap analysis in `docs/APP_EDITOR_PARITY_MANIFEST.md`.
7
+
8
+ - **Rich text is now tiptap v3** (`CanvasRichText`): 42-stack font-family menu
9
+ with Email-safe / Not-email-safe badges and the brand font first, 10-step
10
+ font sizes, line-spacing (Single/1.5/Double), text color, B/I/U, align incl.
11
+ justify, lists, link popover (no `window.prompt`), heading levels P/H1–H6,
12
+ emoji picker, personalization tokens at the cursor. Sanitizer now keeps
13
+ `h5/h6`, inline `line-height`, and heading/list `style`.
14
+ **New peer deps:** `@tiptap/{core,react,starter-kit,extension-underline,
15
+ extension-text-align,extension-link,extension-text-style,extension-color,
16
+ extension-font-family}` ^3.27 and `emoji-picker-react` ^4.19.
17
+ - **New block types:** `hero`, `card`, `container`, `pdf`, `html` (sanitized,
18
+ code/visual modes); `column` gains `distribution` + `gap`; `section` gains
19
+ `backgroundImage` / `borderRadius` / `columnPadding`; social icons gain
20
+ `style` solid|outline|minimal and `colorScheme` brand|black|white|custom.
21
+ Footer derives defaults from `BrandInfo.org` with cleared-vs-unset semantics
22
+ and is enforced singleton + last via `normalizeFooter`.
23
+ - **Padding presets** S/M/L/Custom per block kind (`blocks/padding.ts`,
24
+ `PaddingFields presetKind`), with shorthand ⇄ `{t,r,b,l}` converters.
25
+ - **Undo / redo** (depth 50) with ⌘Z / ⌘⇧Z / Ctrl+Y and TopBar buttons.
26
+ - **Insert model:** animated exact-slot drop zones between blocks (root +
27
+ nested), type-sized drag ghost, grouped Basic / Layout insert menu, `side`
28
+ gutter mode via the new `insertMode` prop; `pointerWithin` collision with a
29
+ `closestCenter` fallback.
30
+ - **Canvas settings:** width Fixed/Fluid, content width px, top/bottom padding
31
+ (new nullable `EmailSettings` fields, honored by canvas + renderer).
32
+ - **Draft safety:** draft signature skips no-op saves; stale-read reseed guard
33
+ ignores an older host document.
34
+ - **Modals + toasts** replace `window.prompt/confirm/alert`.
35
+ - **Preview shells:** Plain / Desktop (mac window) / Mobile (phone), fullscreen.
36
+ - **Styling:** `.ce-*` class contract (300px rails, selection outline, drop
37
+ accents, pills/toggles, toolbar groups, badges, modals, preview shells),
38
+ token-driven light + neutral-black dark.
39
+ - `BrandInfo.org` (companyName/address/websiteUrl/privacyUrl/contactUrl/logoUrl)
40
+ for hosts to feed footer defaults.
41
+
3
42
  ## 0.2.0
4
43
 
5
44
  - **Passthrough `unsupported` block** — a non-destructive block type so hosts
package/README.md CHANGED
@@ -25,6 +25,14 @@ and a Vite (React 18) app alike.
25
25
  | `@the-portland-company/canvas-editor/skeletons` | `Skeleton` / `SkeletonBar` (self-contained, token-driven) | Server-renderable. |
26
26
  | `@the-portland-company/canvas-editor/css` | Primitive component classes (`.card`/`.input`/`.btn`/`.skeleton`…) | Import once in the app. |
27
27
 
28
+ ### Peer dependencies (install alongside)
29
+
30
+ React 18 || 19, `@dnd-kit/{core,sortable,utilities}`, and — since 0.3.0, for the
31
+ tiptap rich-text editor — `@tiptap/core @tiptap/react @tiptap/starter-kit
32
+ @tiptap/extension-underline @tiptap/extension-text-align @tiptap/extension-link
33
+ @tiptap/extension-text-style @tiptap/extension-color @tiptap/extension-font-family`
34
+ (^3.27) and `emoji-picker-react` (^4.19). The package externalizes all of them.
35
+
28
36
  **Never import `/editor` from a server module** — `@dnd-kit` evaluates
29
37
  `React.createContext` at module load and will crash in an RSC / server-action
30
38
  context. Server code that needs block logic imports the root (`.`) only.
@@ -77,9 +85,13 @@ context. Server code that needs block logic imports the root (`.`) only.
77
85
  initialUpdatedAt={updatedAt}
78
86
  nextAction={{ label: "Next: Sender →", href: senderHref }}
79
87
  metaPanel={({ doc, setMeta }) => <MyEmailMetaPanel doc={doc} setMeta={setMeta} />}
88
+ insertMode="center" // or "side" — host owns the preference
80
89
  />
81
90
  ```
82
91
 
92
+ `brand()` may also return `org: { companyName, address, websiteUrl, privacyUrl,
93
+ contactUrl, logoUrl }` so the Footer block derives its defaults from the host.
94
+
83
95
  ## The boundary
84
96
 
85
97
  - **The package owns:** the document model (`CanvasDocument` = `name` + `blocks`
package/dist/canvas.css CHANGED
@@ -65,3 +65,489 @@
65
65
  border: 8px solid var(--color-border); border-radius: 2rem;
66
66
  background: var(--color-surface-2); padding: 1rem 0.75rem; min-height: 12rem;
67
67
  }
68
+
69
+ /* ==========================================================================
70
+ Canvas Editor layout + interaction classes (`.ce-*`)
71
+ --------------------------------------------------------------------------
72
+ PUBLIC CONTRACT — these are the class names editor components consume.
73
+ Everything is token-driven (var(--color-*)); `.dark` overrides exist only
74
+ where a token cannot express the value (shadow alpha, device chrome).
75
+
76
+ .ce-layout 3-column editor grid: 300px rail / canvas / 300px rail
77
+ .ce-layout.is-left-collapsed left rail collapsed to a 44px strip
78
+ .ce-rail a side rail panel (surface, border, scrolls)
79
+ .ce-rail-header sticky rail heading row
80
+ .ce-canvas center scroll column, 24px padding
81
+ .ce-canvas-sheet the white/surface page the blocks render on
82
+ .ce-block one block wrapper (hover/selection host)
83
+ .ce-block.is-selected 2px accent outline
84
+ .ce-block-toolbar floating hover toolbar (drag + delete), top:-40px right:0
85
+ .ce-block-toolbar-btn a button inside that toolbar
86
+ .ce-dropzone collapsed 8px drop target between blocks
87
+ .ce-dropzone.is-hover expanded 32px, accent dashed + tint
88
+ .ce-dropzone.is-drag-active armed while a drag is in flight
89
+ .ce-dropzone-label the 11px uppercase "Insert" prompt inside a dropzone
90
+ .ce-file-overlay dashed-accent full-surface file-drop overlay
91
+ .ce-insert-pill the round "+" affordance that opens the insert menu
92
+ .ce-insert-menu floating insert-menu card (scrolls, deep shadow)
93
+ .ce-insert-item one row in the insert menu
94
+ .ce-ghost drag ghost / dnd-kit DragOverlay card
95
+ .ce-pill outline pill (padding presets, filters)
96
+ .ce-pill.is-active solid accent pill
97
+ .ce-toggle full-radius segmented control wrapper
98
+ .ce-toggle-btn a segment; .is-active marks the selected one
99
+ .ce-toolbar rich-text toolbar row (wraps, .25rem gap)
100
+ .ce-btngroup attached button group inside a toolbar
101
+ .ce-toolbar-btn a toolbar button; .is-active = accent-tinted mark
102
+ .ce-badge-safe email-safe font badge (green tint)
103
+ .ce-badge-unsafe non-email-safe font badge (amber tint)
104
+ .ce-modal-backdrop dimmed modal backdrop
105
+ .ce-modal modal card; .ce-modal-sm/-lg size variants
106
+ .ce-modal-header / .ce-modal-body / .ce-modal-footer
107
+ .ce-preview-shell neutral stage a preview is centred on
108
+ .ce-preview-desktop mac-window chrome around a desktop preview
109
+ .ce-preview-mobile phone body around a mobile preview
110
+ .ce-toast-host fixed toast stack (bottom-right)
111
+ .ce-toast one toast; .is-success/.is-error/.is-info
112
+ ========================================================================== */
113
+
114
+ /* ---- layout -------------------------------------------------------------- */
115
+
116
+ .ce-layout {
117
+ display: grid;
118
+ grid-template-columns: 300px 1fr 300px;
119
+ gap: 1rem;
120
+ height: calc(100vh - 8rem);
121
+ min-height: 32rem;
122
+ }
123
+
124
+ .ce-layout.is-left-collapsed { grid-template-columns: 44px 1fr 300px; }
125
+
126
+ @media (max-width: 1100px) {
127
+ .ce-layout, .ce-layout.is-left-collapsed { grid-template-columns: 1fr; height: auto; }
128
+ }
129
+
130
+ .ce-rail {
131
+ background: var(--color-surface);
132
+ border: 1px solid var(--color-border);
133
+ border-radius: var(--radius, 0.375rem);
134
+ overflow: auto;
135
+ padding: 0.75rem;
136
+ min-height: 0;
137
+ }
138
+
139
+ .ce-rail-header {
140
+ position: sticky; top: -0.75rem; z-index: 1;
141
+ margin: -0.75rem -0.75rem 0.5rem;
142
+ padding: 0.6rem 0.75rem;
143
+ background: var(--color-surface);
144
+ border-bottom: 1px solid var(--color-border);
145
+ display: flex; align-items: center; justify-content: space-between; gap: 0.5rem;
146
+ font-size: 0.78rem; font-weight: 600; color: var(--color-text-muted);
147
+ }
148
+
149
+ .ce-canvas {
150
+ flex: 1 1 auto;
151
+ overflow: auto;
152
+ padding: 1.5rem;
153
+ background: var(--color-bg, var(--color-surface-2));
154
+ border: 1px solid var(--color-border);
155
+ border-radius: var(--radius, 0.375rem);
156
+ min-height: 0;
157
+ }
158
+
159
+ .ce-canvas-sheet {
160
+ margin: 0 auto;
161
+ max-width: 640px;
162
+ background: var(--color-surface);
163
+ border-radius: var(--radius, 0.375rem);
164
+ box-shadow: 0 14px 40px rgba(15, 23, 42, 0.14);
165
+ }
166
+
167
+ .dark .ce-canvas-sheet { box-shadow: 0 14px 40px rgba(0, 0, 0, 0.45); }
168
+
169
+ /* ---- blocks -------------------------------------------------------------- */
170
+
171
+ .ce-block {
172
+ position: relative;
173
+ outline: 2px solid transparent;
174
+ outline-offset: 2px;
175
+ border-radius: var(--radius, 0.375rem);
176
+ transition: outline-color 0.12s, background 0.12s;
177
+ }
178
+
179
+ .ce-block:hover { outline-color: color-mix(in srgb, var(--color-accent) 45%, transparent); }
180
+
181
+ .ce-block.is-selected { outline: 2px solid var(--color-accent); }
182
+
183
+ .ce-block-toolbar {
184
+ position: absolute; top: -40px; right: 0; z-index: 5;
185
+ display: inline-flex; align-items: center; gap: 0.125rem;
186
+ padding: 0.2rem;
187
+ background: var(--color-surface);
188
+ border: 1px solid var(--color-border);
189
+ border-radius: 11px;
190
+ box-shadow: var(--shadow-sm, 0 1px 2px rgba(15, 23, 42, 0.08));
191
+ opacity: 0;
192
+ pointer-events: none;
193
+ transition: opacity 0.12s;
194
+ }
195
+
196
+ .ce-block:hover > .ce-block-toolbar,
197
+ .ce-block:focus-within > .ce-block-toolbar,
198
+ .ce-block.is-selected > .ce-block-toolbar { opacity: 1; pointer-events: auto; }
199
+
200
+ .ce-block-toolbar-btn {
201
+ display: inline-flex; align-items: center; justify-content: center;
202
+ height: 1.5rem; min-width: 1.5rem; padding: 0 0.3rem;
203
+ border: 0; border-radius: 8px; background: transparent;
204
+ color: var(--color-text-muted); font-size: 0.8rem; line-height: 1; cursor: pointer;
205
+ }
206
+
207
+ .ce-block-toolbar-btn:hover { background: var(--color-surface-2); color: var(--color-text); }
208
+ .ce-block-toolbar-btn.is-danger:hover { background: color-mix(in srgb, var(--color-red) 14%, transparent); color: var(--color-red); }
209
+ .ce-block-toolbar-btn.is-grab { cursor: grab; }
210
+ .ce-block-toolbar-btn.is-grab:active { cursor: grabbing; }
211
+
212
+ /* ---- drop targets -------------------------------------------------------- */
213
+
214
+ .ce-dropzone {
215
+ height: 8px;
216
+ margin: 0.125rem 0;
217
+ border-radius: var(--radius, 0.375rem);
218
+ border: 1px dashed transparent;
219
+ display: flex; align-items: center; justify-content: center;
220
+ overflow: hidden;
221
+ transition: height 0.14s ease, background 0.14s, border-color 0.14s;
222
+ }
223
+
224
+ .ce-dropzone.is-drag-active {
225
+ height: 20px;
226
+ border-color: color-mix(in srgb, var(--color-accent) 40%, transparent);
227
+ background: color-mix(in srgb, var(--color-accent) 5%, transparent);
228
+ }
229
+
230
+ .ce-dropzone.is-hover {
231
+ height: 32px;
232
+ border-color: var(--color-accent);
233
+ background: color-mix(in srgb, var(--color-accent) 10%, transparent);
234
+ }
235
+
236
+ .ce-dropzone-label {
237
+ font-size: 11px; text-transform: uppercase; letter-spacing: 0.08em; font-weight: 600;
238
+ color: var(--color-accent);
239
+ opacity: 0;
240
+ transition: opacity 0.12s;
241
+ }
242
+
243
+ .ce-dropzone.is-hover .ce-dropzone-label { opacity: 1; }
244
+
245
+ .ce-file-overlay {
246
+ position: absolute; inset: 0; z-index: 20;
247
+ display: flex; align-items: center; justify-content: center;
248
+ flex-direction: column; gap: 0.4rem;
249
+ border: 2px dashed var(--color-accent);
250
+ border-radius: var(--radius, 0.375rem);
251
+ background: color-mix(in srgb, var(--color-accent) 10%, var(--color-surface));
252
+ font-size: 0.875rem; font-weight: 600; color: var(--color-accent);
253
+ }
254
+
255
+ /* ---- insert menu --------------------------------------------------------- */
256
+
257
+ .ce-insert-pill {
258
+ display: inline-flex; align-items: center; justify-content: center; gap: 0.3rem;
259
+ height: 1.6rem; padding: 0 0.7rem;
260
+ border: 1px solid var(--color-accent);
261
+ border-radius: 999px;
262
+ background: var(--color-surface);
263
+ color: var(--color-accent);
264
+ font-size: 11px; text-transform: uppercase; letter-spacing: 0.08em; font-weight: 600;
265
+ cursor: pointer;
266
+ }
267
+
268
+ .ce-insert-pill:hover { background: color-mix(in srgb, var(--color-accent) 10%, transparent); }
269
+
270
+ .ce-insert-menu {
271
+ background: var(--color-surface);
272
+ border: 1px solid var(--color-border);
273
+ border-radius: 11px;
274
+ box-shadow: 0 18px 40px -12px rgba(15, 23, 42, 0.28);
275
+ max-height: 24rem;
276
+ overflow-y: auto;
277
+ padding: 0.3rem;
278
+ min-width: 14rem;
279
+ z-index: 30;
280
+ }
281
+
282
+ .dark .ce-insert-menu { box-shadow: 0 18px 40px -12px rgba(0, 0, 0, 0.6); }
283
+
284
+ .ce-insert-item {
285
+ display: flex; align-items: center; gap: 0.5rem; width: 100%;
286
+ padding: 0.4rem 0.55rem;
287
+ border: 0; border-radius: 0.375rem; background: transparent;
288
+ color: var(--color-text); font-size: 0.875rem; text-align: left; cursor: pointer;
289
+ }
290
+
291
+ .ce-insert-item:hover { background: var(--color-surface-2); }
292
+ .ce-insert-item[aria-selected="true"] { background: color-mix(in srgb, var(--color-accent) 12%, transparent); }
293
+
294
+ .ce-ghost {
295
+ background: var(--color-surface);
296
+ border: 1px solid var(--color-accent);
297
+ border-radius: var(--radius, 0.375rem);
298
+ box-shadow: 0 18px 40px -12px rgba(15, 23, 42, 0.28);
299
+ padding: 0.5rem 0.75rem;
300
+ font-size: 0.875rem;
301
+ color: var(--color-text);
302
+ opacity: 0.95;
303
+ cursor: grabbing;
304
+ }
305
+
306
+ .dark .ce-ghost { box-shadow: 0 18px 40px -12px rgba(0, 0, 0, 0.6); }
307
+
308
+ /* ---- pills + segmented toggles ------------------------------------------- */
309
+
310
+ .ce-pill {
311
+ display: inline-flex; align-items: center; justify-content: center; gap: 0.3rem;
312
+ height: 1.75rem; padding: 0 0.7rem;
313
+ border: 1px solid var(--color-border);
314
+ border-radius: 999px;
315
+ background: var(--color-surface);
316
+ color: var(--color-text);
317
+ font-size: 0.78rem; font-weight: 600; white-space: nowrap; cursor: pointer;
318
+ transition: background 0.12s, border-color 0.12s, color 0.12s;
319
+ }
320
+
321
+ .ce-pill:hover { background: var(--color-surface-2); }
322
+
323
+ .ce-pill.is-active {
324
+ background: var(--color-accent);
325
+ border-color: var(--color-accent);
326
+ color: #fff;
327
+ }
328
+
329
+ .ce-pill.is-active:hover { background: var(--color-accent-hover, var(--color-accent)); }
330
+
331
+ .ce-pill:disabled { opacity: 0.5; cursor: not-allowed; }
332
+
333
+ .ce-toggle {
334
+ display: inline-flex; align-items: center;
335
+ padding: 2px;
336
+ border: 1px solid var(--color-border);
337
+ border-radius: 999px;
338
+ background: var(--color-surface-2);
339
+ }
340
+
341
+ .ce-toggle-btn {
342
+ display: inline-flex; align-items: center; justify-content: center; gap: 0.3rem;
343
+ height: 1.6rem; padding: 0 0.8rem;
344
+ border: 0; border-radius: 999px; background: transparent;
345
+ color: var(--color-text-muted);
346
+ font-size: 0.78rem; font-weight: 600; cursor: pointer;
347
+ transition: background 0.12s, color 0.12s;
348
+ }
349
+
350
+ .ce-toggle-btn:hover { color: var(--color-text); }
351
+
352
+ .ce-toggle-btn.is-active {
353
+ background: var(--color-surface);
354
+ color: var(--color-text);
355
+ box-shadow: var(--shadow-sm, 0 1px 2px rgba(15, 23, 42, 0.08));
356
+ }
357
+
358
+ /* ---- rich-text toolbar --------------------------------------------------- */
359
+
360
+ .ce-toolbar {
361
+ display: flex; flex-wrap: wrap; align-items: center; gap: 0.25rem;
362
+ padding: 0.35rem;
363
+ background: var(--color-surface);
364
+ border: 1px solid var(--color-border);
365
+ border-radius: var(--radius, 0.375rem);
366
+ }
367
+
368
+ .ce-btngroup { display: inline-flex; align-items: center; }
369
+
370
+ .ce-btngroup > .ce-toolbar-btn { border-radius: 0; margin-left: -1px; }
371
+ .ce-btngroup > .ce-toolbar-btn:first-child { border-top-left-radius: 0.375rem; border-bottom-left-radius: 0.375rem; margin-left: 0; }
372
+ .ce-btngroup > .ce-toolbar-btn:last-child { border-top-right-radius: 0.375rem; border-bottom-right-radius: 0.375rem; }
373
+
374
+ .ce-toolbar-btn {
375
+ display: inline-flex; align-items: center; justify-content: center; gap: 0.25rem;
376
+ height: 1.85rem; min-width: 1.85rem; padding: 0 0.45rem;
377
+ border: 1px solid var(--color-border);
378
+ border-radius: 0.375rem;
379
+ background: var(--color-surface);
380
+ color: var(--color-text);
381
+ font-size: 0.78rem; font-weight: 600; line-height: 1; cursor: pointer;
382
+ transition: background 0.12s, color 0.12s, border-color 0.12s;
383
+ }
384
+
385
+ .ce-toolbar-btn:hover { background: var(--color-surface-2); }
386
+
387
+ .ce-toolbar-btn.is-active {
388
+ background: color-mix(in srgb, var(--color-accent) 14%, transparent);
389
+ border-color: color-mix(in srgb, var(--color-accent) 45%, transparent);
390
+ color: var(--color-accent);
391
+ position: relative; z-index: 1;
392
+ }
393
+
394
+ .ce-toolbar-btn:disabled { opacity: 0.45; cursor: not-allowed; }
395
+
396
+ .ce-toolbar-sep { width: 1px; align-self: stretch; margin: 0.15rem 0.2rem; background: var(--color-border); }
397
+
398
+ /* ---- email-safe font badges ---------------------------------------------- */
399
+
400
+ .ce-badge-safe, .ce-badge-unsafe {
401
+ display: inline-flex; align-items: center; gap: 0.25rem;
402
+ padding: 0.1rem 0.45rem;
403
+ border-radius: 999px;
404
+ border: 1px solid transparent;
405
+ font-size: 0.7rem; font-weight: 600; white-space: nowrap;
406
+ }
407
+
408
+ .ce-badge-safe {
409
+ background: color-mix(in srgb, var(--color-green, #10b981) 14%, transparent);
410
+ border-color: color-mix(in srgb, var(--color-green, #10b981) 40%, transparent);
411
+ color: var(--color-green, #10b981);
412
+ }
413
+
414
+ .ce-badge-unsafe {
415
+ background: color-mix(in srgb, var(--color-yellow, #f59e0b) 16%, transparent);
416
+ border-color: color-mix(in srgb, var(--color-yellow, #f59e0b) 42%, transparent);
417
+ color: var(--color-yellow, #f59e0b);
418
+ }
419
+
420
+ /* ---- modals -------------------------------------------------------------- */
421
+
422
+ .ce-modal-backdrop {
423
+ position: fixed; inset: 0; z-index: 50;
424
+ display: flex; align-items: center; justify-content: center;
425
+ padding: 1rem;
426
+ background: rgba(15, 23, 42, 0.38);
427
+ }
428
+
429
+ .dark .ce-modal-backdrop { background: rgba(0, 0, 0, 0.6); }
430
+
431
+ .ce-modal {
432
+ display: flex; flex-direction: column;
433
+ width: 100%; max-width: 32rem; max-height: 85vh;
434
+ background: var(--color-surface);
435
+ border: 1px solid var(--color-border);
436
+ border-radius: 11px;
437
+ box-shadow: 0 18px 40px -12px rgba(15, 23, 42, 0.28);
438
+ overflow: hidden;
439
+ }
440
+
441
+ .dark .ce-modal { box-shadow: 0 18px 40px -12px rgba(0, 0, 0, 0.7); }
442
+
443
+ .ce-modal-sm { max-width: 24rem; }
444
+ .ce-modal-lg { max-width: 48rem; }
445
+
446
+ .ce-modal-header {
447
+ display: flex; align-items: center; justify-content: space-between; gap: 0.75rem;
448
+ padding: 0.75rem 1rem;
449
+ border-bottom: 1px solid var(--color-border);
450
+ font-size: 0.875rem; font-weight: 600; color: var(--color-text);
451
+ }
452
+
453
+ .ce-modal-body { padding: 1rem; overflow-y: auto; font-size: 0.875rem; color: var(--color-text); }
454
+
455
+ .ce-modal-footer {
456
+ display: flex; align-items: center; justify-content: flex-end; gap: 0.5rem;
457
+ padding: 0.75rem 1rem;
458
+ border-top: 1px solid var(--color-border);
459
+ background: var(--color-surface-2);
460
+ }
461
+
462
+ /* ---- previews ------------------------------------------------------------ */
463
+
464
+ .ce-preview-shell {
465
+ display: flex; align-items: flex-start; justify-content: center;
466
+ padding: 1.5rem;
467
+ background: var(--color-surface-2);
468
+ border-radius: 18px;
469
+ overflow: auto;
470
+ }
471
+
472
+ .ce-preview-desktop {
473
+ width: 100%; max-width: 880px;
474
+ background: var(--color-surface);
475
+ border: 1px solid var(--color-border);
476
+ border-radius: 18px;
477
+ box-shadow: 0 14px 40px rgba(15, 23, 42, 0.14);
478
+ overflow: hidden;
479
+ }
480
+
481
+ .dark .ce-preview-desktop { box-shadow: 0 14px 40px rgba(0, 0, 0, 0.45); }
482
+
483
+ .ce-preview-desktop::before {
484
+ content: "";
485
+ display: block; height: 34px;
486
+ background: var(--color-surface-2);
487
+ border-bottom: 1px solid var(--color-border);
488
+ /* three mac window dots — device chrome, intentionally literal colors */
489
+ background-image:
490
+ radial-gradient(circle 5px at 18px 17px, #ef4444 98%, transparent 100%),
491
+ radial-gradient(circle 5px at 36px 17px, #f59e0b 98%, transparent 100%),
492
+ radial-gradient(circle 5px at 54px 17px, #10b981 98%, transparent 100%);
493
+ }
494
+
495
+ .ce-preview-mobile {
496
+ width: 100%; max-width: 430px;
497
+ padding: 14px;
498
+ /* phone body: a device color, not a theme surface */
499
+ background: #202124;
500
+ border-radius: 32px;
501
+ box-shadow: 0 14px 40px rgba(15, 23, 42, 0.14);
502
+ position: relative;
503
+ }
504
+
505
+ .dark .ce-preview-mobile { box-shadow: 0 14px 40px rgba(0, 0, 0, 0.45); }
506
+
507
+ .ce-preview-mobile::before {
508
+ content: "";
509
+ position: absolute; top: 6px; left: 50%; transform: translateX(-50%);
510
+ width: 96px; height: 5px; border-radius: 999px;
511
+ background: #3a3a3a;
512
+ }
513
+
514
+ .ce-preview-mobile > * {
515
+ display: block;
516
+ background: var(--color-surface);
517
+ border-radius: 20px;
518
+ overflow: hidden;
519
+ }
520
+
521
+ /* ---- toasts -------------------------------------------------------------- */
522
+
523
+ .ce-toast-host {
524
+ position: fixed; right: 1rem; bottom: 1rem; z-index: 60;
525
+ display: flex; flex-direction: column; gap: 0.5rem;
526
+ pointer-events: none;
527
+ }
528
+
529
+ .ce-toast {
530
+ pointer-events: auto;
531
+ display: flex; align-items: center; gap: 0.5rem;
532
+ min-width: 14rem; max-width: 22rem;
533
+ padding: 0.6rem 0.8rem;
534
+ background: var(--color-surface);
535
+ border: 1px solid var(--color-border);
536
+ border-left: 3px solid var(--color-border);
537
+ border-radius: 11px;
538
+ box-shadow: 0 18px 40px -12px rgba(15, 23, 42, 0.28);
539
+ font-size: 0.875rem; color: var(--color-text);
540
+ }
541
+
542
+ .dark .ce-toast { box-shadow: 0 18px 40px -12px rgba(0, 0, 0, 0.6); }
543
+
544
+ .ce-toast.is-success { border-left-color: var(--color-green, #10b981); }
545
+ .ce-toast.is-error { border-left-color: var(--color-red); }
546
+ .ce-toast.is-info { border-left-color: var(--color-accent); }
547
+
548
+ .ce-toast-close {
549
+ margin-left: auto; border: 0; background: transparent; cursor: pointer;
550
+ color: var(--color-text-muted); font-size: 0.85rem; line-height: 1; padding: 0.1rem 0.2rem;
551
+ }
552
+
553
+ .ce-toast-close:hover { color: var(--color-text); }