@seliseblocks/mailcraft 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 (89) hide show
  1. package/DOCS.md +557 -0
  2. package/LICENSE +21 -0
  3. package/README.md +126 -0
  4. package/dist/mailcraft-editor.bundle.js +519 -0
  5. package/dist/mailcraft-editor.bundle.js.map +7 -0
  6. package/examples/templates/activate-your-account.html +54 -0
  7. package/examples/templates/back-in-stock.html +63 -0
  8. package/examples/templates/cart-left-behind.html +74 -0
  9. package/examples/templates/community-giveaway.html +81 -0
  10. package/examples/templates/frontend-futures-invite.html +69 -0
  11. package/examples/templates/give-25-get-25.html +69 -0
  12. package/examples/templates/invoice-paid.html +77 -0
  13. package/examples/templates/meet-nova-launch.html +69 -0
  14. package/examples/templates/mega-weekend-sale.html +57 -0
  15. package/examples/templates/order-confirmed.html +80 -0
  16. package/examples/templates/rate-your-headphones.html +71 -0
  17. package/examples/templates/reset-your-password.html +54 -0
  18. package/examples/templates/thankyou-promo-code.html +68 -0
  19. package/examples/templates/the-sunday-brief.html +47 -0
  20. package/examples/templates/welcome-to-your-workspace.html +62 -0
  21. package/examples/templates/your-order-shipped.html +74 -0
  22. package/examples/templates/your-password-was-changed.html +58 -0
  23. package/examples/templates/your-signin-code.html +58 -0
  24. package/examples/vanilla.html +1574 -0
  25. package/package.json +66 -0
  26. package/src/core/accent.js +194 -0
  27. package/src/core/assets.js +15 -0
  28. package/src/core/binder.js +119 -0
  29. package/src/core/blocks.js +256 -0
  30. package/src/core/css-cascade.js +117 -0
  31. package/src/core/editor-core.js +1492 -0
  32. package/src/core/export.js +58 -0
  33. package/src/core/footer.js +73 -0
  34. package/src/core/i18n/ar.js +177 -0
  35. package/src/core/i18n/bg.js +152 -0
  36. package/src/core/i18n/bn.js +176 -0
  37. package/src/core/i18n/ca.js +152 -0
  38. package/src/core/i18n/cs.js +152 -0
  39. package/src/core/i18n/da.js +152 -0
  40. package/src/core/i18n/de-CH.js +152 -0
  41. package/src/core/i18n/de.js +152 -0
  42. package/src/core/i18n/dz.js +179 -0
  43. package/src/core/i18n/el.js +152 -0
  44. package/src/core/i18n/en.js +269 -0
  45. package/src/core/i18n/es.js +152 -0
  46. package/src/core/i18n/et.js +152 -0
  47. package/src/core/i18n/fi.js +152 -0
  48. package/src/core/i18n/fr.js +152 -0
  49. package/src/core/i18n/hr.js +152 -0
  50. package/src/core/i18n/hu.js +152 -0
  51. package/src/core/i18n/index.js +83 -0
  52. package/src/core/i18n/it.js +152 -0
  53. package/src/core/i18n/lt.js +152 -0
  54. package/src/core/i18n/lv.js +152 -0
  55. package/src/core/i18n/nb.js +152 -0
  56. package/src/core/i18n/nl.js +152 -0
  57. package/src/core/i18n/pl.js +152 -0
  58. package/src/core/i18n/pt.js +152 -0
  59. package/src/core/i18n/ro.js +152 -0
  60. package/src/core/i18n/ru.js +152 -0
  61. package/src/core/i18n/sk.js +152 -0
  62. package/src/core/i18n/sl.js +152 -0
  63. package/src/core/i18n/sv.js +152 -0
  64. package/src/core/i18n/tables.js +50 -0
  65. package/src/core/i18n/tr.js +152 -0
  66. package/src/core/i18n/uk.js +152 -0
  67. package/src/core/icons.js +235 -0
  68. package/src/core/ids.js +1 -0
  69. package/src/core/import-html.js +959 -0
  70. package/src/core/layout-style.js +115 -0
  71. package/src/core/parse.js +10 -0
  72. package/src/core/placeholder.js +15 -0
  73. package/src/core/sanitize.js +141 -0
  74. package/src/core/storage-limits.js +184 -0
  75. package/src/core/storage.js +85 -0
  76. package/src/core/theme.js +1 -0
  77. package/src/core/toolbar.js +67 -0
  78. package/src/core/variables.js +11 -0
  79. package/src/create-editor.js +109 -0
  80. package/src/index.js +9 -0
  81. package/src/mailcraft-editor.js +1862 -0
  82. package/src/render/block-body.js +295 -0
  83. package/src/render/canvas.js +284 -0
  84. package/src/render/fields.js +609 -0
  85. package/src/render/focus-preserve.js +158 -0
  86. package/src/render/rte.js +212 -0
  87. package/src/render/screenshot.js +132 -0
  88. package/src/render/story.js +415 -0
  89. package/src/render/style.js +452 -0
@@ -0,0 +1,452 @@
1
+ /**
2
+ * The editor's chrome stylesheet -- the only CSS that can't be expressed as
3
+ * an inline `style` attribute: the two chrome palettes (light/dark), the RTE's
4
+ * own token set, shared component classes (segmented controls, tooltips, the
5
+ * sheet-wrap/workspace framing), keyframes, and scrollbars. Selectors kept
6
+ * scoped to `#mc` / `#mc[data-chrome="dark"]` since the Shadow DOM gives them
7
+ * their own namespace. `#mc`'s own height is `100%` here instead of `100vh`,
8
+ * since a Web Component sizes to its host element, not the viewport.
9
+ */
10
+ export const STYLE = `
11
+ @import url('https://fonts.googleapis.com/css2?family=Manrope:wght@400;500;600;700&display=swap');
12
+ :host { display: block; height: 100%; }
13
+ #mc, #mc *, #mc *::before, #mc *::after { box-sizing: border-box; }
14
+ #mc {
15
+ --ed-font: 'Manrope', 'Segoe UI Variable', 'Segoe UI', system-ui, -apple-system, BlinkMacSystemFont, sans-serif;
16
+ --ed-bg: #f5f7fa; --ed-panel: #ffffff; --ed-panel-2: #f8fafc; --ed-work: #f1f4f8;
17
+ --ed-line: rgba(15,23,42,0.09); --ed-line-2: rgba(15,23,42,0.16);
18
+ --ed-text: #172033; --ed-muted: #667085; --ed-faint: #98a2b3;
19
+ --ed-panel-label: #344054; --ed-panel-value: #475467; --ed-panel-meta: #7b8799;
20
+ --ed-panel-label-size: 12px; --ed-panel-value-size: 11.5px; --ed-panel-meta-size: 9.5px;
21
+ --ed-accent: #0065b3; --ed-accent-strong: #00538f; --ed-accent-ink: #ffffff; --ed-soft: rgba(0,101,179,0.09);
22
+ --ed-accent-tint: #33a3e8; --ed-glow: rgba(0,101,179,0.28); --ed-select: rgba(0,101,179,0.22);
23
+ /* The accent as it must read on the email sheet, which is a white page in
24
+ both themes -- so this family does NOT flip with the chrome (see the dark
25
+ palette below, where it is deliberately not overridden). */
26
+ --ed-accent-sheet: #0065b3; --ed-accent-sheet-strong: #004f8c; --ed-accent-sheet-ink: #ffffff;
27
+ --ed-accent-sheet-line: rgba(0,101,179,0.5);
28
+ --ed-grid: rgba(71,85,105,0.10);
29
+ --ed-danger: #dc4c53; --ed-danger-soft: rgba(220,76,83,0.12);
30
+ --ed-success: #1a9f6b; --ed-success-soft: rgba(26,159,107,0.12);
31
+ --ed-radius: 10px; --ed-radius-sm: 6px;
32
+ --ed-shadow-sm: 0 1px 2px rgba(15,23,42,0.06);
33
+ --ed-shadow-md: 0 8px 24px rgba(15,23,42,0.12);
34
+ --ed-shadow-lg: 0 20px 48px rgba(15,23,42,0.16);
35
+ --rte-bg: #ffffff; --rte-panel: #f8fafc; --rte-input: #f1f5f9;
36
+ --rte-border: rgba(15,23,42,0.12); --rte-text: #172033; --rte-muted: #64748b;
37
+ --rte-shadow: 0 16px 42px rgba(15,23,42,0.18); --rte-tip-bg: #172033; --rte-tip-text: #ffffff;
38
+ }
39
+ #mc[data-chrome="dark"] {
40
+ --ed-bg: #0b1120; --ed-panel: #111827; --ed-panel-2: #182235; --ed-work: #0d1524;
41
+ --ed-line: rgba(226,232,240,0.10); --ed-line-2: rgba(226,232,240,0.18);
42
+ --ed-text: #f1f5f9; --ed-muted: #a8b3c4; --ed-faint: #718096;
43
+ --ed-panel-label: #e2e8f0; --ed-panel-value: #c0cad8; --ed-panel-meta: #8d9aae;
44
+ --ed-accent: #58a8e3; --ed-accent-strong: #8fc4ec; --ed-accent-ink: #08111f; --ed-soft: rgba(88,168,227,0.13);
45
+ --ed-accent-tint: #8fcdf2; --ed-glow: rgba(88,168,227,0.28); --ed-select: rgba(88,168,227,0.3);
46
+ --ed-grid: rgba(148,163,184,0.08);
47
+ --ed-danger: #f2777c; --ed-danger-soft: rgba(242,119,124,0.14);
48
+ --ed-success: #4ade95; --ed-success-soft: rgba(74,222,149,0.14);
49
+ --ed-shadow-sm: 0 1px 2px rgba(0,0,0,0.24);
50
+ --ed-shadow-md: 0 8px 24px rgba(0,0,0,0.34);
51
+ --ed-shadow-lg: 0 20px 48px rgba(0,0,0,0.46);
52
+ --rte-bg: #111827; --rte-panel: #182235; --rte-input: #1e293b;
53
+ --rte-border: rgba(226,232,240,0.14); --rte-text: #f8fafc; --rte-muted: #a8b3c4;
54
+ --rte-shadow: 0 18px 46px rgba(0,0,0,0.44); --rte-tip-bg: #e2e8f0; --rte-tip-text: #111827;
55
+ }
56
+ #mc button, #mc input, #mc select, #mc textarea { border-radius: 8px; }
57
+ #mc button { font-family: var(--ed-font); -webkit-tap-highlight-color: transparent; }
58
+ #mc button:not(:disabled) { transition-duration: 150ms !important; transition-timing-function: ease !important; }
59
+ #mc button:disabled { cursor: default !important; opacity: 0.45; }
60
+ #mc input, #mc select, #mc textarea { transition: border-color 150ms ease, background-color 150ms ease, box-shadow 150ms ease; }
61
+ /* Custom stepper buttons already cover increment/decrement -- the browser's
62
+ own spinner arrows are redundant chrome-on-chrome and look inconsistent
63
+ across browsers, so they're hidden everywhere a number input appears. */
64
+ #mc input[type="number"] { -moz-appearance: textfield; }
65
+ #mc input[type="number"]::-webkit-inner-spin-button,
66
+ #mc input[type="number"]::-webkit-outer-spin-button { -webkit-appearance: none; margin: 0; }
67
+ /* Native <select> chrome (system font, OS-drawn arrow) doesn't match the rest
68
+ of the UI -- swap in a plain custom arrow and let everything else (font,
69
+ color, border, radius) keep flowing from the same inline styles as every
70
+ other field. */
71
+ /* !important is load-bearing here, not decorative: every select's inline
72
+ style sets background: var(--ed-panel-2) (a shorthand), and the CSS
73
+ background shorthand implicitly resets background-image to none even
74
+ when the declaration never mentions an image -- so without !important
75
+ the inline color wins and silently erases this chevron. */
76
+ #mc select {
77
+ appearance: none; -webkit-appearance: none; -moz-appearance: none;
78
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23667085' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E") !important;
79
+ background-repeat: no-repeat !important; background-position: right 7px center !important; background-size: 11px !important;
80
+ padding-right: 26px !important;
81
+ }
82
+ #mc[data-chrome="dark"] select {
83
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23a8b3c4' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E") !important;
84
+ }
85
+ #mc input[type="color"] { border-radius: var(--ed-radius-sm); overflow: hidden; }
86
+ #mc input[type="color"]::-webkit-color-swatch { border: 0; }
87
+ #mc input[type="color"]::-webkit-color-swatch-wrapper { padding: 0; }
88
+ #mc ::-webkit-scrollbar { width: 9px; height: 9px; }
89
+ #mc ::-webkit-scrollbar-thumb { background: var(--ed-line-2); border-radius: 999px; }
90
+ #mc ::-webkit-scrollbar-track { background: transparent; }
91
+ #mc :focus-visible { outline: 2px solid var(--ed-accent); outline-offset: 2px; }
92
+ #mc ::selection { background: var(--ed-select); }
93
+ #mc a { color: var(--ed-accent); }
94
+ #mc a:hover { color: var(--ed-accent); opacity: 0.72; }
95
+ @keyframes mcIn { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }
96
+ @keyframes mcFade { from { opacity: 0; } to { opacity: 1; } }
97
+ @keyframes mcPulse { 0%, 100% { opacity: 0.5; } 50% { opacity: 1; } }
98
+ @keyframes mcStoryShimmer { from { background-position: 140% 0; } to { background-position: -40% 0; } }
99
+
100
+ /*
101
+ * Hover/selection outlines on canvas rows and blocks. Re-rendering on every
102
+ * mouseover to toggle these would rebuild the whole canvas on every pixel of
103
+ * mouse movement, which is what made drag/hover feel un-smooth -- these are
104
+ * real CSS instead, same visual result, zero JS.
105
+ */
106
+ .mc-row-el { outline: 1px solid transparent; transition: outline-color 0.12s; }
107
+ .mc-row-el.is-selected { outline: 2px solid var(--ed-accent-sheet); }
108
+ .mc-row-el:not(.is-selected):hover { outline: 1px dashed var(--ed-faint); }
109
+ .mc-block-el { outline: 1px solid transparent; outline-offset: 1px; transition: outline-color 0.12s; }
110
+ .mc-block-el.is-selected { outline: 2px solid var(--ed-accent-sheet); }
111
+ .mc-block-el:not(.is-selected):hover { outline: 1px dashed var(--ed-accent-sheet); }
112
+ /*
113
+ * The grab strip runs the row's whole left edge (see render/canvas.js); the
114
+ * visible part is a round badge straddling the selection border. It takes
115
+ * --ed-accent-sheet, not --ed-accent, for the same reason as the canvas
116
+ * toolbars: the email sheet stays a light page in both chrome themes, so the
117
+ * accent here is the one fitted against white (core/accent.js).
118
+ */
119
+ .mc-row-grip { display: none; }
120
+ .mc-row-el:hover .mc-row-grip, .mc-row-el.is-selected .mc-row-grip { display: flex; }
121
+ /* The badge's fill lives here, not inline on the element: an inline
122
+ background would outrank the hover rule below and the badge would never
123
+ light up under the cursor. */
124
+ .mc-row-grip-handle { background: var(--ed-accent-sheet); color: var(--ed-accent-sheet-ink); box-shadow: 0 2px 6px rgba(15,23,42,0.3); transition: background 0.14s ease; }
125
+ .mc-row-grip:hover .mc-row-grip-handle { background: var(--ed-accent-sheet-strong); }
126
+ /* The drag slider (render/fields.js isSlider). The track's filled/empty
127
+ split is painted inline as a gradient that follows the thumb; only the
128
+ chrome that CSS pseudo-elements own lives here. */
129
+ .mc-slider { -webkit-appearance: none; appearance: none; height: 4px; border-radius: 2px; background: var(--ed-line-2); outline: none; cursor: pointer; }
130
+ .mc-slider::-webkit-slider-thumb { -webkit-appearance: none; appearance: none; width: 16px; height: 16px; border-radius: 50%; background: var(--ed-accent); border: 0; box-shadow: 0 1px 4px rgba(15,23,42,0.3); cursor: grab; }
131
+ .mc-slider::-moz-range-thumb { width: 16px; height: 16px; border-radius: 50%; background: var(--ed-accent); border: 0; box-shadow: 0 1px 4px rgba(15,23,42,0.3); cursor: grab; }
132
+ .mc-slider:focus-visible { box-shadow: 0 0 0 3px var(--ed-soft); }
133
+ #mc .mc-slider-field { padding-bottom: 8px; }
134
+ /* A section bar that opens a tab sits flush under the tabbar's own
135
+ border-bottom -- its border-top would double that hairline. */
136
+ .mc-section-bar:first-child { border-top: 0; }
137
+ .mc-rte { overflow: visible; }
138
+
139
+ /* Attribution strip (mailcraft-editor.js buildFooter). Sits on the panel-2
140
+ surface with the panel's own hairline above it, so it reads as chrome
141
+ rather than as content; the link only takes the accent on hover, since a
142
+ permanently colored line would compete with the canvas. */
143
+ .mc-footer-link { cursor: pointer; transition: color 0.14s ease; }
144
+ .mc-footer-link:hover { color: var(--ed-accent); text-decoration: underline; }
145
+
146
+ /* Brand mark -- the small gradient envelope glyph in the header. */
147
+ .mc-brand-mark {
148
+ display: flex; align-items: center; justify-content: center; flex: none;
149
+ width: 26px; height: 26px; border-radius: 8px;
150
+ background: linear-gradient(145deg, var(--ed-accent-tint), var(--ed-accent) 55%, var(--ed-accent-strong));
151
+ box-shadow: 0 5px 14px var(--ed-glow);
152
+ color: #ffffff;
153
+ }
154
+
155
+ /*
156
+ * Shared segmented-control shell -- one bordered/backed track that owns the
157
+ * only visible border, with borderless children floating inside it. Avoids
158
+ * ever doubling a border: each button gets its own smaller inner radius, the
159
+ * wrapper's outer radius is larger, and the gap/padding reveal the track
160
+ * background between items instead of adjoining per-button borders.
161
+ */
162
+ .mc-segment { display: flex; align-items: center; gap: 3px; padding: 3px; border: 1px solid var(--ed-line); border-radius: var(--ed-radius); background: var(--ed-panel); }
163
+ .mc-segment button { border: 0; border-radius: 7px; cursor: pointer; transition: background 0.16s, color 0.16s; }
164
+ .mc-segment button:hover:not(:disabled):not([aria-pressed="true"]) { background: var(--ed-soft) !important; color: var(--ed-accent) !important; }
165
+ .mc-segment button[aria-pressed="true"]:hover { background: var(--ed-accent-strong) !important; color: var(--ed-accent-ink) !important; }
166
+ .mc-segment button:disabled { cursor: default; opacity: 0.42; }
167
+
168
+ /*
169
+ * Shared CSS-only tooltip -- a small bubble shown on hover/focus-visible of
170
+ * the positioned parent that owns it, no JS involved in showing/hiding it.
171
+ * dir="down" (below the control, e.g. tabs/segments) or dir="up" (above,
172
+ * e.g. the RTE toolbar) controls which side it renders on.
173
+ */
174
+ .mc-tooltip {
175
+ position: absolute; left: 50%; transform: translate(-50%, -2px); z-index: 100;
176
+ padding: 6px 8px; border-radius: 7px; background: var(--rte-tip-bg); color: var(--rte-tip-text);
177
+ font: 500 10.5px/1.2 var(--ed-font); white-space: nowrap;
178
+ opacity: 0; visibility: hidden; pointer-events: none;
179
+ box-shadow: 0 7px 20px rgba(15,23,42,0.22);
180
+ transition: opacity 0.12s ease, transform 0.12s ease, visibility 0s linear 0.12s;
181
+ }
182
+ .mc-tooltip-down { top: calc(100% + 7px); }
183
+ .mc-tooltip-down::before { content: ''; position: absolute; left: 50%; top: -4px; width: 8px; height: 8px; background: var(--rte-tip-bg); transform: translateX(-50%) rotate(45deg); }
184
+ .mc-tooltip-up { bottom: calc(100% + 7px); }
185
+ .mc-tooltip-up::before { content: ''; position: absolute; left: 50%; bottom: -4px; width: 8px; height: 8px; background: var(--rte-tip-bg); transform: translateX(-50%) rotate(45deg); }
186
+ [data-tip]:hover > .mc-tooltip, [data-tip]:focus-visible > .mc-tooltip {
187
+ opacity: 1; visibility: visible; transform: translate(-50%, 0); transition-delay: 0s;
188
+ }
189
+
190
+ @media (prefers-reduced-motion: reduce) {
191
+ #mc *, #mc *::before, #mc *::after { animation-duration: 0.01ms !important; animation-iteration-count: 1 !important; transition-duration: 0.01ms !important; }
192
+ }
193
+
194
+ /* Dotted-grid workspace background, shared by the canvas and the preview body. */
195
+ .mc-workspace, .mc-preview-body {
196
+ background-color: var(--ed-work);
197
+ background-image: radial-gradient(circle, var(--ed-grid) 1px, transparent 1.2px);
198
+ background-size: 22px 22px;
199
+ }
200
+
201
+ /*
202
+ * The email sheet gets exactly one visible frame. .mc-sheet-wrap is a pure
203
+ * positioning element (zero padding/border/background/box-shadow) whose only
204
+ * contribution is a drop-shadow filter that hugs the sheet's own silhouette
205
+ * rather than boxing it a second time; the sheet itself ([data-mc-sheet])
206
+ * carries the one real border/radius/shadow, scoped per context since the
207
+ * canvas and the preview modal want slightly different treatments.
208
+ */
209
+ /* No 'filter' here: a filter on the wrap forces the browser to re-rasterize
210
+ the whole email document (and re-run the blur) on every repaint inside it --
211
+ selection, hover and typing all stutter on long templates. The sheet's own
212
+ box-shadow below provides the depth at per-element paint cost. */
213
+ .mc-sheet-wrap { padding: 0; border: 0; border-radius: 0; background: transparent; box-shadow: none; }
214
+ .mc-workspace [data-mc-sheet="1"] { border: 1px solid var(--ed-line); border-radius: 6px; box-shadow: 0 2px 5px rgba(15,23,42,0.05), 0 14px 38px rgba(15,23,42,0.10); }
215
+ #mc[data-chrome="dark"] .mc-workspace [data-mc-sheet="1"] { border-color: rgba(148,163,184,0.4); box-shadow: 0 0 0 1px rgba(15,23,42,0.4), 0 22px 58px rgba(0,0,0,0.36); }
216
+ .mc-preview-body [data-mc-sheet="1"] { border: 1px solid var(--ed-line-2); border-radius: 8px; box-shadow: 0 18px 50px rgba(15,23,42,0.13); }
217
+ #mc[data-chrome="dark"] .mc-preview-body [data-mc-sheet="1"] { border-color: rgba(148,163,184,0.4); box-shadow: 0 22px 58px rgba(0,0,0,0.4); }
218
+
219
+ /* Final component polish mirrored from the approved standalone editor. */
220
+ #mc { --ed-success: #20a779; --ed-danger: #e05766; }
221
+ #mc ::-webkit-scrollbar { width: 8px; height: 8px; }
222
+ #mc :focus-visible { outline: 3px solid var(--ed-soft); outline-offset: 1px; }
223
+ #mc button, #mc input, #mc textarea, #mc select { border-radius: 8px !important; }
224
+ #mc button { transition: transform .18s ease, box-shadow .18s ease, border-color .18s ease, background .18s ease, color .18s ease, filter .18s ease !important; }
225
+ #mc button:not(:disabled):active { transform: translateY(1px); }
226
+ #mc button:disabled { opacity: .42; cursor: not-allowed !important; }
227
+ #mc input, #mc textarea, #mc select { box-shadow: inset 0 0 0 1px transparent; transition: border-color .18s ease, box-shadow .18s ease, background .18s ease !important; }
228
+ #mc input:focus, #mc textarea:focus, #mc select:focus { border-color: var(--ed-accent) !important; box-shadow: 0 0 0 3px var(--ed-soft); outline: none; }
229
+ /* The stepper's value input is a bare number inside the pill -- the generic
230
+ focus ring above drew a floating box around the digits mid-control; the
231
+ pill itself carries the focus treatment instead (render/fields.js). */
232
+ #mc .mc-stepper-input:focus { border-color: transparent !important; box-shadow: none; }
233
+ #mc [data-rte-control="1"] { position: relative; overflow: visible !important; border-radius: 5px !important; transition: background .14s ease, color .14s ease, transform .14s ease !important; }
234
+ #mc [data-rte-control="1"][data-active="false"]:hover { background: var(--ed-soft) !important; color: var(--ed-accent) !important; }
235
+ #mc [data-rte-control="1"]:active { transform: translateY(1px); }
236
+ #mc [data-rte-picker="1"] { transition: box-shadow .14s ease, background .14s ease; }
237
+ #mc [data-rte-picker="1"]:hover { box-shadow: inset 0 0 0 1px var(--ed-accent), 0 0 0 3px var(--ed-soft) !important; }
238
+ #mc .mc-rte-tooltip { position: absolute; left: 50%; bottom: calc(100% + 7px); transform: translate(-50%, 3px); z-index: 120; padding: 5px 7px; border-radius: 5px; background: var(--rte-tip-bg); color: var(--rte-tip-text); font: 500 10px/1.2 var(--ed-font); letter-spacing: 0; white-space: nowrap; opacity: 0; visibility: hidden; pointer-events: none; box-shadow: 0 7px 18px rgba(15,23,42,.20); transition: opacity .12s ease, transform .12s ease, visibility 0s linear .12s; }
239
+ #mc .mc-rte-tooltip::after { content: ''; position: absolute; left: 50%; bottom: -3px; width: 7px; height: 7px; background: var(--rte-tip-bg); transform: translateX(-50%) rotate(45deg); }
240
+ #mc [data-rte-control="1"]:hover .mc-rte-tooltip, #mc [data-rte-control="1"]:focus-visible .mc-rte-tooltip, #mc [data-rte-picker="1"]:hover .mc-rte-tooltip, #mc [data-rte-picker="1"]:focus-within .mc-rte-tooltip { opacity: 1; visibility: visible; transform: translate(-50%, 0); transition-delay: 0s; }
241
+ #mc [data-rte-picker="1"], #mc [data-rte-select="1"] { border-radius: 5px !important; }
242
+ #mc [data-rte-root="1"] button, #mc [data-rte-root="1"] input, #mc [data-rte-root="1"] select { border-radius: 5px !important; }
243
+ #mc [data-rte-select="1"] select:hover, #mc [data-rte-select="1"]:focus-within select { border-color: var(--ed-accent) !important; box-shadow: 0 0 0 3px var(--ed-soft); }
244
+ #mc [data-rte-select="1"] select { background-image: none !important; padding-right: 24px !important; }
245
+ #mc .mc-shell { border-color: var(--ed-line) !important; border-radius: 14px; grid-template-rows: 62px 1fr !important; box-shadow: 0 1px 2px rgba(15,23,42,.04), 0 16px 44px rgba(15,23,42,.10) !important; }
246
+
247
+ /* The header row is pinned above with !important, so a shell built without a
248
+ header has to say so here too -- otherwise the canvas lands in a 62px track
249
+ and the editor renders into a sliver. */
250
+ #mc .mc-shell.mc-no-header { grid-template-rows: 1fr !important; }
251
+ /* Opaque, no backdrop-filter: the header sits over the scrolling canvas, so a
252
+ backdrop blur re-composites every scroll frame -- visible scroll jank. */
253
+ #mc .mc-header { padding: 0 18px !important; background: var(--ed-panel) !important; }
254
+ #mc .mc-brand-mark { width: 30px !important; height: 30px !important; border-radius: 9px; background: linear-gradient(145deg, var(--ed-accent-tint), var(--ed-accent) 56%, var(--ed-accent-strong)) !important; box-shadow: 0 7px 18px var(--ed-glow); }
255
+ #mc .mc-brand-mark svg { width: 20px; height: 20px; display: block; color: #fff; }
256
+ #mc .mc-brand-name { font-family: var(--ed-font) !important; font-size: 17px !important; letter-spacing: -.03em !important; }
257
+ #mc .mc-header button { font-family: var(--ed-font) !important; font-size: 10.5px !important; font-weight: 600; letter-spacing: .015em !important; text-transform: none !important; }
258
+ #mc .mc-header > div { border-radius: 10px; }
259
+ #mc .mc-segment { gap: 3px !important; padding: 3px !important; border: 0 !important; border-radius: 10px; background: var(--ed-panel-2) !important; }
260
+ #mc .mc-segment button { min-width: 62px; height: 26px; padding: 0 10px !important; border: 0 !important; border-radius: 7px !important; font-family: var(--ed-font) !important; font-size: 10px !important; font-weight: 600; letter-spacing: .01em !important; text-transform: none !important; }
261
+ #mc .mc-field-segment { width: 100%; box-shadow: none; }
262
+ #mc .mc-field-segment button { min-width: 0; flex: 1; height: 28px; }
263
+ #mc .mc-device-segment { position: relative; z-index: 40; overflow: visible !important; }
264
+ #mc .mc-device-segment button { position: relative; min-width: 38px; width: 38px; padding: 0 !important; display: flex; align-items: center; justify-content: center; overflow: visible !important; }
265
+ #mc .mc-device-segment button:hover, #mc .mc-device-segment button:focus-visible { z-index: 50; }
266
+ #mc .mc-device-segment button svg { width: 15px; height: 15px; display: block; }
267
+ #mc .mc-layout { grid-template-columns: minmax(0,1fr) 360px !important; }
268
+ #mc .mc-canvas-stage { padding: 28px 40px 150px !important; }
269
+ #mc .mc-workspace [data-mc-sheet="1"] { border: 1px solid rgba(15,23,42,.10); border-radius: 4px; background: #ffffff; box-shadow: 0 2px 5px rgba(15,23,42,.05), 0 14px 38px rgba(15,23,42,.10) !important; }
270
+ #mc[data-chrome="dark"] .mc-workspace [data-mc-sheet="1"] { border-color: rgba(148,163,184,.46); box-shadow: 0 0 0 1px rgba(15,23,42,.48), 0 22px 58px rgba(0,0,0,.38) !important; }
271
+ #mc .mc-preview-body [data-mc-sheet="1"] { border: 1px solid rgba(15,23,42,.12); border-radius: 10px; background: #ffffff; box-shadow: 0 18px 50px rgba(15,23,42,.13) !important; }
272
+ #mc[data-chrome="dark"] .mc-preview-body [data-mc-sheet="1"] { border-color: rgba(203,213,225,.42); box-shadow: 0 0 0 1px rgba(15,23,42,.65), 0 24px 65px rgba(0,0,0,.40) !important; }
273
+ #mc .mc-inspector { box-shadow: -10px 0 30px rgba(42,47,77,.04); color: var(--ed-panel-label); }
274
+ #mc .mc-tabbar { grid-auto-flow: column; grid-auto-columns: minmax(0, 1fr); padding: 7px 8px 0; gap: 3px; border-bottom: 0 !important; background: var(--ed-panel) !important; overflow: visible; position: relative; z-index: 12; }
275
+ #mc .mc-tabbar button { position: relative; min-width: 0; height: 39px; border-radius: 8px 8px 0 0 !important; padding: 0 !important; display: flex; align-items: center; justify-content: center; font-family: var(--ed-font) !important; }
276
+ #mc .mc-tabbar button svg { width: 16px; height: 16px; display: block; }
277
+ #mc .mc-tab-surface { width: 100%; min-width: 0; background: var(--ed-panel); color: var(--ed-text); }
278
+ #mc .mc-inspector .mc-section-label {
279
+ font-size: 10px !important; line-height: 1.2 !important; font-weight: 700 !important;
280
+ letter-spacing: .07em !important; color: var(--ed-muted) !important;
281
+ }
282
+ #mc .mc-inspector .mc-section-body { padding: 12px 14px 16px !important; }
283
+ #mc .mc-inspector .mc-section-body > :first-child { margin-top: 0 !important; }
284
+ #mc .mc-field-list { display: grid; align-content: start; gap: 12px; }
285
+ #mc .mc-field-list .mc-field { min-width: 0; }
286
+ #mc .mc-field-list .mc-field-row {
287
+ display: grid !important; grid-template-columns: minmax(0, 1fr) 168px;
288
+ align-items: center; column-gap: 14px; min-height: 32px;
289
+ }
290
+ #mc .mc-field-list .mc-field-row > .mc-field-label { min-width: 0; }
291
+ #mc .mc-field-list .mc-field-row > .mc-field-control {
292
+ width: 100% !important; min-width: 0; box-sizing: border-box;
293
+ }
294
+ #mc .mc-field-list .mc-field-row > .mc-switch { justify-self: end; }
295
+ #mc .mc-field-list .mc-color-control .mc-stepper-input { width: auto !important; flex: 1; min-width: 0; }
296
+ #mc .mc-field-list .mc-field-label {
297
+ font-family: var(--ed-font) !important; font-size: var(--ed-panel-label-size) !important;
298
+ line-height: 1.4 !important; font-weight: 500 !important; color: var(--ed-panel-label) !important;
299
+ }
300
+ #mc .mc-field-list input:not([type="range"]):not([type="color"]),
301
+ #mc .mc-field-list textarea, #mc .mc-field-list select,
302
+ #mc .mc-field-list .mc-field-segment button {
303
+ font-size: var(--ed-panel-value-size) !important; line-height: 1.4 !important;
304
+ }
305
+ #mc .mc-field-list input:not([type="range"]):not([type="color"]),
306
+ #mc .mc-field-list textarea, #mc .mc-field-list select,
307
+ #mc .mc-field-list .mc-field-segment button[aria-pressed="false"] {
308
+ color: var(--ed-panel-value) !important;
309
+ }
310
+ #mc .mc-field-list .mc-stepper span {
311
+ font-size: var(--ed-panel-value-size) !important; color: var(--ed-panel-value) !important;
312
+ }
313
+ #mc .mc-field-list input:not([type="range"]):not([type="color"]):not(.mc-stepper-input),
314
+ #mc .mc-field-list select { min-height: 32px; }
315
+ #mc .mc-layer-tree { align-content: start; padding-top: 10px !important; }
316
+ #mc .mc-tree-section { min-width: 0; border-bottom: 1px solid var(--ed-line); padding-bottom: 13px !important; }
317
+ #mc .mc-tree-section.is-selected { box-shadow: none; }
318
+ #mc .mc-tree-section + .mc-tree-section { padding-top: 13px !important; }
319
+ #mc .mc-tree-section:last-child { border-bottom: 0; padding-bottom: 0 !important; }
320
+ #mc .mc-tree-section-node { min-height: 34px; padding: 5px 6px !important; }
321
+ #mc .mc-tree-column-node { min-height: 32px !important; padding-block: 4px !important; }
322
+ #mc .mc-tree-block-node { min-height: 32px; padding-block: 4px !important; }
323
+ #mc .mc-tree-children {
324
+ position: relative; display: grid; gap: 2px; margin-inline-start: 18px;
325
+ padding-inline-start: 18px;
326
+ }
327
+ #mc .mc-tree-columns { margin-top: 5px; }
328
+ #mc .mc-tree-blocks { margin-top: 2px; margin-bottom: 5px; }
329
+ #mc .mc-tree-branch, #mc .mc-tree-children > .mc-tree-node { position: relative; min-width: 0; }
330
+ #mc .mc-tree-branch::before, #mc .mc-tree-children > .mc-tree-node::before {
331
+ content: ''; position: absolute; inset-inline-start: -18px; top: 16px;
332
+ width: 17px; border-top: 1px solid var(--ed-line-2);
333
+ }
334
+ #mc .mc-tree-branch::after, #mc .mc-tree-children > .mc-tree-node::after {
335
+ content: ''; position: absolute; inset-inline-start: -18px; top: -3px; bottom: -3px;
336
+ border-inline-start: 1px solid var(--ed-line-2);
337
+ }
338
+ #mc .mc-tree-branch:last-child::after, #mc .mc-tree-children > .mc-tree-node:last-child::after {
339
+ bottom: auto; height: 19px;
340
+ }
341
+ #mc .mc-tree-node { isolation: isolate; }
342
+ #mc .mc-tree-block-node > span:nth-child(2) { min-width: 0; }
343
+ #mc .mc-layer-tree .mc-tree-title {
344
+ font-size: var(--ed-panel-label-size) !important; line-height: 1.35 !important;
345
+ font-weight: 500 !important; color: var(--ed-panel-label) !important;
346
+ }
347
+ #mc .mc-layer-tree .mc-tree-section-node .mc-tree-title { font-weight: 600 !important; }
348
+ #mc .mc-layer-tree .mc-tree-column-node .mc-tree-title {
349
+ font-size: var(--ed-panel-value-size) !important; color: var(--ed-panel-value) !important;
350
+ }
351
+ #mc .mc-layer-tree .mc-tree-count,
352
+ #mc .mc-layer-tree .mc-tree-type,
353
+ #mc .mc-layer-tree .mc-tree-index {
354
+ font-size: var(--ed-panel-meta-size) !important; line-height: 1.3 !important;
355
+ font-weight: 600 !important; color: var(--ed-panel-meta) !important;
356
+ }
357
+ #mc .mc-layer-tree .mc-tree-type { letter-spacing: .035em !important; }
358
+ #mc .mc-layer-tree .mc-tree-section.is-selected .mc-tree-index {
359
+ color: var(--ed-accent-ink) !important;
360
+ }
361
+ #mc .mc-tab-tooltip { position: absolute; top: calc(100% + 7px); left: 50%; transform: translate(-50%, -2px); z-index: 100; padding: 6px 8px; border-radius: 7px; background: #172033; color: #ffffff; font: 500 10.5px/1.2 var(--ed-font); letter-spacing: 0; text-transform: none; white-space: nowrap; opacity: 0; visibility: hidden; pointer-events: none; box-shadow: 0 7px 20px rgba(15,23,42,.22); transition: opacity .12s ease, transform .12s ease, visibility 0s linear .12s; }
362
+ #mc .mc-tab-tooltip::before { content: ''; position: absolute; left: 50%; top: -4px; width: 8px; height: 8px; background: #172033; transform: translateX(-50%) rotate(45deg); }
363
+ #mc [data-tip]:hover .mc-tab-tooltip, #mc [data-tip]:focus-visible .mc-tab-tooltip { opacity: 1; visibility: visible; transform: translate(-50%, 0); transition-delay: 0s; }
364
+ #mc[data-chrome="dark"] .mc-tab-tooltip, #mc[data-chrome="dark"] .mc-tab-tooltip::before { background: #e2e8f0; color: #111827; }
365
+ /* End-aligned tooltip for controls hugging a right edge: hidden tooltips are
366
+ still laid out (visibility, not display), so a centered bubble poking past
367
+ the inspector's edge grew a horizontal scrollbar under the panel. */
368
+ #mc .mc-tooltip-end { left: auto; right: 0; transform: translate(0, -2px); }
369
+ #mc .mc-tooltip-end::before { left: auto; right: 11px; transform: rotate(45deg); }
370
+ #mc [data-tip]:hover > .mc-tooltip-end, #mc [data-tip]:focus-visible > .mc-tooltip-end { transform: translate(0, 0); }
371
+ /* No backdrop-filter: the dock floats over the scrolling canvas (every frame
372
+ would re-blur) and its background is opaque anyway, so the blur never shows. */
373
+ #mc .mc-zoom { border-color: var(--ed-line) !important; border-radius: 12px; box-shadow: 0 10px 30px rgba(36,41,70,.15) !important; }
374
+ #mc .mc-inspector [draggable="true"] { border-radius: 12px; overflow: hidden; }
375
+ #mc .mc-shortcuts { border: 0 !important; border-radius: 12px; padding: 14px !important; background: var(--ed-panel-2); box-shadow: inset 0 0 0 1px var(--ed-line); }
376
+ /* Dim only, no backdrop blur: blurring the whole editor behind a modal is the
377
+ single most expensive paint the chrome can ask for on weak GPUs. */
378
+ #mc .mc-modal-backdrop { background: rgba(14,16,27,.62) !important; }
379
+ #mc .mc-modal { border-color: var(--ed-line) !important; border-radius: 18px; overflow: hidden; box-shadow: 0 28px 80px rgba(10,13,28,.30) !important; }
380
+ #mc .mc-fullscreen-panel { border-radius: 14px; overflow: hidden; }
381
+ #mc .mc-modal button, #mc .mc-fullscreen-panel button { font-family: var(--ed-font) !important; font-size: 10.5px !important; font-weight: 600 !important; letter-spacing: .01em !important; text-transform: none !important; }
382
+ #mc .mc-icon-label { display: inline-flex !important; align-items: center; justify-content: center; gap: 6px; }
383
+ #mc .mc-icon-label svg, #mc .mc-icon-button svg { width: 14px; height: 14px; display: block; flex: none; }
384
+ #mc .mc-icon-button { display: inline-flex !important; align-items: center; justify-content: center; }
385
+ #mc .mc-code-panel { grid-template-rows: 58px minmax(0,1fr) auto !important; background: var(--ed-work) !important; }
386
+ #mc .mc-code-toolbar, #mc .mc-preview-toolbar { position: relative; z-index: 45; overflow: visible !important; padding: 0 16px !important; background: var(--ed-panel) !important; box-shadow: 0 1px 0 var(--ed-line); }
387
+ #mc .mc-code-badge { width: 32px; height: 32px; border-radius: 9px; display: flex !important; align-items: center; justify-content: center; color: var(--ed-accent); background: var(--ed-soft); }
388
+ #mc .mc-code-heading { font-family: var(--ed-font) !important; font-size: 14px !important; font-weight: 600 !important; letter-spacing: -.01em !important; }
389
+ #mc .mc-code-kicker, #mc .mc-preview-kicker { font-family: var(--ed-font) !important; font-size: 9.5px !important; font-weight: 500; letter-spacing: .025em !important; text-transform: none !important; }
390
+ #mc .mc-code-split { gap: 12px; padding: 12px; background: var(--ed-work); }
391
+ #mc .mc-code-source, #mc .mc-code-preview { border: 1px solid var(--ed-line) !important; border-radius: 12px; overflow: hidden; background: var(--ed-panel) !important; }
392
+ #mc .mc-pane-label { padding: 8px 13px !important; background: var(--ed-panel-2) !important; font-size: 9.5px !important; letter-spacing: .025em !important; text-transform: none !important; }
393
+ #mc .mc-code-preview-body { padding: 14px !important; background: var(--ed-work); }
394
+ #mc .mc-code-frame { border: 0 !important; border-radius: 9px; box-shadow: 0 5px 20px rgba(15,23,42,.10) !important; }
395
+ #mc .mc-code-footer { padding: 8px 16px !important; background: var(--ed-panel); font-family: var(--ed-font) !important; font-size: 9.5px !important; letter-spacing: 0 !important; }
396
+ #mc .mc-preview-panel { grid-template-rows: 58px minmax(0,1fr) !important; }
397
+ #mc .mc-preview-title { font-family: var(--ed-font); font-size: 12px !important; font-weight: 600; }
398
+ #mc .mc-preview-body { padding: 32px 24px !important; background-color: var(--ed-work); background-image: radial-gradient(circle, var(--ed-grid) 1px, transparent 1.2px); background-size: 22px 22px; }
399
+ #mc .mc-toast { border-radius: 10px; box-shadow: 0 12px 34px rgba(16,19,36,.28) !important; }
400
+ #mc[data-chrome="dark"] .mc-shell { box-shadow: 0 24px 70px rgba(0,0,0,.44) !important; }
401
+ #mc[data-chrome="dark"] .mc-header { background: rgba(25,28,41,.88) !important; }
402
+ @media (max-width: 1240px) { #mc .mc-layout { grid-template-columns: minmax(0,1fr) 330px !important; } }
403
+
404
+ /*
405
+ * Inspector switch -- the on/off control in the properties panel. Everything
406
+ * visual hangs off the button's own aria-checked, so there is no second
407
+ * source of truth for "is it on": a pill track that fills with the accent
408
+ * color, and a knob that slides (transform, not left, so the motion stays on
409
+ * the compositor). Lives at the very end of the sheet, after the polish
410
+ * block, because that block's blanket "#mc button ... border-radius: 8px
411
+ * !important" beats plain specificity -- hence the !important here too.
412
+ * (No backticks anywhere in this file's CSS: the whole stylesheet is one
413
+ * template literal, so a backtick inside it ends the string.)
414
+ */
415
+ #mc .mc-switch {
416
+ position: relative; flex: none; width: 38px; height: 22px; padding: 0;
417
+ border: 0; border-radius: 999px !important; cursor: pointer;
418
+ background: var(--ed-line-2); box-shadow: inset 0 0 0 1px var(--ed-line);
419
+ }
420
+ #mc .mc-switch-knob {
421
+ position: absolute; top: 3px; left: 3px; width: 16px; height: 16px;
422
+ border-radius: 50%; background: #ffffff; pointer-events: none;
423
+ box-shadow: 0 1px 2px rgba(15,23,42,0.30), 0 2px 5px rgba(15,23,42,0.14);
424
+ transition: transform 0.18s cubic-bezier(0.22,0.61,0.36,1);
425
+ }
426
+ #mc .mc-switch[aria-checked="true"] { background: var(--ed-accent); box-shadow: inset 0 0 0 1px var(--ed-accent); }
427
+ #mc .mc-switch[aria-checked="true"] .mc-switch-knob { transform: translateX(16px); }
428
+ #mc .mc-switch-row:hover .mc-switch { background: color-mix(in srgb, var(--ed-faint) 52%, transparent); }
429
+ #mc .mc-switch-row:hover .mc-switch[aria-checked="true"] { background: var(--ed-accent-strong); box-shadow: inset 0 0 0 1px var(--ed-accent-strong); }
430
+ #mc .mc-switch-row:hover label { color: var(--ed-text); }
431
+ #mc[data-chrome="dark"] .mc-switch-knob { background: #e2e8f0; box-shadow: 0 1px 2px rgba(0,0,0,0.44), 0 2px 5px rgba(0,0,0,0.28); }
432
+ /*
433
+ * One-shot slide for the switch the user just clicked. A prop change rebuilds
434
+ * the panel, so the switch that comes back is a brand new node already in its
435
+ * final state -- nothing for a transition to interpolate. render/fields.js tags
436
+ * just that one node with .mc-switch-anim, and these keyframes replay the move
437
+ * from where the old node stood. Panels that merely open keep painting their
438
+ * switches in place, with no animation.
439
+ */
440
+ #mc .mc-switch-anim[aria-checked="true"] { animation: mcSwitchOn 0.18s cubic-bezier(0.22,0.61,0.36,1); }
441
+ #mc .mc-switch-anim[aria-checked="false"] { animation: mcSwitchOff 0.18s cubic-bezier(0.22,0.61,0.36,1); }
442
+ #mc .mc-switch-anim[aria-checked="true"] .mc-switch-knob { animation: mcKnobOn 0.18s cubic-bezier(0.22,0.61,0.36,1); }
443
+ #mc .mc-switch-anim[aria-checked="false"] .mc-switch-knob { animation: mcKnobOff 0.18s cubic-bezier(0.22,0.61,0.36,1); }
444
+ @keyframes mcSwitchOn { from { background: var(--ed-line-2); } }
445
+ @keyframes mcSwitchOff { from { background: var(--ed-accent); } }
446
+ @keyframes mcKnobOn { from { transform: translateX(0); } }
447
+ @keyframes mcKnobOff { from { transform: translateX(16px); } }
448
+
449
+ /* The inspector inherits the active chrome palette. Keeping theme ownership
450
+ at #mc makes the header, menus, every panel and every field switch together
451
+ when light/dark changes, instead of leaving the side panel permanently dark. */
452
+ `;