@broadpaper/editor 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +72 -0
- package/dist/index.d.ts +41 -9
- package/dist/index.js +860 -354
- package/dist/index.js.map +1 -1
- package/dist/styles.css +51 -1
- package/package.json +25 -4
package/README.md
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# @broadpaper/editor
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@broadpaper/editor)
|
|
4
|
+
[](https://www.broadpaper.com/legal/terms/)
|
|
5
|
+
[](https://www.broadpaper.com/docs/guide/licensing/)
|
|
6
|
+
|
|
7
|
+
The BroadPaper report designer: a visual, embeddable, white-labelled report editor.
|
|
8
|
+
|
|
9
|
+
Part of [**BroadPaper**](https://www.broadpaper.com/developers/) — an embeddable visual report designer and PDF renderer.
|
|
10
|
+
Your users design the report in your application; BroadPaper owns the layout, the branding,
|
|
11
|
+
the pagination and the output.
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install @broadpaper/editor
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
The designer your users lay reports out in, mounted into an element you own:
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
import { createReportDesigner } from "@broadpaper/editor";
|
|
23
|
+
|
|
24
|
+
const designer = createReportDesigner(element, {
|
|
25
|
+
dataSources,
|
|
26
|
+
template,
|
|
27
|
+
onChange: (next) => save(next)
|
|
28
|
+
});
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
React inside, but exposed as a plain mount function, so it embeds in anything — and
|
|
32
|
+
[`@broadpaper/react`](https://www.npmjs.com/package/@broadpaper/react) and
|
|
33
|
+
[`@broadpaper/angular`](https://www.npmjs.com/package/@broadpaper/angular) are thin wrappers
|
|
34
|
+
over this same API.
|
|
35
|
+
|
|
36
|
+
It is themeable down to the chrome, and setting `features.whiteLabel` removes the BroadPaper
|
|
37
|
+
mark from the toolbar, because it is meant to look like part of your product rather than a guest
|
|
38
|
+
in it. Below 820px it becomes a different
|
|
39
|
+
layout — side panels turn into drawers — measured from the element rather than the window,
|
|
40
|
+
since an embedded designer in a narrow pane has the same problem as a phone.
|
|
41
|
+
|
|
42
|
+
## Licensing
|
|
43
|
+
|
|
44
|
+
BroadPaper is **free to evaluate, with no trial clock and nothing behind a feature gate**.
|
|
45
|
+
Install it, use every feature and evaluate it against your real data for as long as you like.
|
|
46
|
+
Production use needs a licence.
|
|
47
|
+
|
|
48
|
+
In the software, the only difference without a licence is that generated PDFs carry an
|
|
49
|
+
evaluation mark, and a licence removes it. Contractually, the licence is also what permits
|
|
50
|
+
production use. No seats, no activation, no machine binding, no telemetry, and no call home at
|
|
51
|
+
any point.
|
|
52
|
+
|
|
53
|
+
- [How licensing works](https://www.broadpaper.com/docs/guide/licensing/)
|
|
54
|
+
- [Pricing](https://www.broadpaper.com/developers/pricing/)
|
|
55
|
+
- [Licence agreement](https://www.broadpaper.com/legal/terms/)
|
|
56
|
+
|
|
57
|
+
## Documentation
|
|
58
|
+
|
|
59
|
+
- [The designer](https://www.broadpaper.com/docs/guide/editor)
|
|
60
|
+
- [Themes and branding](https://www.broadpaper.com/docs/guide/themes)
|
|
61
|
+
- [White labelling](https://www.broadpaper.com/docs/guide/white-labelling)
|
|
62
|
+
- [Custom blocks](https://www.broadpaper.com/docs/guide/custom-blocks)
|
|
63
|
+
- [Full documentation](https://www.broadpaper.com/docs/)
|
|
64
|
+
|
|
65
|
+
## Support
|
|
66
|
+
|
|
67
|
+
Paid licences include email support at [support@broadpaper.com](mailto:support@broadpaper.com).
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
Copyright © 2026 Codingbadger Creative Limited. See [LICENSE](./LICENSE) in this package, and
|
|
72
|
+
[THIRD-PARTY-NOTICES.md](./THIRD-PARTY-NOTICES.md) for the components it ships with.
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ReportTemplate, AnyVersionTemplate, DataSource, ReportData, Theme, BlockDefinition, LibraryItem, LicenseTier, ResolvedNode, GeometryMap, RenderTreeOptions, Issue, EditorStore, BlockRegistry,
|
|
1
|
+
import { ReportTemplate, AnyVersionTemplate, DataSource, ReportData, Theme, BlockDefinition, LibraryItem, LicenseTier, ResolvedNode, GeometryMap, RenderTreeOptions, Issue, EditorStore, BlockRegistry, FieldSchema, EditorState, FieldType, Condition } from '@broadpaper/core';
|
|
2
2
|
import { LayoutResult } from '@broadpaper/renderer';
|
|
3
3
|
import * as react from 'react';
|
|
4
4
|
|
|
@@ -11,6 +11,12 @@ interface EditorMeasurer {
|
|
|
11
11
|
setOptions?(opts: RenderTreeOptions): void;
|
|
12
12
|
/** Invalidates cached geometry, e.g. when a web font finishes loading. */
|
|
13
13
|
clearCache?(): void;
|
|
14
|
+
/**
|
|
15
|
+
* Resolves once whatever the last measurements found still arriving — an image
|
|
16
|
+
* not yet decoded, a face not yet loaded — has arrived, with whether there was
|
|
17
|
+
* anything. The editor lays the canvas out again when there was.
|
|
18
|
+
*/
|
|
19
|
+
settled?(): Promise<boolean>;
|
|
14
20
|
destroy?(): void;
|
|
15
21
|
}
|
|
16
22
|
interface SampleDataSet {
|
|
@@ -209,6 +215,20 @@ declare class Observable<T> {
|
|
|
209
215
|
}
|
|
210
216
|
|
|
211
217
|
type LeftTab = "blocks" | "layouts" | "data" | "layers" | "library";
|
|
218
|
+
/**
|
|
219
|
+
* A field on its way from the data panel to the page.
|
|
220
|
+
*
|
|
221
|
+
* `bound` is `expr` with the field's own formatter appended, and `schema` is the
|
|
222
|
+
* field itself, so the block receiving the drop can decide what to build from it
|
|
223
|
+
* rather than being handed a type name and left to guess.
|
|
224
|
+
*/
|
|
225
|
+
interface FieldDragPayload {
|
|
226
|
+
expr: string;
|
|
227
|
+
bound?: string;
|
|
228
|
+
label: string;
|
|
229
|
+
type?: string;
|
|
230
|
+
schema?: FieldSchema;
|
|
231
|
+
}
|
|
212
232
|
interface SaveState {
|
|
213
233
|
status: "idle" | "saving" | "saved" | "error";
|
|
214
234
|
/** When the last successful save finished. */
|
|
@@ -219,11 +239,7 @@ interface SaveState {
|
|
|
219
239
|
interface DragState {
|
|
220
240
|
kind: "new" | "move" | "row" | "library" | "field";
|
|
221
241
|
/** Block type for "new"; node id for "move"; layout for "row"; library id for "library"; expression for "field". */
|
|
222
|
-
payload: string | number[] |
|
|
223
|
-
expr: string;
|
|
224
|
-
label: string;
|
|
225
|
-
type?: string;
|
|
226
|
-
};
|
|
242
|
+
payload: string | number[] | FieldDragPayload;
|
|
227
243
|
label: string;
|
|
228
244
|
icon?: string;
|
|
229
245
|
x: number;
|
|
@@ -388,7 +404,7 @@ declare function ReportDesignerRoot({ session }: {
|
|
|
388
404
|
session: EditorSession;
|
|
389
405
|
}): react.JSX.Element;
|
|
390
406
|
|
|
391
|
-
declare const EDITOR_CSS = "/* BroadPaper editor design tokens.\n `.bpe` is the editor shell; `.bpe-scope` is a token-only scope used by portalled\n floating elements (tooltips, popovers) so they inherit the palette WITHOUT the\n shell's grid layout, size and background. */\n.bpe,\n.bpe-scope {\n /* colour */\n --bpe-bg: #ffffff;\n --bpe-bg-subtle: #f7f8fa;\n --bpe-bg-muted: #eef0f4;\n --bpe-bg-canvas: #e9ebf0;\n --bpe-bg-elevated: #ffffff;\n --bpe-fg: #111827;\n --bpe-fg-secondary: #4b5563;\n --bpe-fg-muted: #6b7280;\n --bpe-fg-faint: #9ca3af;\n --bpe-border: #e5e7eb;\n --bpe-border-strong: #d1d5db;\n --bpe-accent: #3b5bdb;\n --bpe-accent-hover: #3451c7;\n --bpe-accent-soft: rgba(59, 91, 219, 0.1);\n --bpe-accent-soft-strong: rgba(59, 91, 219, 0.18);\n --bpe-accent-fg: #ffffff;\n\n /* The one filled call to action \u2014 Export, and any host button asking for\n `variant=\"primary\"`. Separate from the accent, and defaulting to it, because\n a host wanting its brand on that button rarely wants the same colour on\n selection outlines, focus rings and the active tab: BroadPaper's own is a\n vermilion that reads as an error the moment it outlines a selected block,\n and `--bpe-danger` is nearly the same red. Set this alone and the button is\n yours while everything that means \"this is selected\" stays legible. */\n --bpe-primary: var(--bpe-accent);\n --bpe-primary-hover: var(--bpe-accent-hover);\n --bpe-primary-fg: var(--bpe-accent-fg);\n\n --bpe-danger: #dc2626;\n --bpe-danger-soft: rgba(220, 38, 38, 0.1);\n --bpe-warning: #d97706;\n --bpe-warning-soft: rgba(217, 119, 6, 0.12);\n --bpe-success: #059669;\n --bpe-success-soft: rgba(5, 150, 105, 0.12);\n --bpe-token: #2f4f7f;\n --bpe-token-bg: rgba(79, 111, 165, 0.14);\n --bpe-selection: var(--bpe-accent);\n --bpe-hover-outline: rgba(59, 91, 219, 0.45);\n --bpe-drop: #10b981;\n\n /* typography */\n --bpe-font: Inter, ui-sans-serif, system-ui, -apple-system, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif;\n --bpe-font-mono: \"JetBrains Mono\", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n --bpe-text-xs: 10.5px;\n --bpe-text-sm: 11.5px;\n --bpe-text-md: 12.5px;\n --bpe-text-lg: 14px;\n --bpe-text-xl: 16px;\n\n /* geometry */\n --bpe-radius-sm: 4px;\n --bpe-radius: 6px;\n --bpe-radius-lg: 10px;\n --bpe-space-1: 4px;\n --bpe-space-2: 8px;\n --bpe-space-3: 12px;\n --bpe-space-4: 16px;\n --bpe-space-5: 20px;\n --bpe-space-6: 24px;\n --bpe-control-h: 28px;\n --bpe-control-h-sm: 24px;\n --bpe-panel-w: 272px;\n --bpe-inspector-w: 296px;\n --bpe-toolbar-h: 48px;\n\n /* effects */\n --bpe-shadow-sm: 0 1px 2px rgba(16, 24, 40, 0.06);\n --bpe-shadow: 0 1px 3px rgba(16, 24, 40, 0.1), 0 1px 2px rgba(16, 24, 40, 0.06);\n --bpe-shadow-lg: 0 10px 30px rgba(16, 24, 40, 0.16), 0 2px 6px rgba(16, 24, 40, 0.08);\n --bpe-shadow-page: 0 1px 2px rgba(16, 24, 40, 0.1), 0 12px 32px rgba(16, 24, 40, 0.1);\n --bpe-focus: 0 0 0 2px var(--bpe-bg), 0 0 0 4px var(--bpe-accent);\n --bpe-ease: cubic-bezier(0.2, 0, 0, 1);\n --bpe-dur: 140ms;\n}\n\n.bpe[data-appearance=\"dark\"],\n.bpe-scope[data-appearance=\"dark\"] {\n --bpe-bg: #17191e;\n --bpe-bg-subtle: #1d2026;\n --bpe-bg-muted: #23262d;\n --bpe-bg-canvas: #101114;\n --bpe-bg-elevated: #1f2228;\n --bpe-fg: #e7e9ee;\n --bpe-fg-secondary: #b9bec8;\n --bpe-fg-muted: #8b919c;\n --bpe-fg-faint: #5e636d;\n --bpe-border: #2b2f37;\n --bpe-border-strong: #3a3f49;\n --bpe-accent: #6b8cff;\n --bpe-accent-hover: #7e9bff;\n --bpe-accent-soft: rgba(107, 140, 255, 0.16);\n --bpe-accent-soft-strong: rgba(107, 140, 255, 0.28);\n --bpe-accent-fg: #0e1220;\n --bpe-token: #a9c0ff;\n --bpe-token-bg: rgba(107, 140, 255, 0.2);\n --bpe-hover-outline: rgba(107, 140, 255, 0.55);\n --bpe-shadow: 0 1px 3px rgba(0, 0, 0, 0.4), 0 1px 2px rgba(0, 0, 0, 0.3);\n --bpe-shadow-lg: 0 10px 30px rgba(0, 0, 0, 0.5), 0 2px 6px rgba(0, 0, 0, 0.3);\n --bpe-shadow-page: 0 1px 2px rgba(0, 0, 0, 0.4), 0 12px 32px rgba(0, 0, 0, 0.4);\n}\n\n/* ===================================================================\n BroadPaper editor \u2014 component styles. Scoped under .bpe.\n =================================================================== */\n\n.bpe, .bpe *, .bpe *::before, .bpe *::after,\n.bpe-scope, .bpe-scope *, .bpe-scope *::before, .bpe-scope *::after { box-sizing: border-box; }\n.bpe {\n position: relative;\n display: grid;\n grid-template-rows: var(--bpe-toolbar-h) 1fr;\n grid-template-columns: var(--bpe-panel-w) minmax(0, 1fr) var(--bpe-inspector-w);\n grid-template-areas: \"toolbar toolbar toolbar\" \"left canvas right\";\n width: 100%;\n height: 100%;\n min-height: 480px;\n background: var(--bpe-bg);\n color: var(--bpe-fg);\n font-family: var(--bpe-font);\n font-size: var(--bpe-text-md);\n line-height: 1.4;\n -webkit-font-smoothing: antialiased;\n overflow: hidden;\n border: 1px solid var(--bpe-border);\n border-radius: var(--bpe-radius-lg);\n}\n.bpe--no-left { grid-template-columns: 0 minmax(0, 1fr) var(--bpe-inspector-w); }\n.bpe--no-right { grid-template-columns: var(--bpe-panel-w) minmax(0, 1fr) 0; }\n.bpe--no-left.bpe--no-right { grid-template-columns: 0 minmax(0, 1fr) 0; }\n.bpe--no-toolbar { grid-template-rows: 0 1fr; }\n.bpe button, .bpe input, .bpe select, .bpe textarea,\n.bpe-scope button, .bpe-scope input, .bpe-scope select, .bpe-scope textarea { font: inherit; color: inherit; }\n.bpe button, .bpe-scope button { cursor: pointer; }\n.bpe :focus-visible, .bpe-scope :focus-visible { outline: none; box-shadow: var(--bpe-focus); }\n.bpe ::selection, .bpe-scope ::selection { background: var(--bpe-accent-soft-strong); }\n.bpe-sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }\n.bpe-mono { font-family: var(--bpe-font-mono); }\n.bpe-muted { color: var(--bpe-fg-muted); }\n.bpe-truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n\n/* scrollbars */\n.bpe *::-webkit-scrollbar, .bpe-scope *::-webkit-scrollbar { width: 10px; height: 10px; }\n.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; }\n.bpe *::-webkit-scrollbar-thumb:hover { background: var(--bpe-fg-faint); background-clip: padding-box; border: 3px solid transparent; }\n.bpe *::-webkit-scrollbar-track { background: transparent; }\n\n/* ------------------------------------------------------------------ */\n/* Primitives */\n/* ------------------------------------------------------------------ */\n.bpe-btn {\n display: inline-flex; align-items: center; justify-content: center; gap: 6px;\n height: var(--bpe-control-h); padding: 0 10px; border-radius: var(--bpe-radius);\n border: 1px solid var(--bpe-border); background: var(--bpe-bg); color: var(--bpe-fg);\n font-size: var(--bpe-text-sm); font-weight: 500; white-space: nowrap; user-select: none; flex-shrink: 0;\n 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);\n}\n.bpe-btn:hover { background: var(--bpe-bg-subtle); border-color: var(--bpe-border-strong); }\n.bpe-btn:active { transform: translateY(0.5px); }\n.bpe-btn:disabled { opacity: 0.45; cursor: not-allowed; transform: none; }\n.bpe-btn--primary { background: var(--bpe-primary); border-color: var(--bpe-primary); color: var(--bpe-primary-fg); }\n.bpe-btn--primary:hover { background: var(--bpe-primary-hover); border-color: var(--bpe-primary-hover); }\n.bpe-btn--ghost { border-color: transparent; background: transparent; }\n.bpe-btn--ghost:hover { background: var(--bpe-bg-muted); border-color: transparent; }\n.bpe-btn--danger { color: var(--bpe-danger); }\n.bpe-btn--danger:hover { background: var(--bpe-danger-soft); border-color: transparent; }\n.bpe-btn--sm { height: var(--bpe-control-h-sm); padding: 0 8px; font-size: var(--bpe-text-xs); }\n.bpe-btn--icon { width: var(--bpe-control-h); padding: 0; }\n.bpe-btn--sm.bpe-btn--icon { width: var(--bpe-control-h-sm); }\n.bpe-btn--active { background: var(--bpe-accent-soft); border-color: transparent; color: var(--bpe-accent); }\n.bpe-btn svg { width: 15px; height: 15px; flex-shrink: 0; }\n.bpe-btn--sm svg { width: 13px; height: 13px; }\n\n.bpe-input, .bpe-select, .bpe-textarea {\n width: 100%; height: var(--bpe-control-h); padding: 0 8px; border-radius: var(--bpe-radius);\n border: 1px solid var(--bpe-border); background: var(--bpe-bg); color: var(--bpe-fg); font-size: var(--bpe-text-sm);\n transition: border-color var(--bpe-dur) var(--bpe-ease), box-shadow var(--bpe-dur) var(--bpe-ease);\n}\n.bpe-input::placeholder, .bpe-textarea::placeholder { color: var(--bpe-fg-faint); }\n.bpe-input:hover, .bpe-select:hover, .bpe-textarea:hover { border-color: var(--bpe-border-strong); }\n.bpe-input:focus, .bpe-select:focus, .bpe-textarea:focus { border-color: var(--bpe-accent); box-shadow: 0 0 0 3px var(--bpe-accent-soft); }\n.bpe-input--invalid { border-color: var(--bpe-danger) !important; }\n.bpe-textarea { height: auto; min-height: 56px; padding: 6px 8px; resize: vertical; line-height: 1.45; }\n.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; }\n.bpe-input-group { position: relative; display: flex; align-items: center; }\n.bpe-input-group .bpe-input { padding-right: 28px; }\n.bpe-input-group__suffix { position: absolute; right: 8px; font-size: var(--bpe-text-xs); color: var(--bpe-fg-faint); pointer-events: none; }\n.bpe-input-group__btn { position: absolute; right: 2px; top: 2px; }\n.bpe-number { display: flex; align-items: center; }\n.bpe-number .bpe-input { text-align: left; }\n\n.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; }\n.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); }\n.bpe-switch[aria-checked=\"true\"] { background: var(--bpe-accent); }\n.bpe-switch[aria-checked=\"true\"]::after { transform: translateX(12px); }\n\n.bpe-segmented { display: inline-flex; padding: 2px; border-radius: var(--bpe-radius); background: var(--bpe-bg-muted); gap: 2px; width: 100%; }\n.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; }\n.bpe-segmented__item:hover { color: var(--bpe-fg); }\n.bpe-segmented__item[aria-pressed=\"true\"] { background: var(--bpe-bg-elevated); color: var(--bpe-fg); box-shadow: var(--bpe-shadow-sm); }\n.bpe-segmented__item svg { width: 13px; height: 13px; }\n\n/* Five named tabs will not fit side by side in a 272px panel, so they stack\n the label under the icon and share the row evenly. Names beat guessing at\n icons; a second row of chrome does not. */\n.bpe-tabs { display: flex; gap: 2px; padding: 6px 6px 0; border-bottom: 1px solid var(--bpe-border); }\n.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; }\n.bpe-tab > span { max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n.bpe-tab:hover { color: var(--bpe-fg); background: var(--bpe-bg-subtle); }\n.bpe-tab[aria-selected=\"true\"] { color: var(--bpe-fg); }\n.bpe-tab[aria-selected=\"true\"] { background: var(--bpe-bg-subtle); }\n.bpe-tab[aria-selected=\"true\"]::after { content: \"\"; position: absolute; left: 6px; right: 6px; bottom: -1px; height: 2px; background: var(--bpe-accent); border-radius: 2px; }\n.bpe-tab svg { width: 14px; height: 14px; }\n\n.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); }\n/* Entrance runs only once floating-ui has positioned the element, so it never\n animates from the viewport corner. */\n.bpe-floating--in { animation: bpe-pop var(--bpe-dur) var(--bpe-ease); }\n@keyframes bpe-pop { from { opacity: 0; transform: translateY(-3px) scale(0.98); } to { opacity: 1; transform: none; } }\n.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); }\n.bpe-menu__item--stacked { align-items: flex-start; padding: 6px 8px; }\n.bpe-menu__item--stacked svg { margin-top: 2px; }\n.bpe-menu__body { display: flex; flex-direction: column; gap: 1px; min-width: 0; flex: 1; }\n.bpe-menu__label-text { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n.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; }\n.bpe-menu__item:hover, .bpe-menu__item:focus-visible { background: var(--bpe-bg-muted); box-shadow: none; }\n.bpe-menu__item:disabled { opacity: 0.45; }\n.bpe-menu__item svg { width: 14px; height: 14px; color: var(--bpe-fg-muted); }\n.bpe-menu__item--danger { color: var(--bpe-danger); }\n.bpe-menu__item--danger svg { color: var(--bpe-danger); }\n.bpe-menu__sep { height: 1px; margin: 4px 0; background: var(--bpe-border); }\n.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); }\n.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; }\n/* The portal is a zero-width positioned box, which is the containing block for\n everything floating inside it. A shrink-to-fit element therefore has no room\n at all and wraps after every word; `max-content` is what stops that. */\n.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; }\n.bpe-tooltip .bpe-kbd { margin-left: 6px; background: rgba(255,255,255,0.14); color: #fff; border-color: transparent; }\n.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; }\n.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; }\n.bpe-badge--accent { background: var(--bpe-accent-soft); color: var(--bpe-accent); }\n.bpe-badge--warning { background: var(--bpe-warning-soft); color: var(--bpe-warning); }\n.bpe-badge--danger { background: var(--bpe-danger-soft); color: var(--bpe-danger); }\n.bpe-badge--success { background: var(--bpe-success-soft); color: var(--bpe-success); }\n.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%; }\n.bpe-chip svg { width: 11px; height: 11px; }\n.bpe-chip__x { margin-left: 2px; border: 0; background: transparent; padding: 0; color: inherit; display: inline-flex; opacity: 0.6; }\n.bpe-chip__x:hover { opacity: 1; }\n.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); }\n.bpe-empty svg { width: 22px; height: 22px; color: var(--bpe-fg-faint); }\n\n/* One region's failure, contained. See ui/Boundary.tsx. */\n.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); }\n.bpe-boundary > svg { flex-shrink: 0; margin-top: 1px; color: var(--bpe-warning); }\n.bpe-boundary__body { flex: 1; min-width: 0; }\n.bpe-boundary__title { font-weight: 600; }\n.bpe-boundary__detail { margin-top: 2px; color: var(--bpe-fg-muted); }\n\n/* Where it went. Applied to a block the canvas has just scrolled to, so an\n insertion that landed on another page is visibly an insertion and not a\n jump. Motion-sensitive users get the tint without the pulse. */\n@keyframes bpe-flash {\n 0% { box-shadow: 0 0 0 2px var(--bpe-accent), 0 0 0 7px var(--bpe-accent-soft); background-color: var(--bpe-accent-soft); }\n 70% { box-shadow: 0 0 0 2px var(--bpe-accent), 0 0 0 7px var(--bpe-accent-soft); background-color: var(--bpe-accent-soft); }\n 100% { box-shadow: 0 0 0 0 transparent, 0 0 0 0 transparent; background-color: transparent; }\n}\n.bpe-flash { animation: bpe-flash 1.2s ease-out both; border-radius: 3px; }\n@media (prefers-reduced-motion: reduce) {\n .bpe-flash { animation: none; background-color: var(--bpe-accent-soft); box-shadow: 0 0 0 2px var(--bpe-accent); }\n}\n.bpe-empty__title { font-weight: 600; color: var(--bpe-fg-secondary); }\n\n/* form field */\n.bpe-field { display: grid; grid-template-columns: 96px minmax(0, 1fr); align-items: center; gap: 6px 10px; min-height: var(--bpe-control-h); }\n.bpe-field--stacked { grid-template-columns: 1fr; gap: 4px; }\n.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; }\n.bpe-field__control { min-width: 0; display: flex; align-items: center; gap: 4px; }\n.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; }\n.bpe-field__error { grid-column: 1 / -1; font-size: var(--bpe-text-xs); color: var(--bpe-danger); }\n.bpe-prop-grid { display: flex; flex-direction: column; gap: 8px; padding: 10px 12px; }\n.bpe-section { border-bottom: 1px solid var(--bpe-border); }\n.bpe-section__head { display: flex; align-items: center; gap: 4px; width: 100%; height: 34px; padding: 0 8px 0 0; }\n.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; }\n.bpe-section__toggle:hover { background: var(--bpe-bg-subtle); }\n.bpe-section__toggle svg { width: 13px; height: 13px; color: var(--bpe-fg-faint); transition: transform var(--bpe-dur) var(--bpe-ease); }\n.bpe-section__toggle[aria-expanded=\"false\"] svg.bpe-section__chev { transform: rotate(-90deg); }\n.bpe-section__toggle .bpe-section__chev { margin-left: auto; }\n.bpe-section__actions { display: inline-flex; }\n.bpe-section__body { padding-bottom: 4px; }\n\n/* ------------------------------------------------------------------ */\n/* Toolbar */\n/* ------------------------------------------------------------------ */\n/* The side tracks must be allowed to shrink. As plain `1fr` they refuse to go\n below their own content, so the end group overflowed leftwards and drew the\n brand picker on top of the Preview button instead of giving anything up. */\n.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; }\n.bpe-toolbar__group { display: flex; align-items: center; gap: 4px; min-width: 0; }\n.bpe-toolbar__group--end { justify-content: flex-end; }\n.bpe-toolbar__sep { width: 1px; height: 20px; margin: 0 6px; background: var(--bpe-border); }\n.bpe-toolbar__brand { display: flex; align-items: center; gap: 8px; margin-right: 6px; min-width: 0; }\n/* The product mark. No tile behind it: the sheet is the shape, and a tile\n would make it a second rectangle inside a rectangle. */\n.bpe-toolbar__mark { display: grid; place-items: center; flex-shrink: 0; }\n.bpe-toolbar__mark svg { width: 22px; height: 18px; display: block; }\n.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); }\n.bpe-toolbar__name:hover { border-color: var(--bpe-border); }\n.bpe-toolbar__name:focus { border-color: var(--bpe-accent); box-shadow: 0 0 0 3px var(--bpe-accent-soft); }\n/* The centre group never gives up room; the ends shrink around it. */\n.bpe-toolbar__group--centre { flex: 0 0 auto; white-space: nowrap; }\n.bpe-toolbar__group--start { overflow: hidden; }\n\n.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; }\n.bpe-save__text { max-width: 130px; overflow: hidden; text-overflow: ellipsis; }\n.bpe-save--muted, .bpe-save--clean { color: var(--bpe-fg-faint); }\n.bpe-save--dirty { color: var(--bpe-warning); background: var(--bpe-bg-muted); }\n.bpe-save--busy { color: var(--bpe-fg-secondary); }\n.bpe-save--error { color: var(--bpe-danger); border-color: var(--bpe-danger); }\n.bpe-save--action { cursor: pointer; }\n.bpe-save--action:hover { background: var(--bpe-bg-subtle); }\n.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; }\n\n.bpe-theme-select__name { max-width: 140px; }\n\n.bpe-issues { width: 340px; }\n.bpe-issues__group + .bpe-issues__group { border-top: 1px solid var(--bpe-border); margin-top: 6px; padding-top: 8px; }\n.bpe-issues__head { display: flex; align-items: center; gap: 6px; padding: 2px 10px 0; }\n.bpe-issues__title { font-size: var(--bpe-text-xs); font-weight: 600; text-transform: uppercase; letter-spacing: 0.04em; color: var(--bpe-fg-secondary); }\n.bpe-issues__blurb { margin: 2px 10px 6px; font-size: var(--bpe-text-xs); color: var(--bpe-fg-faint); line-height: 1.45; }\n.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; }\n.bpe-issues__ok strong { color: var(--bpe-fg-secondary); font-weight: 600; }\n.bpe-mode { width: auto; }\n.bpe-mode .bpe-segmented__item { padding: 0 12px; height: 24px; }\n.bpe-zoom { display: inline-flex; align-items: center; gap: 2px; }\n.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); }\n.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; }\n.bpe-theme-select:hover { border-color: var(--bpe-border-strong); background: var(--bpe-bg-subtle); }\n.bpe-swatches { display: inline-flex; gap: 2px; }\n.bpe-swatch { width: 10px; height: 10px; border-radius: 3px; box-shadow: inset 0 0 0 1px rgba(0,0,0,0.08); }\n\n/* ------------------------------------------------------------------ */\n/* Left panel */\n/* ------------------------------------------------------------------ */\n.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; }\n.bpe-left__body { flex: 1; min-height: 0; overflow-y: auto; overscroll-behavior: contain; }\n.bpe-search { position: relative; padding: 8px 10px 6px; }\n.bpe-search svg { position: absolute; left: 18px; top: 50%; transform: translateY(-50%); width: 13px; height: 13px; color: var(--bpe-fg-faint); pointer-events: none; }\n.bpe-search .bpe-input { padding-left: 26px; }\n.bpe-palette__group { padding: 4px 10px 10px; }\n.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); }\n.bpe-palette__grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 6px; }\n.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); }\n.bpe-palette__item:hover { border-color: var(--bpe-border-strong); background: var(--bpe-bg-subtle); transform: translateY(-1px); box-shadow: var(--bpe-shadow); }\n.bpe-palette__item:active { cursor: grabbing; transform: none; }\n.bpe-palette__item svg { width: 16px; height: 16px; color: var(--bpe-fg-secondary); }\n.bpe-palette__item-name { font-size: var(--bpe-text-sm); font-weight: 500; line-height: 1.2; }\n.bpe-palette__tier { position: absolute; top: 4px; right: 5px; font-size: 8.5px; font-weight: 700; letter-spacing: 0.06em; color: var(--bpe-accent); }\n.bpe-palette__item--locked { opacity: 0.55; cursor: not-allowed; }\n.bpe-palette__item--locked .bpe-palette__tier { color: var(--bpe-fg-faint); }\n.bpe-layouts__grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 6px; }\n.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; }\n.bpe-layout-item:hover { border-color: var(--bpe-border-strong); background: var(--bpe-bg-subtle); }\n.bpe-layout-item__cols { display: grid; gap: 3px; height: 22px; }\n.bpe-layout-item__col { background: var(--bpe-bg-muted); border: 1px solid var(--bpe-border-strong); border-radius: 3px; }\n.bpe-layout-item__name { font-size: var(--bpe-text-xs); color: var(--bpe-fg-secondary); font-weight: 500; }\n\n.bpe-data-tree { padding: 4px 6px 12px; }\n.bpe-data-source { margin-bottom: 4px; }\n.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; }\n.bpe-data-source__head:hover { background: var(--bpe-bg-subtle); }\n.bpe-data-source__head svg { width: 13px; height: 13px; color: var(--bpe-fg-faint); }\n.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; }\n.bpe-data-field:hover { background: var(--bpe-accent-soft); }\n.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); }\n.bpe-data-field__icon { width: 13px; height: 13px; color: var(--bpe-fg-muted); flex-shrink: 0; }\n.bpe-data-field--group { color: var(--bpe-fg-secondary); cursor: default; }\n.bpe-data-children { padding-left: 14px; border-left: 1px solid var(--bpe-border); margin-left: 12px; }\n\n.bpe-layers { padding: 6px 6px 12px; }\n.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; }\n.bpe-layer:hover { background: var(--bpe-bg-subtle); }\n.bpe-layer--selected { background: var(--bpe-accent-soft) !important; color: var(--bpe-accent); }\n.bpe-layer--hover { background: var(--bpe-bg-muted); }\n.bpe-layer { cursor: grab; }\n.bpe-layer:active { cursor: grabbing; }\n/* Row actions stay out of the way until the row is wanted, so the tree still\n reads as a tree rather than as a wall of buttons. */\n.bpe-layer__actions { display: none; margin-left: auto; gap: 0; flex-shrink: 0; }\n.bpe-layer:hover .bpe-layer__actions, .bpe-layer--selected .bpe-layer__actions { display: flex; }\n.bpe-layer__actions .bpe-btn { width: 20px; height: 20px; }\n.bpe-layer__actions .bpe-btn svg { width: 12px; height: 12px; }\n.bpe-layer:hover .bpe-layer__meta, .bpe-layer--selected .bpe-layer__meta { display: none; }\n\n.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; }\n.bpe-layer__toggle svg { width: 12px; height: 12px; transition: transform var(--bpe-dur) var(--bpe-ease); }\n.bpe-layer__toggle[aria-expanded=\"false\"] svg { transform: rotate(-90deg); }\n.bpe-layer__icon { width: 13px; height: 13px; color: var(--bpe-fg-muted); flex-shrink: 0; }\n.bpe-layer--selected .bpe-layer__icon { color: var(--bpe-accent); }\n.bpe-layer__name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n.bpe-layer__meta { font-size: var(--bpe-text-xs); color: var(--bpe-fg-faint); }\n\n.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); }\n.bpe-library__item:hover { border-color: var(--bpe-border-strong); background: var(--bpe-bg-subtle); }\n.bpe-library__item svg { width: 15px; height: 15px; color: var(--bpe-fg-secondary); }\n.bpe-library__name { font-weight: 500; font-size: var(--bpe-text-sm); }\n.bpe-library__desc { font-size: var(--bpe-text-xs); color: var(--bpe-fg-muted); }\n\n/* ------------------------------------------------------------------ */\n/* Canvas */\n/* ------------------------------------------------------------------ */\n.bpe-canvas { grid-area: canvas; position: relative; display: flex; flex-direction: column; min-width: 0; min-height: 0; background: var(--bpe-bg-canvas); }\n.bpe-canvas__scroll { position: relative; flex: 1; min-height: 0; overflow: auto; overscroll-behavior: contain; }\n.bpe-canvas__scroll:focus-visible { box-shadow: none; }\n.bpe-canvas__inner { position: relative; padding: 32px 40px 80px; display: flex; flex-direction: column; align-items: center; min-width: min-content; }\n.bpe-pages { position: relative; display: flex; flex-direction: column; transform-origin: top center; }\n.bpe-page-slot { position: relative; box-shadow: var(--bpe-shadow-page); background: #fff; }\n.bpe-page-slot--placeholder { background: #fff; opacity: 0.9; }\n.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; }\n.bpe-canvas--design .bp-page__region { outline: 1px dashed rgba(59, 91, 219, 0.22); outline-offset: -1px; }\n.bpe-canvas--design .bp-page__region:hover { outline-color: rgba(59, 91, 219, 0.5); }\n.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); }\n.bpe-canvas--design .bp-page__region:hover::before, .bpe-canvas--design .bp-page__region--active::before { opacity: 1; }\n.bpe-canvas--design .bp-page__footer::before { top: auto; bottom: -14px; }\n.bpe-canvas--design .bp-page__body { outline: 1px dashed transparent; }\n.bpe-canvas--design.bpe-canvas--guides .bp-page__body { outline-color: rgba(0,0,0,0.08); }\n.bpe-canvas--design .bp-node { cursor: default; }\n.bpe-canvas--design .bp-node--text .bp-text, .bpe-canvas--design .bp-heading { cursor: text; }\n.bpe-canvas--design .bp-node[data-editing] > * { visibility: hidden; }\n.bpe-canvas--design a { pointer-events: none; }\n.bpe-canvas--dragging, .bpe-canvas--dragging * { cursor: grabbing !important; user-select: none !important; }\n\n/* overlays */\n.bpe-overlay { position: absolute; inset: 0; pointer-events: none; z-index: 3; }\n.bpe-outline { position: absolute; border: 1.5px solid var(--bpe-hover-outline); border-radius: 2px; pointer-events: none; transition: none; }\n.bpe-outline--selected { border-color: var(--bpe-selection); border-width: 2px; box-shadow: 0 0 0 1px rgba(255,255,255,0.7) inset; }\n.bpe-outline--section { border-style: dashed; border-color: rgba(59,91,219,0.35); }\n.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; }\n.bpe-outline--hover .bpe-outline__label { background: var(--bpe-hover-outline); }\n.bpe-outline__label svg { width: 10px; height: 10px; }\n.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); }\n.bpe-block-toolbar .bpe-btn--icon { width: 24px; height: 24px; }\n.bpe-block-toolbar .bpe-btn svg { width: 13px; height: 13px; }\n.bpe-block-toolbar__handle { cursor: grab; }\n.bpe-block-toolbar__handle:active { cursor: grabbing; }\n.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; }\n/* The field list offered while a \"{{\" is open. Positioned in the overlay's\n coordinate space, so it tracks the caret rather than the block. */\n.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); }\n.bpe-suggest__item { display: flex; align-items: center; gap: 7px; min-width: 0; padding: 5px 8px; border-radius: var(--bpe-radius-sm); cursor: pointer; }\n.bpe-suggest__item--active { background: var(--bpe-accent-soft); }\n.bpe-suggest__icon { color: var(--bpe-fg-faint); flex-shrink: 0; }\n.bpe-suggest__item--active .bpe-suggest__icon { color: var(--bpe-accent); }\n.bpe-suggest__label { flex: 1; min-width: 0; color: var(--bpe-fg); }\n.bpe-suggest__expr { flex-shrink: 0; max-width: 45%; color: var(--bpe-fg-faint); font-family: var(--bpe-font-mono); font-size: 10.5px; }\n.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; }\n.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; }\n\n/* Where a click said the next block should go. Quieter than a drop line,\n because it is a standing intention rather than something happening now. */\n.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; }\n.bpe-caret::before { content: \"\"; position: absolute; left: -4px; top: -3px; width: 8px; height: 8px; border-radius: 50%; background: var(--bpe-accent); }\n@keyframes bpe-caret-blink { 0%, 100% { opacity: 0.85; } 50% { opacity: 0.35; } }\n@media (prefers-reduced-motion: reduce) { .bpe-caret { animation: none; } }\n\n.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; }\n.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; }\n.bpe-drop-line::before { left: -5px; }\n.bpe-drop-line::after { right: -5px; }\n.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; }\n.bpe-drop-zone--invalid { border-color: var(--bpe-danger); background: var(--bpe-danger-soft); }\n.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); }\n.bpe-drag-ghost svg { width: 14px; height: 14px; }\n.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; }\n.bpe-page-gap:hover { opacity: 1; }\n\n.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); }\n.bpe-breadcrumb { display: flex; align-items: center; gap: 2px; min-width: 0; overflow: hidden; }\n.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; }\n.bpe-breadcrumb__item:hover { background: var(--bpe-bg-muted); color: var(--bpe-fg); }\n.bpe-breadcrumb__item--current { color: var(--bpe-fg); font-weight: 600; }\n.bpe-breadcrumb__sep { color: var(--bpe-fg-faint); }\n.bpe-canvas__bar-end { margin-left: auto; display: flex; align-items: center; gap: 8px; }\n\n/* inline editor */\n.bpe-inline-editor { position: absolute; z-index: 6; outline: none; }\n.bpe-inline-editor .ProseMirror { outline: none; white-space: pre-wrap; word-wrap: break-word; min-height: 1em; }\n.bpe-inline-editor .ProseMirror p { margin: 0; }\n.bpe-inline-editor .ProseMirror ul, .bpe-inline-editor .ProseMirror ol { margin: 0; padding-left: 1.4em; }\n.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; }\n.bpe-inline-editor .ProseMirror .bp-token.ProseMirror-selectednode { outline: 2px solid var(--bpe-accent); }\n.bpe-inline-editor .ProseMirror-gapcursor { display: none; }\n.bpe-inline-editor__frame { position: absolute; inset: -4px; border: 1.5px solid var(--bpe-accent); border-radius: 3px; pointer-events: none; }\n.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); }\n.bpe-bubble .bpe-btn--icon { width: 26px; height: 26px; }\n.bpe-bubble__sep { width: 1px; height: 18px; margin: 0 2px; background: var(--bpe-border); }\n\n/* ------------------------------------------------------------------ */\n/* Inspector */\n/* ------------------------------------------------------------------ */\n.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; }\n.bpe-inspector__header { display: flex; align-items: center; gap: 8px; height: 44px; padding: 0 12px; border-bottom: 1px solid var(--bpe-border); }\n.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; }\n.bpe-inspector__icon svg { width: 14px; height: 14px; }\n.bpe-inspector__title { font-weight: 600; font-size: var(--bpe-text-md); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n.bpe-inspector__subtitle { font-size: var(--bpe-text-xs); color: var(--bpe-fg-muted); }\n.bpe-inspector__body { flex: 1; min-height: 0; overflow-y: auto; overscroll-behavior: contain; }\n.bpe-inspector__actions { margin-left: auto; display: flex; gap: 2px; }\n.bpe-spacing-grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 4px; }\n.bpe-spacing-grid .bpe-input { padding: 0 4px; text-align: center; }\n.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; }\n.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; }\n.bpe-color-btn:hover { border-color: var(--bpe-border-strong); }\n.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; }\n.bpe-color-btn__swatch::after { content: \"\"; display: block; width: 100%; height: 100%; border-radius: 4px; background: var(--c, transparent); }\n.bpe-color-btn__label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n.bpe-color-popover { width: 236px; }\n.bpe-color-grid { display: grid; grid-template-columns: repeat(8, 1fr); gap: 4px; padding: 4px; }\n.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; }\n.bpe-color-grid__item[aria-pressed=\"true\"] { box-shadow: 0 0 0 2px var(--bpe-bg), 0 0 0 4px var(--bpe-accent); }\n.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); }\n.bpe-color-token:hover { background: var(--bpe-bg-muted); }\n.bpe-color-token[aria-pressed=\"true\"] { background: var(--bpe-accent-soft); color: var(--bpe-accent); }\n.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); }\n.bpe-color-token__val { margin-left: auto; font-family: var(--bpe-font-mono); font-size: 10px; color: var(--bpe-fg-faint); }\n.bpe-binding { display: flex; align-items: center; gap: 4px; width: 100%; min-width: 0; }\n/* The image field's file input is opened by the button beside it. Hidden with\n clip rather than display:none so the browser still counts it as a real\n control and the button's click reaches it. */\n.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; }\n.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); }\n.bpe-binding__btn:hover { border-color: var(--bpe-border-strong); }\n.bpe-binding__btn--empty { color: var(--bpe-fg-faint); }\n.bpe-binding__btn svg { width: 13px; height: 13px; flex-shrink: 0; color: var(--bpe-fg-muted); }\n.bpe-expr { font-family: var(--bpe-font-mono); font-size: 11px; }\n.bpe-picker { width: 300px; max-height: 380px; display: flex; flex-direction: column; padding: 0; overflow: hidden; }\n.bpe-picker__search { padding: 8px; border-bottom: 1px solid var(--bpe-border); }\n.bpe-picker__list { flex: 1; overflow-y: auto; padding: 4px; }\n.bpe-picker__foot { display: flex; gap: 6px; align-items: center; padding: 8px; border-top: 1px solid var(--bpe-border); }\n.bpe-picker__foot .bpe-input { flex: 1; }\n.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); }\n.bpe-cond { display: flex; flex-direction: column; gap: 6px; min-width: 0; }\n.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); }\n.bpe-cond__row { display: grid; grid-template-columns: minmax(0, 1fr); gap: 4px; min-width: 0; }\n.bpe-cond__op { display: flex; align-items: center; gap: 6px; }\n.bpe-cond__group { padding: 6px; border: 1px dashed var(--bpe-border-strong); border-radius: var(--bpe-radius); }\n.bpe-list-editor { display: flex; flex-direction: column; gap: 6px; min-width: 0; }\n.bpe-list-editor__item { border: 1px solid var(--bpe-border); border-radius: var(--bpe-radius); background: var(--bpe-bg-subtle); }\n.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; }\n.bpe-list-editor__head .bpe-truncate { flex: 1 1 0; min-width: 0; }\n.bpe-list-editor__item { min-width: 0; }\n.bpe-list-editor__body { padding: 6px 8px 8px; border-top: 1px solid var(--bpe-border); display: flex; flex-direction: column; gap: 6px; }\n.bpe-list-editor__drag { cursor: grab; color: var(--bpe-fg-faint); display: inline-flex; }\n.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); }\n.bpe-issue:hover { background: var(--bpe-bg-muted); }\n.bpe-issue svg { width: 13px; height: 13px; flex-shrink: 0; margin-top: 2px; }\n.bpe-issue--error svg { color: var(--bpe-danger); }\n.bpe-issue--warning svg { color: var(--bpe-warning); }\n.bpe-issue--info svg { color: var(--bpe-fg-muted); }\n.bpe-issue__meta { display: block; margin-top: 2px; font-size: var(--bpe-text-xs); color: var(--bpe-fg-muted); }\n.bpe-text-style-row { display: grid; grid-template-columns: 1fr auto; align-items: center; gap: 6px; }\n.bpe-text-style-preview { font-weight: 600; }\n.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); }\n.bpe-doc-styles__item:hover { background: var(--bpe-bg-subtle); }\n.bpe-doc-styles__sample { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n.bpe-doc-styles__id { font-family: var(--bpe-font-mono); font-size: 10px; color: var(--bpe-fg-faint); }\n.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); }\n.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); }\n.bpe-toast svg { width: 14px; height: 14px; }\n.bpe-portal { position: absolute; width: 0; height: 0; overflow: visible; }\n.bpe-scope { font-family: var(--bpe-font); }\n\n.bpe-pager { display: inline-flex; align-items: center; gap: 2px; }\n.bpe-pager__label { min-width: 92px; text-align: center; font-variant-numeric: tabular-nums; }\n\n/* Panel resize handles sit on the boundary rather than in the grid, so the\n three-column shell stays three columns and the handles stay draggable even\n when a panel is scrolled. */\n.bpe-resizer { position: absolute; top: var(--bpe-toolbar-h); bottom: 0; width: 9px; cursor: col-resize; z-index: 6; background: transparent; touch-action: none; }\n.bpe-resizer--left { left: var(--bpe-panel-w); margin-left: -4px; }\n.bpe-resizer--right { right: var(--bpe-inspector-w); margin-right: -4px; }\n.bpe-resizer::after { content: \"\"; position: absolute; inset: 0 3px; border-radius: 2px; background: transparent; transition: background var(--bpe-dur) var(--bpe-ease); }\n.bpe-resizer:hover::after, .bpe-resizer:focus-visible::after { background: var(--bpe-accent); }\n.bpe-resizer:focus-visible { outline: none; }\n\n/* A long label stops being a column and becomes a line of its own. */\n.bpe-field--stacked .bpe-field__label { white-space: normal; overflow: visible; }\n\n.bpe-block-toolbar__edit { font-weight: 500; }\n\n.bpe-legend { width: 340px; }\n.bpe-legend__body { padding: 12px 14px 10px; }\n.bpe-legend__title { margin: 0 0 6px; font-size: var(--bpe-text-md); font-weight: 600; }\n.bpe-legend__intro { margin: 0 0 10px; font-size: var(--bpe-text-xs); color: var(--bpe-fg-secondary); line-height: 1.5; }\n.bpe-legend__list { margin: 0; display: flex; flex-direction: column; gap: 10px; }\n.bpe-legend__entry { display: flex; gap: 9px; align-items: flex-start; }\n.bpe-legend__swatch { flex: 0 0 auto; display: grid; place-items: center; min-width: 22px; padding-top: 1px; color: var(--bpe-fg-muted); }\n.bpe-legend__entry dt { font-size: var(--bpe-text-xs); font-weight: 600; color: var(--bpe-fg); }\n.bpe-legend__entry dd { margin: 1px 0 0; font-size: var(--bpe-text-xs); color: var(--bpe-fg-muted); line-height: 1.45; }\n.bpe-legend__token { font-size: 9px; padding: 1px 4px; }\n.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); }\n\n/* ===================================================================\n Narrow \u2014 the designer in a container too small for three columns.\n\n Set by the shell from the root element's own width, not a media query:\n the designer is embedded, so a phone and a narrow pane in a wide page\n are the same problem and the window answers neither. Below the\n breakpoint the panels stop being columns and become drawers over the\n canvas, because 272 + 296 of panel leaves a phone about forty pixels\n of page \u2014 which is not a cramped canvas, it is no canvas.\n =================================================================== */\n\n/* Two classes deliberately: `.bpe--no-left.bpe--no-right` is also two, and a\n single-class rule would lose to it exactly when both panels are shut \u2014\n which on a phone is the normal state. */\n.bpe.bpe--narrow { grid-template-columns: minmax(0, 1fr); grid-template-areas: \"toolbar\" \"canvas\"; }\n\n/* Both panels occupy the canvas cell rather than one of their own, so the\n canvas keeps its full width whether a drawer is open or not \u2014 nothing\n reflows underneath when one opens, which is what makes it a drawer\n rather than a column that happens to overlap. */\n.bpe--narrow .bpe-left, .bpe--narrow .bpe-inspector {\n grid-area: canvas;\n z-index: 20;\n width: min(88%, 360px);\n max-width: 100%;\n box-shadow: 0 24px 48px -24px rgba(0, 0, 0, 0.45);\n animation: bpe-drawer-in 180ms var(--bpe-ease) both;\n}\n.bpe--narrow .bpe-left { justify-self: start; border-right: 1px solid var(--bpe-border); }\n.bpe--narrow .bpe-inspector { justify-self: end; border-left: 1px solid var(--bpe-border); }\n@keyframes bpe-drawer-in { from { transform: translateX(var(--bpe-drawer-from, -8%)); opacity: 0; } to { transform: none; opacity: 1; } }\n.bpe--narrow .bpe-inspector { --bpe-drawer-from: 8%; }\n@media (prefers-reduced-motion: reduce) { .bpe--narrow .bpe-left, .bpe--narrow .bpe-inspector { animation: none; } }\n\n/* Tapping the canvas beside an open drawer closes it. A button rather than\n a div so it is reachable by keyboard and announces itself; it sits under\n the drawer and over everything else. */\n.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; }\n@keyframes bpe-fade-in { from { opacity: 0; } to { opacity: 1; } }\n@media (prefers-reduced-motion: reduce) { .bpe-scrim { animation: none; } }\n\n/* The page needs the room more than the margin around it does. */\n.bpe--narrow .bpe-canvas__inner { padding: 12px 12px 48px; }\n/* Touch targets, and a scroll that does not fight the page it is embedded in. */\n.bpe--narrow .bpe-btn { min-height: 32px; }\n.bpe--narrow .bpe-canvas__scroll { -webkit-overflow-scrolling: touch; }\n\n/* The toolbar's three tracks assume a row wide enough for all of them. On a\n phone the centre group alone \u2014 Design/Preview plus the zoom controls, both\n of which refuse to wrap \u2014 takes the whole width, leaving the leading and\n trailing tracks measuring zero and their contents drawn off the edge. So\n narrow, it wraps: the groups keep their order and their contents, and the\n toolbar takes the two rows it needs. `.bpe` gives it an auto row to grow\n into rather than the fixed height a single row assumed. */\n.bpe.bpe--narrow { grid-template-rows: auto 1fr; }\n.bpe--narrow .bpe-toolbar { display: flex; flex-wrap: wrap; justify-content: space-between; row-gap: 6px; padding: 6px 8px; }\n.bpe--narrow .bpe-toolbar__group--start { overflow: visible; flex: 1 1 auto; }\n.bpe--narrow .bpe-toolbar__group--centre { order: 3; flex: 1 0 100%; justify-content: center; }\n.bpe--narrow .bpe-toolbar__group--end { flex: 0 0 auto; }\n/* The report name gives up its fixed width first; it is the one thing in the\n leading group that can shrink without losing a control. */\n.bpe--narrow .bpe-toolbar__name { width: auto; min-width: 80px; max-width: none; flex: 1 1 80px; }\n/* A backstop for the very narrowest: if the mode switch and the zoom controls\n still will not sit on one row, they take two rather than one of them being\n drawn off the edge. */\n.bpe--narrow .bpe-toolbar__group--centre { flex-wrap: wrap; white-space: normal; row-gap: 6px; }\n";
|
|
407
|
+
declare const EDITOR_CSS = "/* BroadPaper editor design tokens.\n `.bpe` is the editor shell; `.bpe-scope` is a token-only scope used by portalled\n floating elements (tooltips, popovers) so they inherit the palette WITHOUT the\n shell's grid layout, size and background. */\n.bpe,\n.bpe-scope {\n /* colour */\n --bpe-bg: #ffffff;\n --bpe-bg-subtle: #f7f8fa;\n --bpe-bg-muted: #eef0f4;\n --bpe-bg-canvas: #e9ebf0;\n --bpe-bg-elevated: #ffffff;\n --bpe-fg: #111827;\n --bpe-fg-secondary: #4b5563;\n --bpe-fg-muted: #6b7280;\n --bpe-fg-faint: #9ca3af;\n --bpe-border: #e5e7eb;\n --bpe-border-strong: #d1d5db;\n --bpe-accent: #3b5bdb;\n --bpe-accent-hover: #3451c7;\n --bpe-accent-soft: rgba(59, 91, 219, 0.1);\n --bpe-accent-soft-strong: rgba(59, 91, 219, 0.18);\n --bpe-accent-fg: #ffffff;\n\n /* The one filled call to action \u2014 Export, and any host button asking for\n `variant=\"primary\"`. Separate from the accent, and defaulting to it, because\n a host wanting its brand on that button rarely wants the same colour on\n selection outlines, focus rings and the active tab: BroadPaper's own is a\n vermilion that reads as an error the moment it outlines a selected block,\n and `--bpe-danger` is nearly the same red. Set this alone and the button is\n yours while everything that means \"this is selected\" stays legible. */\n --bpe-primary: var(--bpe-accent);\n --bpe-primary-hover: var(--bpe-accent-hover);\n --bpe-primary-fg: var(--bpe-accent-fg);\n\n --bpe-danger: #dc2626;\n --bpe-danger-soft: rgba(220, 38, 38, 0.1);\n --bpe-warning: #d97706;\n --bpe-warning-soft: rgba(217, 119, 6, 0.12);\n --bpe-success: #059669;\n --bpe-success-soft: rgba(5, 150, 105, 0.12);\n --bpe-token: #2f4f7f;\n --bpe-token-bg: rgba(79, 111, 165, 0.14);\n --bpe-selection: var(--bpe-accent);\n --bpe-hover-outline: rgba(59, 91, 219, 0.45);\n --bpe-drop: #10b981;\n\n /* typography */\n --bpe-font: Inter, ui-sans-serif, system-ui, -apple-system, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif;\n --bpe-font-mono: \"JetBrains Mono\", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n --bpe-text-xs: 10.5px;\n --bpe-text-sm: 11.5px;\n --bpe-text-md: 12.5px;\n --bpe-text-lg: 14px;\n --bpe-text-xl: 16px;\n\n /* geometry */\n --bpe-radius-sm: 4px;\n --bpe-radius: 6px;\n --bpe-radius-lg: 10px;\n --bpe-space-1: 4px;\n --bpe-space-2: 8px;\n --bpe-space-3: 12px;\n --bpe-space-4: 16px;\n --bpe-space-5: 20px;\n --bpe-space-6: 24px;\n --bpe-control-h: 28px;\n --bpe-control-h-sm: 24px;\n --bpe-panel-w: 272px;\n --bpe-inspector-w: 296px;\n --bpe-toolbar-h: 48px;\n\n /* effects */\n --bpe-shadow-sm: 0 1px 2px rgba(16, 24, 40, 0.06);\n --bpe-shadow: 0 1px 3px rgba(16, 24, 40, 0.1), 0 1px 2px rgba(16, 24, 40, 0.06);\n --bpe-shadow-lg: 0 10px 30px rgba(16, 24, 40, 0.16), 0 2px 6px rgba(16, 24, 40, 0.08);\n --bpe-shadow-page: 0 1px 2px rgba(16, 24, 40, 0.1), 0 12px 32px rgba(16, 24, 40, 0.1);\n --bpe-focus: 0 0 0 2px var(--bpe-bg), 0 0 0 4px var(--bpe-accent);\n --bpe-ease: cubic-bezier(0.2, 0, 0, 1);\n --bpe-dur: 140ms;\n}\n\n.bpe[data-appearance=\"dark\"],\n.bpe-scope[data-appearance=\"dark\"] {\n --bpe-bg: #17191e;\n --bpe-bg-subtle: #1d2026;\n --bpe-bg-muted: #23262d;\n --bpe-bg-canvas: #101114;\n --bpe-bg-elevated: #1f2228;\n --bpe-fg: #e7e9ee;\n --bpe-fg-secondary: #b9bec8;\n --bpe-fg-muted: #8b919c;\n --bpe-fg-faint: #5e636d;\n --bpe-border: #2b2f37;\n --bpe-border-strong: #3a3f49;\n --bpe-accent: #6b8cff;\n --bpe-accent-hover: #7e9bff;\n --bpe-accent-soft: rgba(107, 140, 255, 0.16);\n --bpe-accent-soft-strong: rgba(107, 140, 255, 0.28);\n --bpe-accent-fg: #0e1220;\n --bpe-token: #a9c0ff;\n --bpe-token-bg: rgba(107, 140, 255, 0.2);\n --bpe-hover-outline: rgba(107, 140, 255, 0.55);\n --bpe-shadow: 0 1px 3px rgba(0, 0, 0, 0.4), 0 1px 2px rgba(0, 0, 0, 0.3);\n --bpe-shadow-lg: 0 10px 30px rgba(0, 0, 0, 0.5), 0 2px 6px rgba(0, 0, 0, 0.3);\n --bpe-shadow-page: 0 1px 2px rgba(0, 0, 0, 0.4), 0 12px 32px rgba(0, 0, 0, 0.4);\n}\n\n/* ===================================================================\n BroadPaper editor \u2014 component styles. Scoped under .bpe.\n =================================================================== */\n\n.bpe, .bpe *, .bpe *::before, .bpe *::after,\n.bpe-scope, .bpe-scope *, .bpe-scope *::before, .bpe-scope *::after { box-sizing: border-box; }\n.bpe {\n position: relative;\n display: grid;\n grid-template-rows: var(--bpe-toolbar-h) 1fr;\n grid-template-columns: var(--bpe-panel-w) minmax(0, 1fr) var(--bpe-inspector-w);\n grid-template-areas: \"toolbar toolbar toolbar\" \"left canvas right\";\n width: 100%;\n height: 100%;\n min-height: 480px;\n background: var(--bpe-bg);\n color: var(--bpe-fg);\n font-family: var(--bpe-font);\n font-size: var(--bpe-text-md);\n line-height: 1.4;\n -webkit-font-smoothing: antialiased;\n overflow: hidden;\n border: 1px solid var(--bpe-border);\n border-radius: var(--bpe-radius-lg);\n}\n.bpe--no-left { grid-template-columns: 0 minmax(0, 1fr) var(--bpe-inspector-w); }\n.bpe--no-right { grid-template-columns: var(--bpe-panel-w) minmax(0, 1fr) 0; }\n.bpe--no-left.bpe--no-right { grid-template-columns: 0 minmax(0, 1fr) 0; }\n.bpe--no-toolbar { grid-template-rows: 0 1fr; }\n.bpe button, .bpe input, .bpe select, .bpe textarea,\n.bpe-scope button, .bpe-scope input, .bpe-scope select, .bpe-scope textarea { font: inherit; color: inherit; }\n.bpe button, .bpe-scope button { cursor: pointer; }\n.bpe :focus-visible, .bpe-scope :focus-visible { outline: none; box-shadow: var(--bpe-focus); }\n.bpe ::selection, .bpe-scope ::selection { background: var(--bpe-accent-soft-strong); }\n.bpe-sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }\n.bpe-mono { font-family: var(--bpe-font-mono); }\n.bpe-muted { color: var(--bpe-fg-muted); }\n.bpe-truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n\n/* scrollbars */\n.bpe *::-webkit-scrollbar, .bpe-scope *::-webkit-scrollbar { width: 10px; height: 10px; }\n.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; }\n.bpe *::-webkit-scrollbar-thumb:hover { background: var(--bpe-fg-faint); background-clip: padding-box; border: 3px solid transparent; }\n.bpe *::-webkit-scrollbar-track { background: transparent; }\n\n/* ------------------------------------------------------------------ */\n/* Primitives */\n/* ------------------------------------------------------------------ */\n.bpe-btn {\n display: inline-flex; align-items: center; justify-content: center; gap: 6px;\n height: var(--bpe-control-h); padding: 0 10px; border-radius: var(--bpe-radius);\n border: 1px solid var(--bpe-border); background: var(--bpe-bg); color: var(--bpe-fg);\n font-size: var(--bpe-text-sm); font-weight: 500; white-space: nowrap; user-select: none; flex-shrink: 0;\n 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);\n}\n.bpe-btn:hover { background: var(--bpe-bg-subtle); border-color: var(--bpe-border-strong); }\n.bpe-btn:active { transform: translateY(0.5px); }\n.bpe-btn:disabled { opacity: 0.45; cursor: not-allowed; transform: none; }\n.bpe-btn--primary { background: var(--bpe-primary); border-color: var(--bpe-primary); color: var(--bpe-primary-fg); }\n.bpe-btn--primary:hover { background: var(--bpe-primary-hover); border-color: var(--bpe-primary-hover); }\n.bpe-btn--ghost { border-color: transparent; background: transparent; }\n.bpe-btn--ghost:hover { background: var(--bpe-bg-muted); border-color: transparent; }\n.bpe-btn--danger { color: var(--bpe-danger); }\n.bpe-btn--danger:hover { background: var(--bpe-danger-soft); border-color: transparent; }\n.bpe-btn--sm { height: var(--bpe-control-h-sm); padding: 0 8px; font-size: var(--bpe-text-xs); }\n.bpe-btn--icon { width: var(--bpe-control-h); padding: 0; }\n.bpe-btn--sm.bpe-btn--icon { width: var(--bpe-control-h-sm); }\n.bpe-btn--active { background: var(--bpe-accent-soft); border-color: transparent; color: var(--bpe-accent); }\n.bpe-btn svg { width: 15px; height: 15px; flex-shrink: 0; }\n.bpe-btn--sm svg { width: 13px; height: 13px; }\n\n.bpe-input, .bpe-select, .bpe-textarea {\n width: 100%; height: var(--bpe-control-h); padding: 0 8px; border-radius: var(--bpe-radius);\n border: 1px solid var(--bpe-border); background: var(--bpe-bg); color: var(--bpe-fg); font-size: var(--bpe-text-sm);\n transition: border-color var(--bpe-dur) var(--bpe-ease), box-shadow var(--bpe-dur) var(--bpe-ease);\n}\n.bpe-input::placeholder, .bpe-textarea::placeholder { color: var(--bpe-fg-faint); }\n.bpe-input:hover, .bpe-select:hover, .bpe-textarea:hover { border-color: var(--bpe-border-strong); }\n.bpe-input:focus, .bpe-select:focus, .bpe-textarea:focus { border-color: var(--bpe-accent); box-shadow: 0 0 0 3px var(--bpe-accent-soft); }\n.bpe-input--invalid { border-color: var(--bpe-danger) !important; }\n.bpe-textarea { height: auto; min-height: 56px; padding: 6px 8px; resize: vertical; line-height: 1.45; }\n.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; }\n/* `width: 100%`, because a flex container sizes to its content and this one holds\n an input. Without it every field with a unit or a suffix \u2014 a width in `%`, a\n size in `px` \u2014 shrank to about twenty characters and left the rest of the row\n blank, while the plain inputs beside it filled. */\n.bpe-input-group { position: relative; display: flex; align-items: center; width: 100%; }\n.bpe-input-group .bpe-input { padding-right: 28px; }\n.bpe-input-group__suffix { position: absolute; right: 8px; font-size: var(--bpe-text-xs); color: var(--bpe-fg-faint); pointer-events: none; }\n.bpe-input-group__btn { position: absolute; right: 2px; top: 2px; }\n.bpe-number { display: flex; align-items: center; }\n.bpe-number .bpe-input { text-align: left; }\n\n.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; }\n.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); }\n.bpe-switch[aria-checked=\"true\"] { background: var(--bpe-accent); }\n.bpe-switch[aria-checked=\"true\"]::after { transform: translateX(12px); }\n\n.bpe-segmented { display: inline-flex; padding: 2px; border-radius: var(--bpe-radius); background: var(--bpe-bg-muted); gap: 2px; width: 100%; }\n.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; }\n.bpe-segmented__item:hover { color: var(--bpe-fg); }\n.bpe-segmented__item[aria-pressed=\"true\"] { background: var(--bpe-bg-elevated); color: var(--bpe-fg); box-shadow: var(--bpe-shadow-sm); }\n.bpe-segmented__item svg { width: 13px; height: 13px; }\n\n/* Five named tabs will not fit side by side in a 272px panel, so they stack\n the label under the icon and share the row evenly. Names beat guessing at\n icons; a second row of chrome does not. */\n.bpe-tabs { display: flex; gap: 2px; padding: 6px 6px 0; border-bottom: 1px solid var(--bpe-border); }\n.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; }\n.bpe-tab > span { max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n.bpe-tab:hover { color: var(--bpe-fg); background: var(--bpe-bg-subtle); }\n.bpe-tab[aria-selected=\"true\"] { color: var(--bpe-fg); }\n.bpe-tab[aria-selected=\"true\"] { background: var(--bpe-bg-subtle); }\n.bpe-tab[aria-selected=\"true\"]::after { content: \"\"; position: absolute; left: 6px; right: 6px; bottom: -1px; height: 2px; background: var(--bpe-accent); border-radius: 2px; }\n.bpe-tab svg { width: 14px; height: 14px; }\n\n.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); }\n/* Entrance runs only once floating-ui has positioned the element, so it never\n animates from the viewport corner. */\n.bpe-floating--in { animation: bpe-pop var(--bpe-dur) var(--bpe-ease); }\n@keyframes bpe-pop { from { opacity: 0; transform: translateY(-3px) scale(0.98); } to { opacity: 1; transform: none; } }\n.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); }\n.bpe-menu__item--stacked { align-items: flex-start; padding: 6px 8px; }\n.bpe-menu__item--stacked svg { margin-top: 2px; }\n.bpe-menu__body { display: flex; flex-direction: column; gap: 1px; min-width: 0; flex: 1; }\n.bpe-menu__label-text { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n.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; }\n.bpe-menu__item:hover, .bpe-menu__item:focus-visible { background: var(--bpe-bg-muted); box-shadow: none; }\n.bpe-menu__item:disabled { opacity: 0.45; }\n.bpe-menu__item svg { width: 14px; height: 14px; color: var(--bpe-fg-muted); }\n.bpe-menu__item--danger { color: var(--bpe-danger); }\n.bpe-menu__item--danger svg { color: var(--bpe-danger); }\n.bpe-menu__sep { height: 1px; margin: 4px 0; background: var(--bpe-border); }\n.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); }\n.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; }\n/* The portal is a zero-width positioned box, which is the containing block for\n everything floating inside it. A shrink-to-fit element therefore has no room\n at all and wraps after every word; `max-content` is what stops that. */\n.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; }\n.bpe-tooltip .bpe-kbd { margin-left: 6px; background: rgba(255,255,255,0.14); color: #fff; border-color: transparent; }\n.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; }\n.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; }\n.bpe-badge--accent { background: var(--bpe-accent-soft); color: var(--bpe-accent); }\n.bpe-badge--warning { background: var(--bpe-warning-soft); color: var(--bpe-warning); }\n.bpe-badge--danger { background: var(--bpe-danger-soft); color: var(--bpe-danger); }\n.bpe-badge--success { background: var(--bpe-success-soft); color: var(--bpe-success); }\n.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%; }\n.bpe-chip svg { width: 11px; height: 11px; }\n.bpe-chip__x { margin-left: 2px; border: 0; background: transparent; padding: 0; color: inherit; display: inline-flex; opacity: 0.6; }\n.bpe-chip__x:hover { opacity: 1; }\n.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); }\n.bpe-empty svg { width: 22px; height: 22px; color: var(--bpe-fg-faint); }\n\n/* One region's failure, contained. See ui/Boundary.tsx. */\n.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); }\n.bpe-boundary > svg { flex-shrink: 0; margin-top: 1px; color: var(--bpe-warning); }\n.bpe-boundary__body { flex: 1; min-width: 0; }\n.bpe-boundary__title { font-weight: 600; }\n.bpe-boundary__detail { margin-top: 2px; color: var(--bpe-fg-muted); }\n\n/* Where it went. Applied to a block the canvas has just scrolled to, so an\n insertion that landed on another page is visibly an insertion and not a\n jump. Motion-sensitive users get the tint without the pulse. */\n@keyframes bpe-flash {\n 0% { box-shadow: 0 0 0 2px var(--bpe-accent), 0 0 0 7px var(--bpe-accent-soft); background-color: var(--bpe-accent-soft); }\n 70% { box-shadow: 0 0 0 2px var(--bpe-accent), 0 0 0 7px var(--bpe-accent-soft); background-color: var(--bpe-accent-soft); }\n 100% { box-shadow: 0 0 0 0 transparent, 0 0 0 0 transparent; background-color: transparent; }\n}\n.bpe-flash { animation: bpe-flash 1.2s ease-out both; border-radius: 3px; }\n@media (prefers-reduced-motion: reduce) {\n .bpe-flash { animation: none; background-color: var(--bpe-accent-soft); box-shadow: 0 0 0 2px var(--bpe-accent); }\n}\n.bpe-empty__title { font-weight: 600; color: var(--bpe-fg-secondary); }\n\n/* form field */\n.bpe-field { display: grid; grid-template-columns: 96px minmax(0, 1fr); align-items: center; gap: 6px 10px; min-height: var(--bpe-control-h); }\n.bpe-field--stacked { grid-template-columns: 1fr; gap: 4px; }\n.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; }\n.bpe-field__control { min-width: 0; display: flex; align-items: center; gap: 4px; }\n.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; }\n.bpe-field__error { grid-column: 1 / -1; font-size: var(--bpe-text-xs); color: var(--bpe-danger); }\n.bpe-prop-grid { display: flex; flex-direction: column; gap: 8px; padding: 10px 12px; }\n.bpe-section { border-bottom: 1px solid var(--bpe-border); }\n.bpe-section__head { display: flex; align-items: center; gap: 4px; width: 100%; height: 34px; padding: 0 8px 0 0; }\n.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; }\n.bpe-section__toggle:hover { background: var(--bpe-bg-subtle); }\n.bpe-section__toggle svg { width: 13px; height: 13px; color: var(--bpe-fg-faint); transition: transform var(--bpe-dur) var(--bpe-ease); }\n.bpe-section__toggle[aria-expanded=\"false\"] svg.bpe-section__chev { transform: rotate(-90deg); }\n.bpe-section__toggle .bpe-section__chev { margin-left: auto; }\n.bpe-section__actions { display: inline-flex; }\n.bpe-section__body { padding-bottom: 4px; }\n\n/* ------------------------------------------------------------------ */\n/* Toolbar */\n/* ------------------------------------------------------------------ */\n/* The side tracks must be allowed to shrink. As plain `1fr` they refuse to go\n below their own content, so the end group overflowed leftwards and drew the\n brand picker on top of the Preview button instead of giving anything up. */\n.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; }\n.bpe-toolbar__group { display: flex; align-items: center; gap: 4px; min-width: 0; }\n.bpe-toolbar__group--end { justify-content: flex-end; }\n.bpe-toolbar__sep { width: 1px; height: 20px; margin: 0 6px; background: var(--bpe-border); }\n.bpe-toolbar__brand { display: flex; align-items: center; gap: 8px; margin-right: 6px; min-width: 0; }\n/* The product mark. No tile behind it: the sheet is the shape, and a tile\n would make it a second rectangle inside a rectangle. */\n.bpe-toolbar__mark { display: grid; place-items: center; flex-shrink: 0; }\n.bpe-toolbar__mark svg { width: 22px; height: 18px; display: block; }\n.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); }\n.bpe-toolbar__name:hover { border-color: var(--bpe-border); }\n.bpe-toolbar__name:focus { border-color: var(--bpe-accent); box-shadow: 0 0 0 3px var(--bpe-accent-soft); }\n/* The centre group never gives up room; the ends shrink around it. */\n.bpe-toolbar__group--centre { flex: 0 0 auto; white-space: nowrap; }\n.bpe-toolbar__group--start { overflow: hidden; }\n\n.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; }\n.bpe-save__text { max-width: 130px; overflow: hidden; text-overflow: ellipsis; }\n.bpe-save--muted, .bpe-save--clean { color: var(--bpe-fg-faint); }\n.bpe-save--dirty { color: var(--bpe-warning); background: var(--bpe-bg-muted); }\n.bpe-save--busy { color: var(--bpe-fg-secondary); }\n.bpe-save--error { color: var(--bpe-danger); border-color: var(--bpe-danger); }\n.bpe-save--action { cursor: pointer; }\n.bpe-save--action:hover { background: var(--bpe-bg-subtle); }\n.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; }\n\n.bpe-theme-select__name { max-width: 140px; }\n\n.bpe-issues { width: 340px; }\n.bpe-issues__group + .bpe-issues__group { border-top: 1px solid var(--bpe-border); margin-top: 6px; padding-top: 8px; }\n.bpe-issues__head { display: flex; align-items: center; gap: 6px; padding: 2px 10px 0; }\n.bpe-issues__title { font-size: var(--bpe-text-xs); font-weight: 600; text-transform: uppercase; letter-spacing: 0.04em; color: var(--bpe-fg-secondary); }\n.bpe-issues__blurb { margin: 2px 10px 6px; font-size: var(--bpe-text-xs); color: var(--bpe-fg-faint); line-height: 1.45; }\n.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; }\n.bpe-issues__ok strong { color: var(--bpe-fg-secondary); font-weight: 600; }\n.bpe-mode { width: auto; }\n.bpe-mode .bpe-segmented__item { padding: 0 12px; height: 24px; }\n.bpe-zoom { display: inline-flex; align-items: center; gap: 2px; }\n.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); }\n.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; }\n.bpe-theme-select:hover { border-color: var(--bpe-border-strong); background: var(--bpe-bg-subtle); }\n.bpe-swatches { display: inline-flex; gap: 2px; }\n.bpe-swatch { width: 10px; height: 10px; border-radius: 3px; box-shadow: inset 0 0 0 1px rgba(0,0,0,0.08); }\n\n/* ------------------------------------------------------------------ */\n/* Left panel */\n/* ------------------------------------------------------------------ */\n.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; }\n.bpe-left__body { flex: 1; min-height: 0; overflow-y: auto; overscroll-behavior: contain; }\n.bpe-search { position: relative; padding: 8px 10px 6px; }\n.bpe-search svg { position: absolute; left: 18px; top: 50%; transform: translateY(-50%); width: 13px; height: 13px; color: var(--bpe-fg-faint); pointer-events: none; }\n.bpe-search .bpe-input { padding-left: 26px; }\n.bpe-palette__group { padding: 4px 10px 10px; }\n.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); }\n.bpe-palette__grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 6px; }\n.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); }\n.bpe-palette__item:hover { border-color: var(--bpe-border-strong); background: var(--bpe-bg-subtle); transform: translateY(-1px); box-shadow: var(--bpe-shadow); }\n.bpe-palette__item:active { cursor: grabbing; transform: none; }\n.bpe-palette__item svg { width: 16px; height: 16px; color: var(--bpe-fg-secondary); }\n.bpe-palette__item-name { font-size: var(--bpe-text-sm); font-weight: 500; line-height: 1.2; }\n.bpe-palette__tier { position: absolute; top: 4px; right: 5px; font-size: 8.5px; font-weight: 700; letter-spacing: 0.06em; color: var(--bpe-accent); }\n.bpe-palette__item--locked { opacity: 0.55; cursor: not-allowed; }\n.bpe-palette__item--locked .bpe-palette__tier { color: var(--bpe-fg-faint); }\n.bpe-layouts__grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 6px; }\n.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; }\n.bpe-layout-item:hover { border-color: var(--bpe-border-strong); background: var(--bpe-bg-subtle); }\n.bpe-layout-item__cols { display: grid; gap: 3px; height: 22px; }\n.bpe-layout-item__col { background: var(--bpe-bg-muted); border: 1px solid var(--bpe-border-strong); border-radius: 3px; }\n.bpe-layout-item__name { font-size: var(--bpe-text-xs); color: var(--bpe-fg-secondary); font-weight: 500; }\n\n.bpe-data-tree { padding: 4px 6px 12px; }\n.bpe-data-source { margin-bottom: 4px; }\n.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; }\n.bpe-data-source__head:hover { background: var(--bpe-bg-subtle); }\n.bpe-data-source__head svg { width: 13px; height: 13px; color: var(--bpe-fg-faint); }\n.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; }\n.bpe-data-field:hover { background: var(--bpe-accent-soft); }\n.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); }\n.bpe-data-field__icon { width: 13px; height: 13px; color: var(--bpe-fg-muted); flex-shrink: 0; }\n.bpe-data-field--group { color: var(--bpe-fg-secondary); cursor: default; }\n.bpe-data-children { padding-left: 14px; border-left: 1px solid var(--bpe-border); margin-left: 12px; }\n\n.bpe-layers { padding: 6px 6px 12px; }\n.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; }\n.bpe-layer:hover { background: var(--bpe-bg-subtle); }\n.bpe-layer--selected { background: var(--bpe-accent-soft) !important; color: var(--bpe-accent); }\n.bpe-layer--hover { background: var(--bpe-bg-muted); }\n.bpe-layer { cursor: grab; }\n.bpe-layer:active { cursor: grabbing; }\n/* Row actions stay out of the way until the row is wanted, so the tree still\n reads as a tree rather than as a wall of buttons. */\n.bpe-layer__actions { display: none; margin-left: auto; gap: 0; flex-shrink: 0; }\n.bpe-layer:hover .bpe-layer__actions, .bpe-layer--selected .bpe-layer__actions { display: flex; }\n.bpe-layer__actions .bpe-btn { width: 20px; height: 20px; }\n.bpe-layer__actions .bpe-btn svg { width: 12px; height: 12px; }\n.bpe-layer:hover .bpe-layer__meta, .bpe-layer--selected .bpe-layer__meta { display: none; }\n\n.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; }\n.bpe-layer__toggle svg { width: 12px; height: 12px; transition: transform var(--bpe-dur) var(--bpe-ease); }\n.bpe-layer__toggle[aria-expanded=\"false\"] svg { transform: rotate(-90deg); }\n.bpe-layer__icon { width: 13px; height: 13px; color: var(--bpe-fg-muted); flex-shrink: 0; }\n.bpe-layer--selected .bpe-layer__icon { color: var(--bpe-accent); }\n.bpe-layer__name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n.bpe-layer__meta { font-size: var(--bpe-text-xs); color: var(--bpe-fg-faint); }\n\n.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); }\n.bpe-library__item:hover { border-color: var(--bpe-border-strong); background: var(--bpe-bg-subtle); }\n.bpe-library__item svg { width: 15px; height: 15px; color: var(--bpe-fg-secondary); }\n.bpe-library__name { font-weight: 500; font-size: var(--bpe-text-sm); }\n.bpe-library__desc { font-size: var(--bpe-text-xs); color: var(--bpe-fg-muted); }\n\n/* ------------------------------------------------------------------ */\n/* Canvas */\n/* ------------------------------------------------------------------ */\n.bpe-canvas { grid-area: canvas; position: relative; display: flex; flex-direction: column; min-width: 0; min-height: 0; background: var(--bpe-bg-canvas); }\n.bpe-canvas__scroll { position: relative; flex: 1; min-height: 0; overflow: auto; overscroll-behavior: contain; }\n.bpe-canvas__scroll:focus-visible { box-shadow: none; }\n.bpe-canvas__inner { position: relative; padding: 32px 40px 80px; display: flex; flex-direction: column; align-items: center; min-width: min-content; }\n.bpe-pages { position: relative; display: flex; flex-direction: column; transform-origin: top center; }\n.bpe-page-slot { position: relative; box-shadow: var(--bpe-shadow-page); background: #fff; }\n.bpe-page-slot--placeholder { background: #fff; opacity: 0.9; }\n.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; }\n.bpe-canvas--design .bp-page__region { outline: 1px dashed rgba(59, 91, 219, 0.22); outline-offset: -1px; }\n.bpe-canvas--design .bp-page__region:hover { outline-color: rgba(59, 91, 219, 0.5); }\n.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); }\n.bpe-canvas--design .bp-page__region:hover::before, .bpe-canvas--design .bp-page__region--active::before { opacity: 1; }\n.bpe-canvas--design .bp-page__footer::before { top: auto; bottom: -14px; }\n.bpe-canvas--design .bp-page__body { outline: 1px dashed transparent; }\n.bpe-canvas--design.bpe-canvas--guides .bp-page__body { outline-color: rgba(0,0,0,0.08); }\n.bpe-canvas--design .bp-node { cursor: default; }\n.bpe-canvas--design .bp-node--text .bp-text, .bpe-canvas--design .bp-heading { cursor: text; }\n.bpe-canvas--design .bp-node[data-editing] > * { visibility: hidden; }\n.bpe-canvas--design a { pointer-events: none; }\n.bpe-canvas--dragging, .bpe-canvas--dragging * { cursor: grabbing !important; user-select: none !important; }\n\n/* overlays */\n.bpe-overlay { position: absolute; inset: 0; pointer-events: none; z-index: 3; }\n.bpe-outline { position: absolute; border: 1.5px solid var(--bpe-hover-outline); border-radius: 2px; pointer-events: none; transition: none; }\n.bpe-outline--selected { border-color: var(--bpe-selection); border-width: 2px; box-shadow: 0 0 0 1px rgba(255,255,255,0.7) inset; }\n.bpe-outline--section { border-style: dashed; border-color: rgba(59,91,219,0.35); }\n.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; }\n.bpe-outline--hover .bpe-outline__label { background: var(--bpe-hover-outline); }\n.bpe-outline__label svg { width: 10px; height: 10px; }\n.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); }\n.bpe-block-toolbar .bpe-btn--icon { width: 24px; height: 24px; }\n.bpe-block-toolbar .bpe-btn svg { width: 13px; height: 13px; }\n.bpe-block-toolbar__handle { cursor: grab; }\n.bpe-block-toolbar__handle:active { cursor: grabbing; }\n.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; }\n/* The field list offered while a \"{{\" is open. Positioned in the overlay's\n coordinate space, so it tracks the caret rather than the block. */\n.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); }\n.bpe-suggest__item { display: flex; align-items: center; gap: 7px; min-width: 0; padding: 5px 8px; border-radius: var(--bpe-radius-sm); cursor: pointer; }\n.bpe-suggest__item--active { background: var(--bpe-accent-soft); }\n.bpe-suggest__icon { color: var(--bpe-fg-faint); flex-shrink: 0; }\n.bpe-suggest__item--active .bpe-suggest__icon { color: var(--bpe-accent); }\n.bpe-suggest__label { flex: 1; min-width: 0; color: var(--bpe-fg); }\n.bpe-suggest__expr { flex-shrink: 0; max-width: 45%; color: var(--bpe-fg-faint); font-family: var(--bpe-font-mono); font-size: 10.5px; }\n.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; }\n.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; }\n\n/* Where a click said the next block should go. Quieter than a drop line,\n because it is a standing intention rather than something happening now. */\n.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; }\n.bpe-caret::before { content: \"\"; position: absolute; left: -4px; top: -3px; width: 8px; height: 8px; border-radius: 50%; background: var(--bpe-accent); }\n@keyframes bpe-caret-blink { 0%, 100% { opacity: 0.85; } 50% { opacity: 0.35; } }\n@media (prefers-reduced-motion: reduce) { .bpe-caret { animation: none; } }\n\n.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; }\n.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; }\n.bpe-drop-line::before { left: -5px; }\n.bpe-drop-line::after { right: -5px; }\n.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; }\n.bpe-drop-zone--invalid { border-color: var(--bpe-danger); background: var(--bpe-danger-soft); }\n.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); }\n.bpe-drag-ghost svg { width: 14px; height: 14px; }\n.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; }\n.bpe-page-gap:hover { opacity: 1; }\n\n.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); }\n.bpe-breadcrumb { display: flex; align-items: center; gap: 2px; min-width: 0; overflow: hidden; }\n.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; }\n.bpe-breadcrumb__item:hover { background: var(--bpe-bg-muted); color: var(--bpe-fg); }\n.bpe-breadcrumb__item--current { color: var(--bpe-fg); font-weight: 600; }\n.bpe-breadcrumb__sep { color: var(--bpe-fg-faint); }\n.bpe-canvas__bar-end { margin-left: auto; display: flex; align-items: center; gap: 8px; }\n\n/* inline editor */\n.bpe-inline-editor { position: absolute; z-index: 6; outline: none; }\n.bpe-inline-editor .ProseMirror { outline: none; white-space: pre-wrap; word-wrap: break-word; min-height: 1em; }\n.bpe-inline-editor .ProseMirror p { margin: 0; }\n.bpe-inline-editor .ProseMirror ul, .bpe-inline-editor .ProseMirror ol { margin: 0; padding-left: 1.4em; }\n.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; }\n.bpe-inline-editor .ProseMirror .bp-token.ProseMirror-selectednode { outline: 2px solid var(--bpe-accent); }\n.bpe-inline-editor .ProseMirror-gapcursor { display: none; }\n.bpe-inline-editor__frame { position: absolute; inset: -4px; border: 1.5px solid var(--bpe-accent); border-radius: 3px; pointer-events: none; }\n.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); }\n.bpe-bubble .bpe-btn--icon { width: 26px; height: 26px; }\n.bpe-bubble__sep { width: 1px; height: 18px; margin: 0 2px; background: var(--bpe-border); }\n\n/* ------------------------------------------------------------------ */\n/* Inspector */\n/* ------------------------------------------------------------------ */\n.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; }\n.bpe-inspector__header { display: flex; align-items: center; gap: 8px; height: 44px; padding: 0 12px; border-bottom: 1px solid var(--bpe-border); }\n.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; }\n.bpe-inspector__icon svg { width: 14px; height: 14px; }\n.bpe-inspector__title { font-weight: 600; font-size: var(--bpe-text-md); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n.bpe-inspector__subtitle { font-size: var(--bpe-text-xs); color: var(--bpe-fg-muted); }\n.bpe-inspector__body { flex: 1; min-height: 0; overflow-y: auto; overscroll-behavior: contain; }\n.bpe-inspector__actions { margin-left: auto; display: flex; gap: 2px; }\n.bpe-spacing-grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 4px; }\n.bpe-spacing-grid .bpe-input { padding: 0 4px; text-align: center; }\n.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; }\n.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; }\n.bpe-color-btn:hover { border-color: var(--bpe-border-strong); }\n.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; }\n.bpe-color-btn__swatch::after { content: \"\"; display: block; width: 100%; height: 100%; border-radius: 4px; background: var(--c, transparent); }\n.bpe-color-btn__label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n.bpe-color-popover { width: 236px; }\n.bpe-color-grid { display: grid; grid-template-columns: repeat(8, 1fr); gap: 4px; padding: 4px; }\n.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; }\n.bpe-color-grid__item[aria-pressed=\"true\"] { box-shadow: 0 0 0 2px var(--bpe-bg), 0 0 0 4px var(--bpe-accent); }\n.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); }\n.bpe-color-token:hover { background: var(--bpe-bg-muted); }\n.bpe-color-token[aria-pressed=\"true\"] { background: var(--bpe-accent-soft); color: var(--bpe-accent); }\n.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); }\n.bpe-color-token__val { margin-left: auto; font-family: var(--bpe-font-mono); font-size: 10px; color: var(--bpe-fg-faint); }\n.bpe-binding { display: flex; align-items: center; gap: 4px; width: 100%; min-width: 0; }\n/* The image field's file input is opened by the button beside it. Hidden with\n clip rather than display:none so the browser still counts it as a real\n control and the button's click reaches it. */\n.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; }\n.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); }\n.bpe-binding__btn:hover { border-color: var(--bpe-border-strong); }\n.bpe-binding__btn--empty { color: var(--bpe-fg-faint); }\n/* A value the document works out. Shown, named after the fields it reads, and not\n editable where there is no expression editor to edit it with. */\n.bpe-binding__btn--locked { background: var(--bpe-bg-subtle); color: var(--bpe-fg-muted); cursor: default; }\n.bpe-binding__btn--locked:hover { border-color: var(--bpe-border); }\n.bpe-binding__btn svg { width: 13px; height: 13px; flex-shrink: 0; color: var(--bpe-fg-muted); }\n.bpe-expr { font-family: var(--bpe-font-mono); font-size: 11px; }\n.bpe-picker { width: 300px; max-height: 380px; display: flex; flex-direction: column; padding: 0; overflow: hidden; }\n/* Wider when it is holding the calculation builder, which is three controls on a\n row plus a field name. At 300px every one of them truncated \u2014 \"a total\",\n \"Total of\", \"In\u2026\" \u2014 so the panel read as an interface that had been broken\n rather than one that was cramped. The field list on the other tab is a single\n column and does not need it, but one width for the panel is better than a\n popover that changes size when you change tab. */\n.bpe-picker--wide { width: 420px; max-height: 460px; }\n.bpe-picker__search { padding: 8px; border-bottom: 1px solid var(--bpe-border); }\n.bpe-picker__list { flex: 1; overflow-y: auto; padding: 4px; }\n.bpe-picker__foot { display: flex; gap: 6px; align-items: center; padding: 8px; border-top: 1px solid var(--bpe-border); }\n.bpe-picker__foot .bpe-input { flex: 1; }\n.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); }\n/* The value panel's two answers: a field, or a value worked out. Shorter than the\n inspector's own tabs because these carry a word rather than a word and an icon. */\n.bpe-picker__tabs { display: flex; gap: 2px; padding: 6px 6px 0; border-bottom: 1px solid var(--bpe-border); }\n.bpe-picker__tabs .bpe-tab { height: 28px; }\n\n/* The calculation builder. A column of rows, each reading as part of a sentence,\n and every control allowed to shrink \u2014 the properties panel is resizable, so\n anything in it can be squeezed and `min-width: 0` is what keeps the remove\n button inside the panel rather than out through its `overflow: hidden`. */\n.bpe-calc { display: flex; flex-direction: column; gap: 10px; padding: 10px; min-width: 0; }\n/* A step is a labelled block, not a line of controls.\n Three selects and a field name across one row is what made this unreadable:\n every control shrank to its truncation point and the sentence the builder is\n supposed to read as was unrecoverable from the abbreviations. */\n.bpe-calc__step { display: flex; flex-direction: column; gap: 4px; min-width: 0; padding: 8px; border: 1px solid var(--bpe-border); border-radius: var(--bpe-radius); background: var(--bpe-bg-subtle); }\n.bpe-calc__row { display: flex; align-items: center; gap: 6px; min-width: 0; }\n.bpe-grow { flex: 1 1 auto; }\n.bpe-calc__lead { flex: 0 0 auto; font-size: var(--bpe-text-xs); font-weight: 600; text-transform: uppercase; letter-spacing: 0.04em; color: var(--bpe-fg-faint); }\n.bpe-calc__row .bpe-select { flex: 1 1 auto; min-width: 0; }\n.bpe-calc__term { display: flex; flex-direction: column; gap: 4px; min-width: 0; }\n.bpe-calc__term .bpe-binding { width: 100%; }\n.bpe-calc__term .bpe-binding__btn { flex: 1 1 auto; min-width: 0; }\n.bpe-calc__term .bpe-input { width: 100%; }\n.bpe-calc__reads { margin: 2px 0 0; padding-top: 8px; border-top: 1px solid var(--bpe-border); font-size: var(--bpe-text-xs); color: var(--bpe-fg-muted); line-height: 1.5; }\n.bpe-cond { display: flex; flex-direction: column; gap: 6px; min-width: 0; }\n.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); }\n.bpe-cond__row { display: grid; grid-template-columns: minmax(0, 1fr); gap: 4px; min-width: 0; }\n.bpe-cond__op { display: flex; align-items: center; gap: 6px; }\n.bpe-cond__group { padding: 6px; border: 1px dashed var(--bpe-border-strong); border-radius: var(--bpe-radius); }\n.bpe-list-editor { display: flex; flex-direction: column; gap: 6px; min-width: 0; }\n.bpe-list-editor__item { border: 1px solid var(--bpe-border); border-radius: var(--bpe-radius); background: var(--bpe-bg-subtle); }\n.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; }\n.bpe-list-editor__head .bpe-truncate { flex: 1 1 0; min-width: 0; }\n.bpe-list-editor__item { min-width: 0; }\n.bpe-list-editor__body { padding: 6px 8px 8px; border-top: 1px solid var(--bpe-border); display: flex; flex-direction: column; gap: 6px; }\n/*\n * Inside a list item, every field is stacked.\n *\n * The two-column field \u2014 a 96px label and the control beside it \u2014 is right in the\n * panel and wrong one level in: the item is already indented and bordered, so the\n * label column eats a third of what is left and the control ends in the middle of\n * the row with the rest blank. Stacked gives the control the whole width, and it\n * matches the fields in there that were already stacked because they needed it.\n */\n.bpe-list-editor__body .bpe-field { grid-template-columns: 1fr; gap: 4px; }\n.bpe-list-editor__body .bpe-field__label { white-space: normal; overflow: visible; }\n.bpe-list-editor__drag { cursor: grab; color: var(--bpe-fg-faint); display: inline-flex; }\n.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); }\n.bpe-issue:hover { background: var(--bpe-bg-muted); }\n.bpe-issue svg { width: 13px; height: 13px; flex-shrink: 0; margin-top: 2px; }\n.bpe-issue--error svg { color: var(--bpe-danger); }\n.bpe-issue--warning svg { color: var(--bpe-warning); }\n.bpe-issue--info svg { color: var(--bpe-fg-muted); }\n.bpe-issue__meta { display: block; margin-top: 2px; font-size: var(--bpe-text-xs); color: var(--bpe-fg-muted); }\n.bpe-text-style-row { display: grid; grid-template-columns: 1fr auto; align-items: center; gap: 6px; }\n.bpe-text-style-preview { font-weight: 600; }\n.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); }\n.bpe-doc-styles__item:hover { background: var(--bpe-bg-subtle); }\n.bpe-doc-styles__sample { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n.bpe-doc-styles__id { font-family: var(--bpe-font-mono); font-size: 10px; color: var(--bpe-fg-faint); }\n.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); }\n.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); }\n.bpe-toast svg { width: 14px; height: 14px; }\n.bpe-portal { position: absolute; width: 0; height: 0; overflow: visible; }\n.bpe-scope { font-family: var(--bpe-font); }\n\n.bpe-pager { display: inline-flex; align-items: center; gap: 2px; }\n.bpe-pager__label { min-width: 92px; text-align: center; font-variant-numeric: tabular-nums; }\n\n/* Panel resize handles sit on the boundary rather than in the grid, so the\n three-column shell stays three columns and the handles stay draggable even\n when a panel is scrolled. */\n.bpe-resizer { position: absolute; top: var(--bpe-toolbar-h); bottom: 0; width: 9px; cursor: col-resize; z-index: 6; background: transparent; touch-action: none; }\n.bpe-resizer--left { left: var(--bpe-panel-w); margin-left: -4px; }\n.bpe-resizer--right { right: var(--bpe-inspector-w); margin-right: -4px; }\n.bpe-resizer::after { content: \"\"; position: absolute; inset: 0 3px; border-radius: 2px; background: transparent; transition: background var(--bpe-dur) var(--bpe-ease); }\n.bpe-resizer:hover::after, .bpe-resizer:focus-visible::after { background: var(--bpe-accent); }\n.bpe-resizer:focus-visible { outline: none; }\n\n/* A long label stops being a column and becomes a line of its own. */\n.bpe-field--stacked .bpe-field__label { white-space: normal; overflow: visible; }\n\n.bpe-block-toolbar__edit { font-weight: 500; }\n\n.bpe-legend { width: 340px; }\n.bpe-legend__body { padding: 12px 14px 10px; }\n.bpe-legend__title { margin: 0 0 6px; font-size: var(--bpe-text-md); font-weight: 600; }\n.bpe-legend__intro { margin: 0 0 10px; font-size: var(--bpe-text-xs); color: var(--bpe-fg-secondary); line-height: 1.5; }\n.bpe-legend__list { margin: 0; display: flex; flex-direction: column; gap: 10px; }\n.bpe-legend__entry { display: flex; gap: 9px; align-items: flex-start; }\n.bpe-legend__swatch { flex: 0 0 auto; display: grid; place-items: center; min-width: 22px; padding-top: 1px; color: var(--bpe-fg-muted); }\n.bpe-legend__entry dt { font-size: var(--bpe-text-xs); font-weight: 600; color: var(--bpe-fg); }\n.bpe-legend__entry dd { margin: 1px 0 0; font-size: var(--bpe-text-xs); color: var(--bpe-fg-muted); line-height: 1.45; }\n.bpe-legend__token { font-size: 9px; padding: 1px 4px; }\n.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); }\n\n/* ===================================================================\n Narrow \u2014 the designer in a container too small for three columns.\n\n Set by the shell from the root element's own width, not a media query:\n the designer is embedded, so a phone and a narrow pane in a wide page\n are the same problem and the window answers neither. Below the\n breakpoint the panels stop being columns and become drawers over the\n canvas, because 272 + 296 of panel leaves a phone about forty pixels\n of page \u2014 which is not a cramped canvas, it is no canvas.\n =================================================================== */\n\n/* Two classes deliberately: `.bpe--no-left.bpe--no-right` is also two, and a\n single-class rule would lose to it exactly when both panels are shut \u2014\n which on a phone is the normal state. */\n.bpe.bpe--narrow { grid-template-columns: minmax(0, 1fr); grid-template-areas: \"toolbar\" \"canvas\"; }\n\n/* Both panels occupy the canvas cell rather than one of their own, so the\n canvas keeps its full width whether a drawer is open or not \u2014 nothing\n reflows underneath when one opens, which is what makes it a drawer\n rather than a column that happens to overlap. */\n.bpe--narrow .bpe-left, .bpe--narrow .bpe-inspector {\n grid-area: canvas;\n z-index: 20;\n width: min(88%, 360px);\n max-width: 100%;\n box-shadow: 0 24px 48px -24px rgba(0, 0, 0, 0.45);\n animation: bpe-drawer-in 180ms var(--bpe-ease) both;\n}\n.bpe--narrow .bpe-left { justify-self: start; border-right: 1px solid var(--bpe-border); }\n.bpe--narrow .bpe-inspector { justify-self: end; border-left: 1px solid var(--bpe-border); }\n@keyframes bpe-drawer-in { from { transform: translateX(var(--bpe-drawer-from, -8%)); opacity: 0; } to { transform: none; opacity: 1; } }\n.bpe--narrow .bpe-inspector { --bpe-drawer-from: 8%; }\n@media (prefers-reduced-motion: reduce) { .bpe--narrow .bpe-left, .bpe--narrow .bpe-inspector { animation: none; } }\n\n/* Tapping the canvas beside an open drawer closes it. A button rather than\n a div so it is reachable by keyboard and announces itself; it sits under\n the drawer and over everything else. */\n.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; }\n@keyframes bpe-fade-in { from { opacity: 0; } to { opacity: 1; } }\n@media (prefers-reduced-motion: reduce) { .bpe-scrim { animation: none; } }\n\n/* The page needs the room more than the margin around it does. */\n.bpe--narrow .bpe-canvas__inner { padding: 12px 12px 48px; }\n/* Touch targets, and a scroll that does not fight the page it is embedded in. */\n.bpe--narrow .bpe-btn { min-height: 32px; }\n.bpe--narrow .bpe-canvas__scroll { -webkit-overflow-scrolling: touch; }\n\n/* The toolbar's three tracks assume a row wide enough for all of them. On a\n phone the centre group alone \u2014 Design/Preview plus the zoom controls, both\n of which refuse to wrap \u2014 takes the whole width, leaving the leading and\n trailing tracks measuring zero and their contents drawn off the edge. So\n narrow, it wraps: the groups keep their order and their contents, and the\n toolbar takes the two rows it needs. `.bpe` gives it an auto row to grow\n into rather than the fixed height a single row assumed. */\n.bpe.bpe--narrow { grid-template-rows: auto 1fr; }\n.bpe--narrow .bpe-toolbar { display: flex; flex-wrap: wrap; justify-content: space-between; row-gap: 6px; padding: 6px 8px; }\n.bpe--narrow .bpe-toolbar__group--start { overflow: visible; flex: 1 1 auto; }\n.bpe--narrow .bpe-toolbar__group--centre { order: 3; flex: 1 0 100%; justify-content: center; }\n.bpe--narrow .bpe-toolbar__group--end { flex: 0 0 auto; }\n/* The report name gives up its fixed width first; it is the one thing in the\n leading group that can shrink without losing a control. */\n.bpe--narrow .bpe-toolbar__name { width: auto; min-width: 80px; max-width: none; flex: 1 1 80px; }\n/* A backstop for the very narrowest: if the mode switch and the zoom controls\n still will not sit on one row, they take two rather than one of them being\n drawn off the edge. */\n.bpe--narrow .bpe-toolbar__group--centre { flex-wrap: wrap; white-space: normal; row-gap: 6px; }\n";
|
|
392
408
|
|
|
393
409
|
interface PickerEntry {
|
|
394
410
|
expr: string;
|
|
@@ -422,9 +438,25 @@ interface BindingFieldProps {
|
|
|
422
438
|
types?: FieldType[];
|
|
423
439
|
placeholder?: string;
|
|
424
440
|
allowExpression?: boolean;
|
|
441
|
+
/**
|
|
442
|
+
* Whether arithmetic is a sensible answer for this property.
|
|
443
|
+
*
|
|
444
|
+
* False for the few props that are an expression by nature rather than a value
|
|
445
|
+
* — a chart's category label, a table rule's condition. Offering to total
|
|
446
|
+
* something up there would be offering a wrong answer confidently.
|
|
447
|
+
*/
|
|
448
|
+
allowCalculation?: boolean;
|
|
425
449
|
}
|
|
426
|
-
/**
|
|
427
|
-
|
|
450
|
+
/**
|
|
451
|
+
* A button that opens the value panel; shows the human label of the current
|
|
452
|
+
* binding.
|
|
453
|
+
*
|
|
454
|
+
* The panel has two halves and the binding decides which opens first: a field, or
|
|
455
|
+
* a calculation. Both are available to every host — a formula built by choosing
|
|
456
|
+
* steps is not a Cloud concession, it is the designer finally being able to author
|
|
457
|
+
* the arithmetic its own templates were shipping.
|
|
458
|
+
*/
|
|
459
|
+
declare function BindingField({ value, onChange, dataSources, scopeVars, arraysOnly, types, placeholder, allowExpression, allowCalculation }: BindingFieldProps): react.JSX.Element;
|
|
428
460
|
|
|
429
461
|
interface Props {
|
|
430
462
|
value: Condition | undefined;
|