@broadpaper/editor 0.1.1

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.
@@ -0,0 +1,673 @@
1
+ /* BroadPaper editor design tokens.
2
+ `.bpe` is the editor shell; `.bpe-scope` is a token-only scope used by portalled
3
+ floating elements (tooltips, popovers) so they inherit the palette WITHOUT the
4
+ shell's grid layout, size and background. */
5
+ .bpe,
6
+ .bpe-scope {
7
+ /* colour */
8
+ --bpe-bg: #ffffff;
9
+ --bpe-bg-subtle: #f7f8fa;
10
+ --bpe-bg-muted: #eef0f4;
11
+ --bpe-bg-canvas: #e9ebf0;
12
+ --bpe-bg-elevated: #ffffff;
13
+ --bpe-fg: #111827;
14
+ --bpe-fg-secondary: #4b5563;
15
+ --bpe-fg-muted: #6b7280;
16
+ --bpe-fg-faint: #9ca3af;
17
+ --bpe-border: #e5e7eb;
18
+ --bpe-border-strong: #d1d5db;
19
+ --bpe-accent: #3b5bdb;
20
+ --bpe-accent-hover: #3451c7;
21
+ --bpe-accent-soft: rgba(59, 91, 219, 0.1);
22
+ --bpe-accent-soft-strong: rgba(59, 91, 219, 0.18);
23
+ --bpe-accent-fg: #ffffff;
24
+
25
+ /* The one filled call to action — Export, and any host button asking for
26
+ `variant="primary"`. Separate from the accent, and defaulting to it, because
27
+ a host wanting its brand on that button rarely wants the same colour on
28
+ selection outlines, focus rings and the active tab: BroadPaper's own is a
29
+ vermilion that reads as an error the moment it outlines a selected block,
30
+ and `--bpe-danger` is nearly the same red. Set this alone and the button is
31
+ yours while everything that means "this is selected" stays legible. */
32
+ --bpe-primary: var(--bpe-accent);
33
+ --bpe-primary-hover: var(--bpe-accent-hover);
34
+ --bpe-primary-fg: var(--bpe-accent-fg);
35
+
36
+ --bpe-danger: #dc2626;
37
+ --bpe-danger-soft: rgba(220, 38, 38, 0.1);
38
+ --bpe-warning: #d97706;
39
+ --bpe-warning-soft: rgba(217, 119, 6, 0.12);
40
+ --bpe-success: #059669;
41
+ --bpe-success-soft: rgba(5, 150, 105, 0.12);
42
+ --bpe-token: #2f4f7f;
43
+ --bpe-token-bg: rgba(79, 111, 165, 0.14);
44
+ --bpe-selection: var(--bpe-accent);
45
+ --bpe-hover-outline: rgba(59, 91, 219, 0.45);
46
+ --bpe-drop: #10b981;
47
+
48
+ /* typography */
49
+ --bpe-font: Inter, ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
50
+ --bpe-font-mono: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
51
+ --bpe-text-xs: 10.5px;
52
+ --bpe-text-sm: 11.5px;
53
+ --bpe-text-md: 12.5px;
54
+ --bpe-text-lg: 14px;
55
+ --bpe-text-xl: 16px;
56
+
57
+ /* geometry */
58
+ --bpe-radius-sm: 4px;
59
+ --bpe-radius: 6px;
60
+ --bpe-radius-lg: 10px;
61
+ --bpe-space-1: 4px;
62
+ --bpe-space-2: 8px;
63
+ --bpe-space-3: 12px;
64
+ --bpe-space-4: 16px;
65
+ --bpe-space-5: 20px;
66
+ --bpe-space-6: 24px;
67
+ --bpe-control-h: 28px;
68
+ --bpe-control-h-sm: 24px;
69
+ --bpe-panel-w: 272px;
70
+ --bpe-inspector-w: 296px;
71
+ --bpe-toolbar-h: 48px;
72
+
73
+ /* effects */
74
+ --bpe-shadow-sm: 0 1px 2px rgba(16, 24, 40, 0.06);
75
+ --bpe-shadow: 0 1px 3px rgba(16, 24, 40, 0.1), 0 1px 2px rgba(16, 24, 40, 0.06);
76
+ --bpe-shadow-lg: 0 10px 30px rgba(16, 24, 40, 0.16), 0 2px 6px rgba(16, 24, 40, 0.08);
77
+ --bpe-shadow-page: 0 1px 2px rgba(16, 24, 40, 0.1), 0 12px 32px rgba(16, 24, 40, 0.1);
78
+ --bpe-focus: 0 0 0 2px var(--bpe-bg), 0 0 0 4px var(--bpe-accent);
79
+ --bpe-ease: cubic-bezier(0.2, 0, 0, 1);
80
+ --bpe-dur: 140ms;
81
+ }
82
+
83
+ .bpe[data-appearance="dark"],
84
+ .bpe-scope[data-appearance="dark"] {
85
+ --bpe-bg: #17191e;
86
+ --bpe-bg-subtle: #1d2026;
87
+ --bpe-bg-muted: #23262d;
88
+ --bpe-bg-canvas: #101114;
89
+ --bpe-bg-elevated: #1f2228;
90
+ --bpe-fg: #e7e9ee;
91
+ --bpe-fg-secondary: #b9bec8;
92
+ --bpe-fg-muted: #8b919c;
93
+ --bpe-fg-faint: #5e636d;
94
+ --bpe-border: #2b2f37;
95
+ --bpe-border-strong: #3a3f49;
96
+ --bpe-accent: #6b8cff;
97
+ --bpe-accent-hover: #7e9bff;
98
+ --bpe-accent-soft: rgba(107, 140, 255, 0.16);
99
+ --bpe-accent-soft-strong: rgba(107, 140, 255, 0.28);
100
+ --bpe-accent-fg: #0e1220;
101
+ --bpe-token: #a9c0ff;
102
+ --bpe-token-bg: rgba(107, 140, 255, 0.2);
103
+ --bpe-hover-outline: rgba(107, 140, 255, 0.55);
104
+ --bpe-shadow: 0 1px 3px rgba(0, 0, 0, 0.4), 0 1px 2px rgba(0, 0, 0, 0.3);
105
+ --bpe-shadow-lg: 0 10px 30px rgba(0, 0, 0, 0.5), 0 2px 6px rgba(0, 0, 0, 0.3);
106
+ --bpe-shadow-page: 0 1px 2px rgba(0, 0, 0, 0.4), 0 12px 32px rgba(0, 0, 0, 0.4);
107
+ }
108
+
109
+ /* ===================================================================
110
+ BroadPaper editor — component styles. Scoped under .bpe.
111
+ =================================================================== */
112
+
113
+ .bpe, .bpe *, .bpe *::before, .bpe *::after,
114
+ .bpe-scope, .bpe-scope *, .bpe-scope *::before, .bpe-scope *::after { box-sizing: border-box; }
115
+ .bpe {
116
+ position: relative;
117
+ display: grid;
118
+ grid-template-rows: var(--bpe-toolbar-h) 1fr;
119
+ grid-template-columns: var(--bpe-panel-w) minmax(0, 1fr) var(--bpe-inspector-w);
120
+ grid-template-areas: "toolbar toolbar toolbar" "left canvas right";
121
+ width: 100%;
122
+ height: 100%;
123
+ min-height: 480px;
124
+ background: var(--bpe-bg);
125
+ color: var(--bpe-fg);
126
+ font-family: var(--bpe-font);
127
+ font-size: var(--bpe-text-md);
128
+ line-height: 1.4;
129
+ -webkit-font-smoothing: antialiased;
130
+ overflow: hidden;
131
+ border: 1px solid var(--bpe-border);
132
+ border-radius: var(--bpe-radius-lg);
133
+ }
134
+ .bpe--no-left { grid-template-columns: 0 minmax(0, 1fr) var(--bpe-inspector-w); }
135
+ .bpe--no-right { grid-template-columns: var(--bpe-panel-w) minmax(0, 1fr) 0; }
136
+ .bpe--no-left.bpe--no-right { grid-template-columns: 0 minmax(0, 1fr) 0; }
137
+ .bpe--no-toolbar { grid-template-rows: 0 1fr; }
138
+ .bpe button, .bpe input, .bpe select, .bpe textarea,
139
+ .bpe-scope button, .bpe-scope input, .bpe-scope select, .bpe-scope textarea { font: inherit; color: inherit; }
140
+ .bpe button, .bpe-scope button { cursor: pointer; }
141
+ .bpe :focus-visible, .bpe-scope :focus-visible { outline: none; box-shadow: var(--bpe-focus); }
142
+ .bpe ::selection, .bpe-scope ::selection { background: var(--bpe-accent-soft-strong); }
143
+ .bpe-sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
144
+ .bpe-mono { font-family: var(--bpe-font-mono); }
145
+ .bpe-muted { color: var(--bpe-fg-muted); }
146
+ .bpe-truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
147
+
148
+ /* scrollbars */
149
+ .bpe *::-webkit-scrollbar, .bpe-scope *::-webkit-scrollbar { width: 10px; height: 10px; }
150
+ .bpe *::-webkit-scrollbar-thumb, .bpe-scope *::-webkit-scrollbar-thumb { background: var(--bpe-border-strong); border: 3px solid transparent; border-radius: 8px; background-clip: padding-box; }
151
+ .bpe *::-webkit-scrollbar-thumb:hover { background: var(--bpe-fg-faint); background-clip: padding-box; border: 3px solid transparent; }
152
+ .bpe *::-webkit-scrollbar-track { background: transparent; }
153
+
154
+ /* ------------------------------------------------------------------ */
155
+ /* Primitives */
156
+ /* ------------------------------------------------------------------ */
157
+ .bpe-btn {
158
+ display: inline-flex; align-items: center; justify-content: center; gap: 6px;
159
+ height: var(--bpe-control-h); padding: 0 10px; border-radius: var(--bpe-radius);
160
+ border: 1px solid var(--bpe-border); background: var(--bpe-bg); color: var(--bpe-fg);
161
+ font-size: var(--bpe-text-sm); font-weight: 500; white-space: nowrap; user-select: none; flex-shrink: 0;
162
+ transition: background var(--bpe-dur) var(--bpe-ease), border-color var(--bpe-dur) var(--bpe-ease), color var(--bpe-dur) var(--bpe-ease), transform 80ms var(--bpe-ease);
163
+ }
164
+ .bpe-btn:hover { background: var(--bpe-bg-subtle); border-color: var(--bpe-border-strong); }
165
+ .bpe-btn:active { transform: translateY(0.5px); }
166
+ .bpe-btn:disabled { opacity: 0.45; cursor: not-allowed; transform: none; }
167
+ .bpe-btn--primary { background: var(--bpe-primary); border-color: var(--bpe-primary); color: var(--bpe-primary-fg); }
168
+ .bpe-btn--primary:hover { background: var(--bpe-primary-hover); border-color: var(--bpe-primary-hover); }
169
+ .bpe-btn--ghost { border-color: transparent; background: transparent; }
170
+ .bpe-btn--ghost:hover { background: var(--bpe-bg-muted); border-color: transparent; }
171
+ .bpe-btn--danger { color: var(--bpe-danger); }
172
+ .bpe-btn--danger:hover { background: var(--bpe-danger-soft); border-color: transparent; }
173
+ .bpe-btn--sm { height: var(--bpe-control-h-sm); padding: 0 8px; font-size: var(--bpe-text-xs); }
174
+ .bpe-btn--icon { width: var(--bpe-control-h); padding: 0; }
175
+ .bpe-btn--sm.bpe-btn--icon { width: var(--bpe-control-h-sm); }
176
+ .bpe-btn--active { background: var(--bpe-accent-soft); border-color: transparent; color: var(--bpe-accent); }
177
+ .bpe-btn svg { width: 15px; height: 15px; flex-shrink: 0; }
178
+ .bpe-btn--sm svg { width: 13px; height: 13px; }
179
+
180
+ .bpe-input, .bpe-select, .bpe-textarea {
181
+ width: 100%; height: var(--bpe-control-h); padding: 0 8px; border-radius: var(--bpe-radius);
182
+ border: 1px solid var(--bpe-border); background: var(--bpe-bg); color: var(--bpe-fg); font-size: var(--bpe-text-sm);
183
+ transition: border-color var(--bpe-dur) var(--bpe-ease), box-shadow var(--bpe-dur) var(--bpe-ease);
184
+ }
185
+ .bpe-input::placeholder, .bpe-textarea::placeholder { color: var(--bpe-fg-faint); }
186
+ .bpe-input:hover, .bpe-select:hover, .bpe-textarea:hover { border-color: var(--bpe-border-strong); }
187
+ .bpe-input:focus, .bpe-select:focus, .bpe-textarea:focus { border-color: var(--bpe-accent); box-shadow: 0 0 0 3px var(--bpe-accent-soft); }
188
+ .bpe-input--invalid { border-color: var(--bpe-danger) !important; }
189
+ .bpe-textarea { height: auto; min-height: 56px; padding: 6px 8px; resize: vertical; line-height: 1.45; }
190
+ .bpe-select { appearance: none; padding-right: 24px; background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'><path d='M1 1l4 4 4-4' fill='none' stroke='%236b7280' stroke-width='1.5' stroke-linecap='round'/></svg>"); background-repeat: no-repeat; background-position: right 8px center; }
191
+ .bpe-input-group { position: relative; display: flex; align-items: center; }
192
+ .bpe-input-group .bpe-input { padding-right: 28px; }
193
+ .bpe-input-group__suffix { position: absolute; right: 8px; font-size: var(--bpe-text-xs); color: var(--bpe-fg-faint); pointer-events: none; }
194
+ .bpe-input-group__btn { position: absolute; right: 2px; top: 2px; }
195
+ .bpe-number { display: flex; align-items: center; }
196
+ .bpe-number .bpe-input { text-align: left; }
197
+
198
+ .bpe-switch { position: relative; display: inline-flex; width: 30px; height: 18px; border-radius: 9px; background: var(--bpe-border-strong); border: 0; padding: 0; transition: background var(--bpe-dur) var(--bpe-ease); flex-shrink: 0; }
199
+ .bpe-switch::after { content: ""; position: absolute; top: 2px; left: 2px; width: 14px; height: 14px; border-radius: 50%; background: #fff; box-shadow: var(--bpe-shadow-sm); transition: transform var(--bpe-dur) var(--bpe-ease); }
200
+ .bpe-switch[aria-checked="true"] { background: var(--bpe-accent); }
201
+ .bpe-switch[aria-checked="true"]::after { transform: translateX(12px); }
202
+
203
+ .bpe-segmented { display: inline-flex; padding: 2px; border-radius: var(--bpe-radius); background: var(--bpe-bg-muted); gap: 2px; width: 100%; }
204
+ .bpe-segmented__item { flex: 1; display: inline-flex; align-items: center; justify-content: center; gap: 4px; height: 22px; padding: 0 6px; border: 0; border-radius: 4px; background: transparent; color: var(--bpe-fg-muted); font-size: var(--bpe-text-xs); font-weight: 500; transition: background var(--bpe-dur) var(--bpe-ease), color var(--bpe-dur) var(--bpe-ease); min-width: 0; }
205
+ .bpe-segmented__item:hover { color: var(--bpe-fg); }
206
+ .bpe-segmented__item[aria-pressed="true"] { background: var(--bpe-bg-elevated); color: var(--bpe-fg); box-shadow: var(--bpe-shadow-sm); }
207
+ .bpe-segmented__item svg { width: 13px; height: 13px; }
208
+
209
+ /* Five named tabs will not fit side by side in a 272px panel, so they stack
210
+ the label under the icon and share the row evenly. Names beat guessing at
211
+ icons; a second row of chrome does not. */
212
+ .bpe-tabs { display: flex; gap: 2px; padding: 6px 6px 0; border-bottom: 1px solid var(--bpe-border); }
213
+ .bpe-tab { position: relative; flex: 1 1 0; min-width: 0; display: inline-flex; flex-direction: column; align-items: center; justify-content: center; gap: 2px; height: 42px; padding: 0 2px; border: 0; background: transparent; color: var(--bpe-fg-muted); font-size: var(--bpe-text-xs); font-weight: 500; border-radius: var(--bpe-radius-sm) var(--bpe-radius-sm) 0 0; }
214
+ .bpe-tab > span { max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
215
+ .bpe-tab:hover { color: var(--bpe-fg); background: var(--bpe-bg-subtle); }
216
+ .bpe-tab[aria-selected="true"] { color: var(--bpe-fg); }
217
+ .bpe-tab[aria-selected="true"] { background: var(--bpe-bg-subtle); }
218
+ .bpe-tab[aria-selected="true"]::after { content: ""; position: absolute; left: 6px; right: 6px; bottom: -1px; height: 2px; background: var(--bpe-accent); border-radius: 2px; }
219
+ .bpe-tab svg { width: 14px; height: 14px; }
220
+
221
+ .bpe-popover { z-index: 1000; min-width: 180px; max-width: 340px; padding: 6px; border-radius: var(--bpe-radius-lg); border: 1px solid var(--bpe-border); background: var(--bpe-bg-elevated); color: var(--bpe-fg); box-shadow: var(--bpe-shadow-lg); font-family: var(--bpe-font); font-size: var(--bpe-text-sm); }
222
+ /* Entrance runs only once floating-ui has positioned the element, so it never
223
+ animates from the viewport corner. */
224
+ .bpe-floating--in { animation: bpe-pop var(--bpe-dur) var(--bpe-ease); }
225
+ @keyframes bpe-pop { from { opacity: 0; transform: translateY(-3px) scale(0.98); } to { opacity: 1; transform: none; } }
226
+ .bpe-menu__item { display: flex; align-items: center; gap: 8px; width: 100%; min-height: 28px; padding: 4px 8px; border: 0; border-radius: var(--bpe-radius-sm); background: transparent; color: var(--bpe-fg); text-align: left; font-size: var(--bpe-text-sm); }
227
+ .bpe-menu__item--stacked { align-items: flex-start; padding: 6px 8px; }
228
+ .bpe-menu__item--stacked svg { margin-top: 2px; }
229
+ .bpe-menu__body { display: flex; flex-direction: column; gap: 1px; min-width: 0; flex: 1; }
230
+ .bpe-menu__label-text { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
231
+ .bpe-menu__desc { font-size: var(--bpe-text-xs); color: var(--bpe-fg-muted); line-height: 1.35; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
232
+ .bpe-menu__item:hover, .bpe-menu__item:focus-visible { background: var(--bpe-bg-muted); box-shadow: none; }
233
+ .bpe-menu__item:disabled { opacity: 0.45; }
234
+ .bpe-menu__item svg { width: 14px; height: 14px; color: var(--bpe-fg-muted); }
235
+ .bpe-menu__item--danger { color: var(--bpe-danger); }
236
+ .bpe-menu__item--danger svg { color: var(--bpe-danger); }
237
+ .bpe-menu__sep { height: 1px; margin: 4px 0; background: var(--bpe-border); }
238
+ .bpe-menu__label { padding: 6px 8px 2px; font-size: var(--bpe-text-xs); font-weight: 600; letter-spacing: 0.04em; text-transform: uppercase; color: var(--bpe-fg-faint); }
239
+ .bpe-menu__hint { margin-left: auto; padding-left: 8px; color: var(--bpe-fg-faint); font-size: var(--bpe-text-xs); white-space: nowrap; flex-shrink: 0; }
240
+ /* The portal is a zero-width positioned box, which is the containing block for
241
+ everything floating inside it. A shrink-to-fit element therefore has no room
242
+ at all and wraps after every word; `max-content` is what stops that. */
243
+ .bpe-tooltip { width: max-content; z-index: 1100; padding: 4px 8px; border-radius: var(--bpe-radius-sm); background: #111827; color: #fff; font-size: var(--bpe-text-xs); font-family: var(--bpe-font); font-weight: 500; pointer-events: none; box-shadow: var(--bpe-shadow); max-width: 260px; }
244
+ .bpe-tooltip .bpe-kbd { margin-left: 6px; background: rgba(255,255,255,0.14); color: #fff; border-color: transparent; }
245
+ .bpe-kbd { display: inline-block; padding: 0 4px; border-radius: 3px; border: 1px solid var(--bpe-border); background: var(--bpe-bg-subtle); font-size: 10px; font-family: var(--bpe-font); color: var(--bpe-fg-muted); line-height: 15px; }
246
+ .bpe-badge { display: inline-flex; align-items: center; gap: 4px; height: 18px; padding: 0 6px; border-radius: 999px; background: var(--bpe-bg-muted); color: var(--bpe-fg-secondary); font-size: var(--bpe-text-xs); font-weight: 600; white-space: nowrap; }
247
+ .bpe-badge--accent { background: var(--bpe-accent-soft); color: var(--bpe-accent); }
248
+ .bpe-badge--warning { background: var(--bpe-warning-soft); color: var(--bpe-warning); }
249
+ .bpe-badge--danger { background: var(--bpe-danger-soft); color: var(--bpe-danger); }
250
+ .bpe-badge--success { background: var(--bpe-success-soft); color: var(--bpe-success); }
251
+ .bpe-chip { display: inline-flex; align-items: center; gap: 4px; height: 20px; padding: 0 7px; border-radius: 4px; background: var(--bpe-token-bg); color: var(--bpe-token); font-size: var(--bpe-text-xs); font-weight: 500; max-width: 100%; }
252
+ .bpe-chip svg { width: 11px; height: 11px; }
253
+ .bpe-chip__x { margin-left: 2px; border: 0; background: transparent; padding: 0; color: inherit; display: inline-flex; opacity: 0.6; }
254
+ .bpe-chip__x:hover { opacity: 1; }
255
+ .bpe-empty { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 6px; padding: 28px 16px; text-align: center; color: var(--bpe-fg-muted); font-size: var(--bpe-text-sm); }
256
+ .bpe-empty svg { width: 22px; height: 22px; color: var(--bpe-fg-faint); }
257
+
258
+ /* One region's failure, contained. See ui/Boundary.tsx. */
259
+ .bpe-boundary { display: flex; align-items: flex-start; gap: 10px; overflow: auto; padding: 12px 14px; background: var(--bpe-bg); border: 1px solid var(--bpe-border); color: var(--bpe-fg); font-size: var(--bpe-text-sm); }
260
+ .bpe-boundary > svg { flex-shrink: 0; margin-top: 1px; color: var(--bpe-warning); }
261
+ .bpe-boundary__body { flex: 1; min-width: 0; }
262
+ .bpe-boundary__title { font-weight: 600; }
263
+ .bpe-boundary__detail { margin-top: 2px; color: var(--bpe-fg-muted); }
264
+
265
+ /* Where it went. Applied to a block the canvas has just scrolled to, so an
266
+ insertion that landed on another page is visibly an insertion and not a
267
+ jump. Motion-sensitive users get the tint without the pulse. */
268
+ @keyframes bpe-flash {
269
+ 0% { box-shadow: 0 0 0 2px var(--bpe-accent), 0 0 0 7px var(--bpe-accent-soft); background-color: var(--bpe-accent-soft); }
270
+ 70% { box-shadow: 0 0 0 2px var(--bpe-accent), 0 0 0 7px var(--bpe-accent-soft); background-color: var(--bpe-accent-soft); }
271
+ 100% { box-shadow: 0 0 0 0 transparent, 0 0 0 0 transparent; background-color: transparent; }
272
+ }
273
+ .bpe-flash { animation: bpe-flash 1.2s ease-out both; border-radius: 3px; }
274
+ @media (prefers-reduced-motion: reduce) {
275
+ .bpe-flash { animation: none; background-color: var(--bpe-accent-soft); box-shadow: 0 0 0 2px var(--bpe-accent); }
276
+ }
277
+ .bpe-empty__title { font-weight: 600; color: var(--bpe-fg-secondary); }
278
+
279
+ /* form field */
280
+ .bpe-field { display: grid; grid-template-columns: 96px minmax(0, 1fr); align-items: center; gap: 6px 10px; min-height: var(--bpe-control-h); }
281
+ .bpe-field--stacked { grid-template-columns: 1fr; gap: 4px; }
282
+ .bpe-field__label { font-size: var(--bpe-text-sm); color: var(--bpe-fg-secondary); font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
283
+ .bpe-field__control { min-width: 0; display: flex; align-items: center; gap: 4px; }
284
+ .bpe-field__hint { grid-column: 1 / -1; min-width: 0; margin-top: -2px; font-size: var(--bpe-text-xs); color: var(--bpe-fg-muted); line-height: 1.35; overflow-wrap: anywhere; }
285
+ .bpe-field__error { grid-column: 1 / -1; font-size: var(--bpe-text-xs); color: var(--bpe-danger); }
286
+ .bpe-prop-grid { display: flex; flex-direction: column; gap: 8px; padding: 10px 12px; }
287
+ .bpe-section { border-bottom: 1px solid var(--bpe-border); }
288
+ .bpe-section__head { display: flex; align-items: center; gap: 4px; width: 100%; height: 34px; padding: 0 8px 0 0; }
289
+ .bpe-section__toggle { display: flex; align-items: center; gap: 6px; flex: 1; min-width: 0; height: 100%; padding: 0 4px 0 12px; border: 0; background: transparent; color: var(--bpe-fg); font-size: var(--bpe-text-sm); font-weight: 600; text-align: left; }
290
+ .bpe-section__toggle:hover { background: var(--bpe-bg-subtle); }
291
+ .bpe-section__toggle svg { width: 13px; height: 13px; color: var(--bpe-fg-faint); transition: transform var(--bpe-dur) var(--bpe-ease); }
292
+ .bpe-section__toggle[aria-expanded="false"] svg.bpe-section__chev { transform: rotate(-90deg); }
293
+ .bpe-section__toggle .bpe-section__chev { margin-left: auto; }
294
+ .bpe-section__actions { display: inline-flex; }
295
+ .bpe-section__body { padding-bottom: 4px; }
296
+
297
+ /* ------------------------------------------------------------------ */
298
+ /* Toolbar */
299
+ /* ------------------------------------------------------------------ */
300
+ /* The side tracks must be allowed to shrink. As plain `1fr` they refuse to go
301
+ below their own content, so the end group overflowed leftwards and drew the
302
+ brand picker on top of the Preview button instead of giving anything up. */
303
+ .bpe-toolbar { grid-area: toolbar; display: grid; grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr); align-items: center; gap: 8px; padding: 0 12px; border-bottom: 1px solid var(--bpe-border); background: var(--bpe-bg); z-index: 5; }
304
+ .bpe-toolbar__group { display: flex; align-items: center; gap: 4px; min-width: 0; }
305
+ .bpe-toolbar__group--end { justify-content: flex-end; }
306
+ .bpe-toolbar__sep { width: 1px; height: 20px; margin: 0 6px; background: var(--bpe-border); }
307
+ .bpe-toolbar__brand { display: flex; align-items: center; gap: 8px; margin-right: 6px; min-width: 0; }
308
+ /* The product mark. No tile behind it: the sheet is the shape, and a tile
309
+ would make it a second rectangle inside a rectangle. */
310
+ .bpe-toolbar__mark { display: grid; place-items: center; flex-shrink: 0; }
311
+ .bpe-toolbar__mark svg { width: 22px; height: 18px; display: block; }
312
+ .bpe-toolbar__name { height: 26px; flex: 0 1 auto; min-width: 0; width: 220px; max-width: 260px; padding: 0 6px; border: 1px solid transparent; border-radius: var(--bpe-radius-sm); background: transparent; font-weight: 600; font-size: var(--bpe-text-md); color: var(--bpe-fg); }
313
+ .bpe-toolbar__name:hover { border-color: var(--bpe-border); }
314
+ .bpe-toolbar__name:focus { border-color: var(--bpe-accent); box-shadow: 0 0 0 3px var(--bpe-accent-soft); }
315
+ /* The centre group never gives up room; the ends shrink around it. */
316
+ .bpe-toolbar__group--centre { flex: 0 0 auto; white-space: nowrap; }
317
+ .bpe-toolbar__group--start { overflow: hidden; }
318
+
319
+ .bpe-save { display: inline-flex; align-items: center; gap: 5px; height: 24px; padding: 0 7px; border: 1px solid transparent; border-radius: var(--bpe-radius-sm); font-size: var(--bpe-text-xs); font-weight: 500; white-space: nowrap; flex-shrink: 0; }
320
+ .bpe-save__text { max-width: 130px; overflow: hidden; text-overflow: ellipsis; }
321
+ .bpe-save--muted, .bpe-save--clean { color: var(--bpe-fg-faint); }
322
+ .bpe-save--dirty { color: var(--bpe-warning); background: var(--bpe-bg-muted); }
323
+ .bpe-save--busy { color: var(--bpe-fg-secondary); }
324
+ .bpe-save--error { color: var(--bpe-danger); border-color: var(--bpe-danger); }
325
+ .bpe-save--action { cursor: pointer; }
326
+ .bpe-save--action:hover { background: var(--bpe-bg-subtle); }
327
+ .bpe-sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
328
+
329
+ .bpe-theme-select__name { max-width: 140px; }
330
+
331
+ .bpe-issues { width: 340px; }
332
+ .bpe-issues__group + .bpe-issues__group { border-top: 1px solid var(--bpe-border); margin-top: 6px; padding-top: 8px; }
333
+ .bpe-issues__head { display: flex; align-items: center; gap: 6px; padding: 2px 10px 0; }
334
+ .bpe-issues__title { font-size: var(--bpe-text-xs); font-weight: 600; text-transform: uppercase; letter-spacing: 0.04em; color: var(--bpe-fg-secondary); }
335
+ .bpe-issues__blurb { margin: 2px 10px 6px; font-size: var(--bpe-text-xs); color: var(--bpe-fg-faint); line-height: 1.45; }
336
+ .bpe-issues__ok { display: flex; align-items: baseline; gap: 5px; padding: 2px 10px 6px; font-size: var(--bpe-text-xs); color: var(--bpe-fg-faint); flex-wrap: wrap; }
337
+ .bpe-issues__ok strong { color: var(--bpe-fg-secondary); font-weight: 600; }
338
+ .bpe-mode { width: auto; }
339
+ .bpe-mode .bpe-segmented__item { padding: 0 12px; height: 24px; }
340
+ .bpe-zoom { display: inline-flex; align-items: center; gap: 2px; }
341
+ .bpe-zoom__value { min-width: 44px; text-align: center; font-size: var(--bpe-text-sm); font-variant-numeric: tabular-nums; color: var(--bpe-fg-secondary); }
342
+ .bpe-theme-select { display: inline-flex; align-items: center; gap: 6px; height: var(--bpe-control-h); padding: 0 8px 0 6px; border: 1px solid var(--bpe-border); border-radius: var(--bpe-radius); background: var(--bpe-bg); font-size: var(--bpe-text-sm); font-weight: 500; }
343
+ .bpe-theme-select:hover { border-color: var(--bpe-border-strong); background: var(--bpe-bg-subtle); }
344
+ .bpe-swatches { display: inline-flex; gap: 2px; }
345
+ .bpe-swatch { width: 10px; height: 10px; border-radius: 3px; box-shadow: inset 0 0 0 1px rgba(0,0,0,0.08); }
346
+
347
+ /* ------------------------------------------------------------------ */
348
+ /* Left panel */
349
+ /* ------------------------------------------------------------------ */
350
+ .bpe-left { grid-area: left; display: flex; flex-direction: column; min-height: 0; border-right: 1px solid var(--bpe-border); background: var(--bpe-bg); overflow: hidden; }
351
+ .bpe-left__body { flex: 1; min-height: 0; overflow-y: auto; overscroll-behavior: contain; }
352
+ .bpe-search { position: relative; padding: 8px 10px 6px; }
353
+ .bpe-search svg { position: absolute; left: 18px; top: 50%; transform: translateY(-50%); width: 13px; height: 13px; color: var(--bpe-fg-faint); pointer-events: none; }
354
+ .bpe-search .bpe-input { padding-left: 26px; }
355
+ .bpe-palette__group { padding: 4px 10px 10px; }
356
+ .bpe-palette__title { display: flex; align-items: center; gap: 6px; padding: 8px 2px 6px; font-size: var(--bpe-text-xs); font-weight: 600; letter-spacing: 0.05em; text-transform: uppercase; color: var(--bpe-fg-faint); }
357
+ .bpe-palette__grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 6px; }
358
+ .bpe-palette__item { position: relative; display: flex; flex-direction: column; align-items: flex-start; gap: 6px; padding: 8px 9px; border: 1px solid var(--bpe-border); border-radius: var(--bpe-radius); background: var(--bpe-bg); color: var(--bpe-fg); text-align: left; cursor: grab; user-select: none; transition: border-color var(--bpe-dur) var(--bpe-ease), background var(--bpe-dur) var(--bpe-ease), transform 120ms var(--bpe-ease), box-shadow var(--bpe-dur) var(--bpe-ease); }
359
+ .bpe-palette__item:hover { border-color: var(--bpe-border-strong); background: var(--bpe-bg-subtle); transform: translateY(-1px); box-shadow: var(--bpe-shadow); }
360
+ .bpe-palette__item:active { cursor: grabbing; transform: none; }
361
+ .bpe-palette__item svg { width: 16px; height: 16px; color: var(--bpe-fg-secondary); }
362
+ .bpe-palette__item-name { font-size: var(--bpe-text-sm); font-weight: 500; line-height: 1.2; }
363
+ .bpe-palette__tier { position: absolute; top: 4px; right: 5px; font-size: 8.5px; font-weight: 700; letter-spacing: 0.06em; color: var(--bpe-accent); }
364
+ .bpe-palette__item--locked { opacity: 0.55; cursor: not-allowed; }
365
+ .bpe-palette__item--locked .bpe-palette__tier { color: var(--bpe-fg-faint); }
366
+ .bpe-layouts__grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 6px; }
367
+ .bpe-layout-item { display: flex; flex-direction: column; gap: 6px; padding: 8px; border: 1px solid var(--bpe-border); border-radius: var(--bpe-radius); background: var(--bpe-bg); cursor: grab; }
368
+ .bpe-layout-item:hover { border-color: var(--bpe-border-strong); background: var(--bpe-bg-subtle); }
369
+ .bpe-layout-item__cols { display: grid; gap: 3px; height: 22px; }
370
+ .bpe-layout-item__col { background: var(--bpe-bg-muted); border: 1px solid var(--bpe-border-strong); border-radius: 3px; }
371
+ .bpe-layout-item__name { font-size: var(--bpe-text-xs); color: var(--bpe-fg-secondary); font-weight: 500; }
372
+
373
+ .bpe-data-tree { padding: 4px 6px 12px; }
374
+ .bpe-data-source { margin-bottom: 4px; }
375
+ .bpe-data-source__head { display: flex; align-items: center; gap: 6px; width: 100%; height: 28px; padding: 0 6px; border: 0; border-radius: var(--bpe-radius-sm); background: transparent; color: var(--bpe-fg); font-weight: 600; font-size: var(--bpe-text-sm); text-align: left; }
376
+ .bpe-data-source__head:hover { background: var(--bpe-bg-subtle); }
377
+ .bpe-data-source__head svg { width: 13px; height: 13px; color: var(--bpe-fg-faint); }
378
+ .bpe-data-field { display: flex; align-items: center; gap: 6px; width: 100%; height: 26px; padding: 0 6px; border: 0; border-radius: var(--bpe-radius-sm); background: transparent; color: var(--bpe-fg); font-size: var(--bpe-text-sm); text-align: left; cursor: grab; }
379
+ .bpe-data-field:hover { background: var(--bpe-accent-soft); }
380
+ .bpe-data-field__type { margin-left: auto; font-size: 9.5px; font-weight: 600; letter-spacing: 0.04em; text-transform: uppercase; color: var(--bpe-fg-faint); }
381
+ .bpe-data-field__icon { width: 13px; height: 13px; color: var(--bpe-fg-muted); flex-shrink: 0; }
382
+ .bpe-data-field--group { color: var(--bpe-fg-secondary); cursor: default; }
383
+ .bpe-data-children { padding-left: 14px; border-left: 1px solid var(--bpe-border); margin-left: 12px; }
384
+
385
+ .bpe-layers { padding: 6px 6px 12px; }
386
+ .bpe-layer { display: flex; align-items: center; gap: 5px; height: 26px; padding-right: 4px; border-radius: var(--bpe-radius-sm); color: var(--bpe-fg); font-size: var(--bpe-text-sm); cursor: default; user-select: none; }
387
+ .bpe-layer:hover { background: var(--bpe-bg-subtle); }
388
+ .bpe-layer--selected { background: var(--bpe-accent-soft) !important; color: var(--bpe-accent); }
389
+ .bpe-layer--hover { background: var(--bpe-bg-muted); }
390
+ .bpe-layer { cursor: grab; }
391
+ .bpe-layer:active { cursor: grabbing; }
392
+ /* Row actions stay out of the way until the row is wanted, so the tree still
393
+ reads as a tree rather than as a wall of buttons. */
394
+ .bpe-layer__actions { display: none; margin-left: auto; gap: 0; flex-shrink: 0; }
395
+ .bpe-layer:hover .bpe-layer__actions, .bpe-layer--selected .bpe-layer__actions { display: flex; }
396
+ .bpe-layer__actions .bpe-btn { width: 20px; height: 20px; }
397
+ .bpe-layer__actions .bpe-btn svg { width: 12px; height: 12px; }
398
+ .bpe-layer:hover .bpe-layer__meta, .bpe-layer--selected .bpe-layer__meta { display: none; }
399
+
400
+ .bpe-layer__toggle { display: grid; place-items: center; width: 18px; height: 18px; border: 0; background: transparent; color: var(--bpe-fg-faint); padding: 0; border-radius: 3px; flex-shrink: 0; }
401
+ .bpe-layer__toggle svg { width: 12px; height: 12px; transition: transform var(--bpe-dur) var(--bpe-ease); }
402
+ .bpe-layer__toggle[aria-expanded="false"] svg { transform: rotate(-90deg); }
403
+ .bpe-layer__icon { width: 13px; height: 13px; color: var(--bpe-fg-muted); flex-shrink: 0; }
404
+ .bpe-layer--selected .bpe-layer__icon { color: var(--bpe-accent); }
405
+ .bpe-layer__name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
406
+ .bpe-layer__meta { font-size: var(--bpe-text-xs); color: var(--bpe-fg-faint); }
407
+
408
+ .bpe-library__item { display: flex; align-items: center; gap: 8px; padding: 8px 10px; margin: 0 8px 6px; border: 1px solid var(--bpe-border); border-radius: var(--bpe-radius); cursor: grab; background: var(--bpe-bg); }
409
+ .bpe-library__item:hover { border-color: var(--bpe-border-strong); background: var(--bpe-bg-subtle); }
410
+ .bpe-library__item svg { width: 15px; height: 15px; color: var(--bpe-fg-secondary); }
411
+ .bpe-library__name { font-weight: 500; font-size: var(--bpe-text-sm); }
412
+ .bpe-library__desc { font-size: var(--bpe-text-xs); color: var(--bpe-fg-muted); }
413
+
414
+ /* ------------------------------------------------------------------ */
415
+ /* Canvas */
416
+ /* ------------------------------------------------------------------ */
417
+ .bpe-canvas { grid-area: canvas; position: relative; display: flex; flex-direction: column; min-width: 0; min-height: 0; background: var(--bpe-bg-canvas); }
418
+ .bpe-canvas__scroll { position: relative; flex: 1; min-height: 0; overflow: auto; overscroll-behavior: contain; }
419
+ .bpe-canvas__scroll:focus-visible { box-shadow: none; }
420
+ .bpe-canvas__inner { position: relative; padding: 32px 40px 80px; display: flex; flex-direction: column; align-items: center; min-width: min-content; }
421
+ .bpe-pages { position: relative; display: flex; flex-direction: column; transform-origin: top center; }
422
+ .bpe-page-slot { position: relative; box-shadow: var(--bpe-shadow-page); background: #fff; }
423
+ .bpe-page-slot--placeholder { background: #fff; opacity: 0.9; }
424
+ .bpe-page-number { position: absolute; left: 0; top: -20px; font-size: var(--bpe-text-xs); color: var(--bpe-fg-muted); font-weight: 500; user-select: none; }
425
+ .bpe-canvas--design .bp-page__region { outline: 1px dashed rgba(59, 91, 219, 0.22); outline-offset: -1px; }
426
+ .bpe-canvas--design .bp-page__region:hover { outline-color: rgba(59, 91, 219, 0.5); }
427
+ .bpe-canvas--design .bp-page__region::before { content: attr(data-region); position: absolute; right: 0; top: -14px; font: 600 9px/1 var(--bpe-font); letter-spacing: 0.08em; text-transform: uppercase; color: rgba(59, 91, 219, 0.55); opacity: 0; transition: opacity var(--bpe-dur); }
428
+ .bpe-canvas--design .bp-page__region:hover::before, .bpe-canvas--design .bp-page__region--active::before { opacity: 1; }
429
+ .bpe-canvas--design .bp-page__footer::before { top: auto; bottom: -14px; }
430
+ .bpe-canvas--design .bp-page__body { outline: 1px dashed transparent; }
431
+ .bpe-canvas--design.bpe-canvas--guides .bp-page__body { outline-color: rgba(0,0,0,0.08); }
432
+ .bpe-canvas--design .bp-node { cursor: default; }
433
+ .bpe-canvas--design .bp-node--text .bp-text, .bpe-canvas--design .bp-heading { cursor: text; }
434
+ .bpe-canvas--design .bp-node[data-editing] > * { visibility: hidden; }
435
+ .bpe-canvas--design a { pointer-events: none; }
436
+ .bpe-canvas--dragging, .bpe-canvas--dragging * { cursor: grabbing !important; user-select: none !important; }
437
+
438
+ /* overlays */
439
+ .bpe-overlay { position: absolute; inset: 0; pointer-events: none; z-index: 3; }
440
+ .bpe-outline { position: absolute; border: 1.5px solid var(--bpe-hover-outline); border-radius: 2px; pointer-events: none; transition: none; }
441
+ .bpe-outline--selected { border-color: var(--bpe-selection); border-width: 2px; box-shadow: 0 0 0 1px rgba(255,255,255,0.7) inset; }
442
+ .bpe-outline--section { border-style: dashed; border-color: rgba(59,91,219,0.35); }
443
+ .bpe-outline__label { position: absolute; left: -2px; top: -20px; display: inline-flex; align-items: center; gap: 4px; height: 18px; padding: 0 6px; border-radius: 4px 4px 0 0; background: var(--bpe-selection); color: #fff; font: 600 10px/1 var(--bpe-font); letter-spacing: 0.02em; white-space: nowrap; }
444
+ .bpe-outline--hover .bpe-outline__label { background: var(--bpe-hover-outline); }
445
+ .bpe-outline__label svg { width: 10px; height: 10px; }
446
+ .bpe-block-toolbar { position: absolute; z-index: 4; display: inline-flex; align-items: center; gap: 2px; padding: 3px; border-radius: 7px; border: 1px solid var(--bpe-border); background: var(--bpe-bg-elevated); box-shadow: var(--bpe-shadow-lg); pointer-events: auto; animation: bpe-pop var(--bpe-dur) var(--bpe-ease); }
447
+ .bpe-block-toolbar .bpe-btn--icon { width: 24px; height: 24px; }
448
+ .bpe-block-toolbar .bpe-btn svg { width: 13px; height: 13px; }
449
+ .bpe-block-toolbar__handle { cursor: grab; }
450
+ .bpe-block-toolbar__handle:active { cursor: grabbing; }
451
+ .bpe-block-toolbar__type { padding: 0 6px 0 4px; font-size: var(--bpe-text-xs); font-weight: 600; color: var(--bpe-fg-secondary); white-space: nowrap; }
452
+ /* The field list offered while a "{{" is open. Positioned in the overlay's
453
+ coordinate space, so it tracks the caret rather than the block. */
454
+ .bpe-suggest { position: absolute; z-index: 40; width: 340px; max-height: 268px; overflow-y: auto; padding: 4px; border-radius: var(--bpe-radius); border: 1px solid var(--bpe-border); background: var(--bpe-bg-elevated); box-shadow: var(--bpe-shadow-lg); font-size: var(--bpe-text-sm); }
455
+ .bpe-suggest__item { display: flex; align-items: center; gap: 7px; min-width: 0; padding: 5px 8px; border-radius: var(--bpe-radius-sm); cursor: pointer; }
456
+ .bpe-suggest__item--active { background: var(--bpe-accent-soft); }
457
+ .bpe-suggest__icon { color: var(--bpe-fg-faint); flex-shrink: 0; }
458
+ .bpe-suggest__item--active .bpe-suggest__icon { color: var(--bpe-accent); }
459
+ .bpe-suggest__label { flex: 1; min-width: 0; color: var(--bpe-fg); }
460
+ .bpe-suggest__expr { flex-shrink: 0; max-width: 45%; color: var(--bpe-fg-faint); font-family: var(--bpe-font-mono); font-size: 10.5px; }
461
+ .bpe-suggest__hint { padding: 6px 8px 3px; color: var(--bpe-fg-faint); font-size: 10.5px; border-top: 1px solid var(--bpe-border); margin-top: 4px; }
462
+ .bpe-suggest__hint kbd { display: inline-block; padding: 0 3px; border: 1px solid var(--bpe-border); border-bottom-width: 2px; border-radius: 3px; font-family: inherit; font-size: 9.5px; }
463
+
464
+ /* Where a click said the next block should go. Quieter than a drop line,
465
+ because it is a standing intention rather than something happening now. */
466
+ .bpe-caret { position: absolute; height: 2px; margin-top: -1px; border-radius: 2px; background: var(--bpe-accent); opacity: 0.85; pointer-events: none; z-index: 4; animation: bpe-caret-blink 1.1s ease-in-out infinite; }
467
+ .bpe-caret::before { content: ""; position: absolute; left: -4px; top: -3px; width: 8px; height: 8px; border-radius: 50%; background: var(--bpe-accent); }
468
+ @keyframes bpe-caret-blink { 0%, 100% { opacity: 0.85; } 50% { opacity: 0.35; } }
469
+ @media (prefers-reduced-motion: reduce) { .bpe-caret { animation: none; } }
470
+
471
+ .bpe-drop-line { position: absolute; height: 3px; margin-top: -1.5px; border-radius: 2px; background: var(--bpe-drop); box-shadow: 0 0 0 1px rgba(255,255,255,0.8), 0 0 8px rgba(16,185,129,0.5); pointer-events: none; z-index: 5; }
472
+ .bpe-drop-line::before, .bpe-drop-line::after { content: ""; position: absolute; top: -3.5px; width: 10px; height: 10px; border-radius: 50%; background: var(--bpe-drop); box-shadow: 0 0 0 2px #fff; }
473
+ .bpe-drop-line::before { left: -5px; }
474
+ .bpe-drop-line::after { right: -5px; }
475
+ .bpe-drop-zone { position: absolute; border: 2px solid var(--bpe-drop); border-radius: 4px; background: rgba(16,185,129,0.06); pointer-events: none; z-index: 4; }
476
+ .bpe-drop-zone--invalid { border-color: var(--bpe-danger); background: var(--bpe-danger-soft); }
477
+ .bpe-drag-ghost { position: fixed; z-index: 2000; pointer-events: none; display: inline-flex; align-items: center; gap: 6px; padding: 6px 10px; border-radius: var(--bpe-radius); background: var(--bpe-fg); color: var(--bpe-bg); font: 500 12px var(--bpe-font); box-shadow: var(--bpe-shadow-lg); transform: translate(12px, 12px); }
478
+ .bpe-drag-ghost svg { width: 14px; height: 14px; }
479
+ .bpe-page-gap { position: absolute; left: 0; right: 0; display: flex; align-items: center; justify-content: center; opacity: 0; transition: opacity var(--bpe-dur); pointer-events: auto; }
480
+ .bpe-page-gap:hover { opacity: 1; }
481
+
482
+ .bpe-canvas__bar { display: flex; align-items: center; gap: 8px; height: 30px; padding: 0 10px; border-top: 1px solid var(--bpe-border); background: var(--bpe-bg); font-size: var(--bpe-text-xs); color: var(--bpe-fg-muted); }
483
+ .bpe-breadcrumb { display: flex; align-items: center; gap: 2px; min-width: 0; overflow: hidden; }
484
+ .bpe-breadcrumb__item { display: inline-flex; align-items: center; gap: 4px; height: 20px; padding: 0 6px; border: 0; border-radius: 4px; background: transparent; color: var(--bpe-fg-muted); font-size: var(--bpe-text-xs); white-space: nowrap; }
485
+ .bpe-breadcrumb__item:hover { background: var(--bpe-bg-muted); color: var(--bpe-fg); }
486
+ .bpe-breadcrumb__item--current { color: var(--bpe-fg); font-weight: 600; }
487
+ .bpe-breadcrumb__sep { color: var(--bpe-fg-faint); }
488
+ .bpe-canvas__bar-end { margin-left: auto; display: flex; align-items: center; gap: 8px; }
489
+
490
+ /* inline editor */
491
+ .bpe-inline-editor { position: absolute; z-index: 6; outline: none; }
492
+ .bpe-inline-editor .ProseMirror { outline: none; white-space: pre-wrap; word-wrap: break-word; min-height: 1em; }
493
+ .bpe-inline-editor .ProseMirror p { margin: 0; }
494
+ .bpe-inline-editor .ProseMirror ul, .bpe-inline-editor .ProseMirror ol { margin: 0; padding-left: 1.4em; }
495
+ .bpe-inline-editor .ProseMirror .bp-token { display: inline; padding: 0 5px; margin: 0 1px; border-radius: 4px; background: var(--bpe-token-bg); color: var(--bpe-token); font-size: 0.92em; font-weight: 500; white-space: nowrap; user-select: none; }
496
+ .bpe-inline-editor .ProseMirror .bp-token.ProseMirror-selectednode { outline: 2px solid var(--bpe-accent); }
497
+ .bpe-inline-editor .ProseMirror-gapcursor { display: none; }
498
+ .bpe-inline-editor__frame { position: absolute; inset: -4px; border: 1.5px solid var(--bpe-accent); border-radius: 3px; pointer-events: none; }
499
+ .bpe-bubble { position: absolute; z-index: 7; display: inline-flex; align-items: center; gap: 2px; padding: 3px; border-radius: 8px; border: 1px solid var(--bpe-border); background: var(--bpe-bg-elevated); box-shadow: var(--bpe-shadow-lg); animation: bpe-pop var(--bpe-dur) var(--bpe-ease); }
500
+ .bpe-bubble .bpe-btn--icon { width: 26px; height: 26px; }
501
+ .bpe-bubble__sep { width: 1px; height: 18px; margin: 0 2px; background: var(--bpe-border); }
502
+
503
+ /* ------------------------------------------------------------------ */
504
+ /* Inspector */
505
+ /* ------------------------------------------------------------------ */
506
+ .bpe-inspector { grid-area: right; display: flex; flex-direction: column; min-height: 0; border-left: 1px solid var(--bpe-border); background: var(--bpe-bg); overflow: hidden; }
507
+ .bpe-inspector__header { display: flex; align-items: center; gap: 8px; height: 44px; padding: 0 12px; border-bottom: 1px solid var(--bpe-border); }
508
+ .bpe-inspector__icon { display: grid; place-items: center; width: 26px; height: 26px; border-radius: 6px; background: var(--bpe-accent-soft); color: var(--bpe-accent); flex-shrink: 0; }
509
+ .bpe-inspector__icon svg { width: 14px; height: 14px; }
510
+ .bpe-inspector__title { font-weight: 600; font-size: var(--bpe-text-md); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
511
+ .bpe-inspector__subtitle { font-size: var(--bpe-text-xs); color: var(--bpe-fg-muted); }
512
+ .bpe-inspector__body { flex: 1; min-height: 0; overflow-y: auto; overscroll-behavior: contain; }
513
+ .bpe-inspector__actions { margin-left: auto; display: flex; gap: 2px; }
514
+ .bpe-spacing-grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 4px; }
515
+ .bpe-spacing-grid .bpe-input { padding: 0 4px; text-align: center; }
516
+ .bpe-spacing-grid__lbl { font-size: 9px; text-transform: uppercase; letter-spacing: 0.06em; color: var(--bpe-fg-faint); text-align: center; margin-top: 2px; }
517
+ .bpe-color-btn { display: inline-flex; align-items: center; gap: 6px; height: var(--bpe-control-h); width: 100%; padding: 0 8px 0 6px; border: 1px solid var(--bpe-border); border-radius: var(--bpe-radius); background: var(--bpe-bg); font-size: var(--bpe-text-sm); text-align: left; }
518
+ .bpe-color-btn:hover { border-color: var(--bpe-border-strong); }
519
+ .bpe-color-btn__swatch { width: 16px; height: 16px; border-radius: 4px; box-shadow: inset 0 0 0 1px rgba(0,0,0,0.12); flex-shrink: 0; background-image: linear-gradient(45deg, #ddd 25%, transparent 25%, transparent 75%, #ddd 75%), linear-gradient(45deg, #ddd 25%, transparent 25%, transparent 75%, #ddd 75%); background-size: 8px 8px; background-position: 0 0, 4px 4px; }
520
+ .bpe-color-btn__swatch::after { content: ""; display: block; width: 100%; height: 100%; border-radius: 4px; background: var(--c, transparent); }
521
+ .bpe-color-btn__label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
522
+ .bpe-color-popover { width: 236px; }
523
+ .bpe-color-grid { display: grid; grid-template-columns: repeat(8, 1fr); gap: 4px; padding: 4px; }
524
+ .bpe-color-grid__item { position: relative; width: 24px; height: 24px; border-radius: 6px; border: 1px solid rgba(0,0,0,0.08); background: var(--c); padding: 0; }
525
+ .bpe-color-grid__item[aria-pressed="true"] { box-shadow: 0 0 0 2px var(--bpe-bg), 0 0 0 4px var(--bpe-accent); }
526
+ .bpe-color-token { display: flex; align-items: center; gap: 8px; width: 100%; height: 26px; padding: 0 6px; border: 0; border-radius: 4px; background: transparent; font-size: var(--bpe-text-sm); text-align: left; color: var(--bpe-fg); }
527
+ .bpe-color-token:hover { background: var(--bpe-bg-muted); }
528
+ .bpe-color-token[aria-pressed="true"] { background: var(--bpe-accent-soft); color: var(--bpe-accent); }
529
+ .bpe-color-token__sw { width: 14px; height: 14px; border-radius: 4px; background: var(--c); box-shadow: inset 0 0 0 1px rgba(0,0,0,0.1); }
530
+ .bpe-color-token__val { margin-left: auto; font-family: var(--bpe-font-mono); font-size: 10px; color: var(--bpe-fg-faint); }
531
+ .bpe-binding { display: flex; align-items: center; gap: 4px; width: 100%; min-width: 0; }
532
+ /* The image field's file input is opened by the button beside it. Hidden with
533
+ clip rather than display:none so the browser still counts it as a real
534
+ control and the button's click reaches it. */
535
+ .bpe-image__file { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; border: 0; }
536
+ .bpe-binding__btn { display: flex; align-items: center; gap: 6px; flex: 1; min-width: 0; height: var(--bpe-control-h); padding: 0 8px; border: 1px solid var(--bpe-border); border-radius: var(--bpe-radius); background: var(--bpe-bg); text-align: left; font-size: var(--bpe-text-sm); }
537
+ .bpe-binding__btn:hover { border-color: var(--bpe-border-strong); }
538
+ .bpe-binding__btn--empty { color: var(--bpe-fg-faint); }
539
+ .bpe-binding__btn svg { width: 13px; height: 13px; flex-shrink: 0; color: var(--bpe-fg-muted); }
540
+ .bpe-expr { font-family: var(--bpe-font-mono); font-size: 11px; }
541
+ .bpe-picker { width: 300px; max-height: 380px; display: flex; flex-direction: column; padding: 0; overflow: hidden; }
542
+ .bpe-picker__search { padding: 8px; border-bottom: 1px solid var(--bpe-border); }
543
+ .bpe-picker__list { flex: 1; overflow-y: auto; padding: 4px; }
544
+ .bpe-picker__foot { display: flex; gap: 6px; align-items: center; padding: 8px; border-top: 1px solid var(--bpe-border); }
545
+ .bpe-picker__foot .bpe-input { flex: 1; }
546
+ .bpe-picker__group { padding: 6px 6px 2px; font-size: var(--bpe-text-xs); font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; color: var(--bpe-fg-faint); }
547
+ .bpe-cond { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
548
+ .bpe-cond__rule { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 4px; min-width: 0; padding: 6px; border: 1px solid var(--bpe-border); border-radius: var(--bpe-radius); background: var(--bpe-bg-subtle); }
549
+ .bpe-cond__row { display: grid; grid-template-columns: minmax(0, 1fr); gap: 4px; min-width: 0; }
550
+ .bpe-cond__op { display: flex; align-items: center; gap: 6px; }
551
+ .bpe-cond__group { padding: 6px; border: 1px dashed var(--bpe-border-strong); border-radius: var(--bpe-radius); }
552
+ .bpe-list-editor { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
553
+ .bpe-list-editor__item { border: 1px solid var(--bpe-border); border-radius: var(--bpe-radius); background: var(--bpe-bg-subtle); }
554
+ .bpe-list-editor__head { display: flex; align-items: center; gap: 4px; min-width: 0; overflow: hidden; height: 28px; padding: 0 4px 0 8px; font-size: var(--bpe-text-sm); font-weight: 500; }
555
+ .bpe-list-editor__head .bpe-truncate { flex: 1 1 0; min-width: 0; }
556
+ .bpe-list-editor__item { min-width: 0; }
557
+ .bpe-list-editor__body { padding: 6px 8px 8px; border-top: 1px solid var(--bpe-border); display: flex; flex-direction: column; gap: 6px; }
558
+ .bpe-list-editor__drag { cursor: grab; color: var(--bpe-fg-faint); display: inline-flex; }
559
+ .bpe-issue { display: flex; gap: 8px; align-items: flex-start; width: 100%; padding: 6px 8px; border: 0; border-radius: var(--bpe-radius-sm); background: transparent; text-align: left; font-size: var(--bpe-text-sm); color: var(--bpe-fg); }
560
+ .bpe-issue:hover { background: var(--bpe-bg-muted); }
561
+ .bpe-issue svg { width: 13px; height: 13px; flex-shrink: 0; margin-top: 2px; }
562
+ .bpe-issue--error svg { color: var(--bpe-danger); }
563
+ .bpe-issue--warning svg { color: var(--bpe-warning); }
564
+ .bpe-issue--info svg { color: var(--bpe-fg-muted); }
565
+ .bpe-issue__meta { display: block; margin-top: 2px; font-size: var(--bpe-text-xs); color: var(--bpe-fg-muted); }
566
+ .bpe-text-style-row { display: grid; grid-template-columns: 1fr auto; align-items: center; gap: 6px; }
567
+ .bpe-text-style-preview { font-weight: 600; }
568
+ .bpe-doc-styles__item { display: flex; align-items: center; gap: 8px; width: 100%; height: 32px; padding: 0 12px; border: 0; background: transparent; text-align: left; color: var(--bpe-fg); font-size: var(--bpe-text-sm); }
569
+ .bpe-doc-styles__item:hover { background: var(--bpe-bg-subtle); }
570
+ .bpe-doc-styles__sample { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
571
+ .bpe-doc-styles__id { font-family: var(--bpe-font-mono); font-size: 10px; color: var(--bpe-fg-faint); }
572
+ .bpe-preview-bar { display: flex; align-items: center; gap: 8px; padding: 6px 12px; background: var(--bpe-accent-soft); color: var(--bpe-accent); font-size: var(--bpe-text-sm); font-weight: 500; border-bottom: 1px solid var(--bpe-border); }
573
+ .bpe-toast { position: absolute; left: 50%; bottom: 44px; transform: translateX(-50%); z-index: 20; display: inline-flex; align-items: center; gap: 8px; padding: 8px 12px; border-radius: var(--bpe-radius); background: var(--bpe-fg); color: var(--bpe-bg); font-size: var(--bpe-text-sm); box-shadow: var(--bpe-shadow-lg); animation: bpe-pop var(--bpe-dur) var(--bpe-ease); }
574
+ .bpe-toast svg { width: 14px; height: 14px; }
575
+ .bpe-portal { position: absolute; width: 0; height: 0; overflow: visible; }
576
+ .bpe-scope { font-family: var(--bpe-font); }
577
+
578
+ .bpe-pager { display: inline-flex; align-items: center; gap: 2px; }
579
+ .bpe-pager__label { min-width: 92px; text-align: center; font-variant-numeric: tabular-nums; }
580
+
581
+ /* Panel resize handles sit on the boundary rather than in the grid, so the
582
+ three-column shell stays three columns and the handles stay draggable even
583
+ when a panel is scrolled. */
584
+ .bpe-resizer { position: absolute; top: var(--bpe-toolbar-h); bottom: 0; width: 9px; cursor: col-resize; z-index: 6; background: transparent; touch-action: none; }
585
+ .bpe-resizer--left { left: var(--bpe-panel-w); margin-left: -4px; }
586
+ .bpe-resizer--right { right: var(--bpe-inspector-w); margin-right: -4px; }
587
+ .bpe-resizer::after { content: ""; position: absolute; inset: 0 3px; border-radius: 2px; background: transparent; transition: background var(--bpe-dur) var(--bpe-ease); }
588
+ .bpe-resizer:hover::after, .bpe-resizer:focus-visible::after { background: var(--bpe-accent); }
589
+ .bpe-resizer:focus-visible { outline: none; }
590
+
591
+ /* A long label stops being a column and becomes a line of its own. */
592
+ .bpe-field--stacked .bpe-field__label { white-space: normal; overflow: visible; }
593
+
594
+ .bpe-block-toolbar__edit { font-weight: 500; }
595
+
596
+ .bpe-legend { width: 340px; }
597
+ .bpe-legend__body { padding: 12px 14px 10px; }
598
+ .bpe-legend__title { margin: 0 0 6px; font-size: var(--bpe-text-md); font-weight: 600; }
599
+ .bpe-legend__intro { margin: 0 0 10px; font-size: var(--bpe-text-xs); color: var(--bpe-fg-secondary); line-height: 1.5; }
600
+ .bpe-legend__list { margin: 0; display: flex; flex-direction: column; gap: 10px; }
601
+ .bpe-legend__entry { display: flex; gap: 9px; align-items: flex-start; }
602
+ .bpe-legend__swatch { flex: 0 0 auto; display: grid; place-items: center; min-width: 22px; padding-top: 1px; color: var(--bpe-fg-muted); }
603
+ .bpe-legend__entry dt { font-size: var(--bpe-text-xs); font-weight: 600; color: var(--bpe-fg); }
604
+ .bpe-legend__entry dd { margin: 1px 0 0; font-size: var(--bpe-text-xs); color: var(--bpe-fg-muted); line-height: 1.45; }
605
+ .bpe-legend__token { font-size: 9px; padding: 1px 4px; }
606
+ .bpe-legend__foot { margin: 10px 0 0; padding-top: 8px; border-top: 1px solid var(--bpe-border); font-size: var(--bpe-text-xs); color: var(--bpe-fg-faint); }
607
+
608
+ /* ===================================================================
609
+ Narrow — the designer in a container too small for three columns.
610
+
611
+ Set by the shell from the root element's own width, not a media query:
612
+ the designer is embedded, so a phone and a narrow pane in a wide page
613
+ are the same problem and the window answers neither. Below the
614
+ breakpoint the panels stop being columns and become drawers over the
615
+ canvas, because 272 + 296 of panel leaves a phone about forty pixels
616
+ of page — which is not a cramped canvas, it is no canvas.
617
+ =================================================================== */
618
+
619
+ /* Two classes deliberately: `.bpe--no-left.bpe--no-right` is also two, and a
620
+ single-class rule would lose to it exactly when both panels are shut —
621
+ which on a phone is the normal state. */
622
+ .bpe.bpe--narrow { grid-template-columns: minmax(0, 1fr); grid-template-areas: "toolbar" "canvas"; }
623
+
624
+ /* Both panels occupy the canvas cell rather than one of their own, so the
625
+ canvas keeps its full width whether a drawer is open or not — nothing
626
+ reflows underneath when one opens, which is what makes it a drawer
627
+ rather than a column that happens to overlap. */
628
+ .bpe--narrow .bpe-left, .bpe--narrow .bpe-inspector {
629
+ grid-area: canvas;
630
+ z-index: 20;
631
+ width: min(88%, 360px);
632
+ max-width: 100%;
633
+ box-shadow: 0 24px 48px -24px rgba(0, 0, 0, 0.45);
634
+ animation: bpe-drawer-in 180ms var(--bpe-ease) both;
635
+ }
636
+ .bpe--narrow .bpe-left { justify-self: start; border-right: 1px solid var(--bpe-border); }
637
+ .bpe--narrow .bpe-inspector { justify-self: end; border-left: 1px solid var(--bpe-border); }
638
+ @keyframes bpe-drawer-in { from { transform: translateX(var(--bpe-drawer-from, -8%)); opacity: 0; } to { transform: none; opacity: 1; } }
639
+ .bpe--narrow .bpe-inspector { --bpe-drawer-from: 8%; }
640
+ @media (prefers-reduced-motion: reduce) { .bpe--narrow .bpe-left, .bpe--narrow .bpe-inspector { animation: none; } }
641
+
642
+ /* Tapping the canvas beside an open drawer closes it. A button rather than
643
+ a div so it is reachable by keyboard and announces itself; it sits under
644
+ the drawer and over everything else. */
645
+ .bpe-scrim { grid-area: canvas; z-index: 15; border: 0; padding: 0; background: rgba(23, 21, 17, 0.32); cursor: default; animation: bpe-fade-in 180ms var(--bpe-ease) both; }
646
+ @keyframes bpe-fade-in { from { opacity: 0; } to { opacity: 1; } }
647
+ @media (prefers-reduced-motion: reduce) { .bpe-scrim { animation: none; } }
648
+
649
+ /* The page needs the room more than the margin around it does. */
650
+ .bpe--narrow .bpe-canvas__inner { padding: 12px 12px 48px; }
651
+ /* Touch targets, and a scroll that does not fight the page it is embedded in. */
652
+ .bpe--narrow .bpe-btn { min-height: 32px; }
653
+ .bpe--narrow .bpe-canvas__scroll { -webkit-overflow-scrolling: touch; }
654
+
655
+ /* The toolbar's three tracks assume a row wide enough for all of them. On a
656
+ phone the centre group alone — Design/Preview plus the zoom controls, both
657
+ of which refuse to wrap — takes the whole width, leaving the leading and
658
+ trailing tracks measuring zero and their contents drawn off the edge. So
659
+ narrow, it wraps: the groups keep their order and their contents, and the
660
+ toolbar takes the two rows it needs. `.bpe` gives it an auto row to grow
661
+ into rather than the fixed height a single row assumed. */
662
+ .bpe.bpe--narrow { grid-template-rows: auto 1fr; }
663
+ .bpe--narrow .bpe-toolbar { display: flex; flex-wrap: wrap; justify-content: space-between; row-gap: 6px; padding: 6px 8px; }
664
+ .bpe--narrow .bpe-toolbar__group--start { overflow: visible; flex: 1 1 auto; }
665
+ .bpe--narrow .bpe-toolbar__group--centre { order: 3; flex: 1 0 100%; justify-content: center; }
666
+ .bpe--narrow .bpe-toolbar__group--end { flex: 0 0 auto; }
667
+ /* The report name gives up its fixed width first; it is the one thing in the
668
+ leading group that can shrink without losing a control. */
669
+ .bpe--narrow .bpe-toolbar__name { width: auto; min-width: 80px; max-width: none; flex: 1 1 80px; }
670
+ /* A backstop for the very narrowest: if the mode switch and the zoom controls
671
+ still will not sit on one row, they take two rather than one of them being
672
+ drawn off the edge. */
673
+ .bpe--narrow .bpe-toolbar__group--centre { flex-wrap: wrap; white-space: normal; row-gap: 6px; }