@gemboss/ui 0.8.0 → 0.10.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/src/editor.css ADDED
@@ -0,0 +1,1096 @@
1
+ /*
2
+ * The EDITORS — @gemboss/ui (moved from apps/admin/src/app/gemboss-shell.css, 04/10/2026, step 1 of 5 of the editor kit).
3
+ *
4
+ * Everything the admin's theme, page and section editors are drawn with: the full-screen frame and its body
5
+ * (`.gemboss-editor`, `-body`, `-preview-pane`, `-resizer`), the top bar (`.gemboss-editor-topbar`, `-tb-*`, publish
6
+ * split button, overflow menu `.gemboss-editor-menu*`, page picker `.gemboss-pagepicker*`, device toggle `.gemboss-pv-device`),
7
+ * the preview canvas (`.gemboss-pv*`), the left column's tab row and Configure dock (`.gemboss-ec-tabs`, `.gemboss-ec-tab`,
8
+ * `.gemboss-ec-dock-body`, and the editor's sizing of the chat column `.gemboss-editor-body .gemboss-ec`), the inspector
9
+ * and its controls (`.gemboss-ei*`: groups, fields, segmented, colour, range, edge box, image, repeater, element tree),
10
+ * Layers (`.gemboss-layers`, `.gemboss-layer*`), the settings drawer (`.gemboss-settings*`, `.gemboss-picker-*`), the
11
+ * page / section editors' own bar pieces (`.gemboss-pageedit*`, `.gemboss-sec-type`) and the try door's bar
12
+ * (`.gemboss-guest-*`).
13
+ *
14
+ * Rules and their order are the admin's, unchanged: a rule moved when every selector in it names an editor piece and
15
+ * nothing only the admin has. Left in the admin on purpose: the body modes that hide the admin's own topbar and rail
16
+ * while an editor is up (`gemboss-editor-fullscreen`, `-just-exited`, `-layer-open`), and the admin extending a piece
17
+ * for something only it has (`.gemboss-product-studio > .gemboss-editor-resizer`).
18
+ *
19
+ * A SEPARATE ENTRY, not part of gemboss-ui.css. In the admin it follows the editor components (each file that renders an
20
+ * editor class imports it), never a layout: it left gemboss-shell.css so the admin's shell first load stops carrying it.
21
+ * Other surfaces import `@gemboss/ui/editor.css` next to gemboss-ui.css only where they show an editor.
22
+ *
23
+ * Needs gemboss-ui.css (or, in the admin, gemboss-tokens.css + ui.css + gemboss-field.css + chat.css) for its tokens,
24
+ * `gemboss-field`, `ui-resizer` and the `gemboss-spin` keyframes.
25
+ */
26
+
27
+ /* Keyframes the rules below play (moved with them; `gemboss-spin` is chat.css's). */
28
+ @keyframes gemboss-editor-enter { from { opacity: 0; } to { opacity: 1; } }
29
+ @keyframes gemboss-editor-toast-in { from { opacity: 0; transform: translate(-50%, -8px); } to { opacity: 1; transform: translate(-50%, 0); } }
30
+ @keyframes gemboss-pagepicker-in { from { opacity: 0; transform: translateY(-4px); } to { opacity: 1; transform: translateY(0); } }
31
+ @keyframes gemboss-statusbar-in { from { opacity: 0; transform: translateY(-6px); } to { opacity: 1; transform: none; } }
32
+
33
+ /* Editor is position:fixed and self-contained, so main needs no height/padding —
34
+ * collapse it to avoid any outer page scroll. */
35
+ .gemboss-platform-main:has(.gemboss-editor) {
36
+ box-sizing: border-box;
37
+ height: 100dvh;
38
+ min-height: 0;
39
+ padding-top: 0;
40
+ overflow: hidden;
41
+ }
42
+ .gemboss-editor {
43
+ /* One spacing token: --gemboss-editor-inset is BOTH the inter-panel gap / resizer width AND
44
+ the outer padding that floats the panels off the topbar/frame — equal on all sides keeps
45
+ the layout tight and balanced (cards hug the frame; the gap to the chat == the gap to the
46
+ edges). */
47
+ --gemboss-editor-inset: 5px;
48
+ position: fixed;
49
+ inset: 0;
50
+ box-sizing: border-box;
51
+ display: flex;
52
+ flex-direction: column;
53
+ background: var(--gemboss-canvas);
54
+ overflow: hidden;
55
+ z-index: 400;
56
+ /* Soft entrance: the editor is reached both by opening a store from the list and
57
+ by handing off from the StoreStudio composer; a quick fade keeps either swap
58
+ from reading as a hard cut. */
59
+ animation: gemboss-editor-enter 260ms cubic-bezier(0.16, 1, 0.3, 1);
60
+ }
61
+ @media (prefers-reduced-motion: reduce) {
62
+ .gemboss-editor { animation: none; } }
63
+ /* Embedded editor: mounted INSIDE another surface (the A/B campaign editor, under its variant bar)
64
+ rather than taking over the viewport. Fill the container, don't float over it. */
65
+ .gemboss-editor--embedded { position: relative; inset: auto; height: 100%; min-height: 0; z-index: auto; animation: none; }
66
+ /* One-time "saved as draft" confirmation, shown when arriving straight from
67
+ creating a theme. Floats below the top bar, centered, auto-dismisses. */
68
+ .gemboss-editor-toast {
69
+ position: absolute; top: 62px; left: 50%; transform: translateX(-50%);
70
+ z-index: 500; display: flex; align-items: center; gap: 12px;
71
+ max-width: min(560px, 92vw); padding: 12px 12px 12px 16px;
72
+ background: var(--gemboss-text); color: #fff; border-radius: 12px;
73
+ font-size: 13px; line-height: 1.4; box-shadow: var(--gemboss-shadow-overlay);
74
+ animation: gemboss-editor-toast-in 240ms cubic-bezier(0.16, 1, 0.3, 1);
75
+ }
76
+ .gemboss-editor-toast strong { font-weight: 600; }
77
+ .gemboss-editor-toast button {
78
+ flex: 0 0 auto; border: 0; background: rgba(255, 255, 255, 0.16); color: #fff;
79
+ width: 22px; height: 22px; border-radius: 8px; cursor: pointer; font-size: 12px; line-height: 1;
80
+ transition: background var(--gemboss-transition-fast) ease;
81
+ }
82
+ .gemboss-editor-toast button:hover { background: rgba(255, 255, 255, 0.28); }
83
+ .gemboss-editor-toast button:focus-visible { outline: 2px solid #fff; outline-offset: 2px; }
84
+ @media (prefers-reduced-motion: reduce) {
85
+ .gemboss-editor-toast { animation: none; } }
86
+ /* Editor's own top bar (replaces the dashboard topbar) */
87
+ .gemboss-editor-topbar {
88
+ flex: 0 0 auto;
89
+ height: var(--gemboss-editor-topbar-h, 52px);
90
+ /* (the content-sized tracks below are scoped to --theme; see that rule) */
91
+ /* Tracks sized to their CONTENT, not 1fr-auto-1fr. Grid columns never overlap but their content does:
92
+ with equal 1fr side tracks the middle group is centred in its own track and the right group is
93
+ justify-self:end in its own, so as soon as the right group is wider than its share it reaches back
94
+ across the middle. Measured at 1366 — where everything fits, roughly 1220px of content in 1366px —
95
+ the device toggle's "Mobile" segment and "Add section" still overlapped by 27x28px and part of each
96
+ was unclickable. auto tracks plus space-between lay them out by width instead of by share.
97
+ Flex was worse: making the left group the only one that can shrink pushed Publish off the edge at
98
+ 768px, which is the failure this was fixing. */
99
+ display: grid;
100
+ grid-template-columns: 1fr auto 1fr;
101
+ align-items: center;
102
+ gap: 12px;
103
+ padding: 0 16px;
104
+ background: var(--gemboss-surface);
105
+ border-bottom: 1px solid var(--gemboss-divider);
106
+ }
107
+ /* All three editors that render this bar: the theme editor, and the page + section editors via
108
+ .gemboss-pageedit-top. They were left out of the first pass because neither would open in this
109
+ checkout, and an unmeasured surface does not get a layout change. Both open now — the page editor
110
+ needs a page made through /api/gemboss/pages, and the section editor's id comes from the static
111
+ sections-registry.json, NOT from user-sections, which is why /app/sections/<user section>/edit 404s.
112
+ Measured with them open: the section editor overlapped three ways at 1024px, its device toggle
113
+ sitting across "Save as my section" by up to 30x28px — the same 1fr-auto-1fr spill the theme editor
114
+ had. Same cause, same fix, and now the same evidence. */
115
+ /* ABOVE 1000px only. These selectors are more specific than the .gemboss-editor-topbar rule inside the
116
+ <=1000px reflow below, and specificity beats media queries — so without this bound they silently
117
+ overrode the two-row mobile layout and put Back on top of "Save as my section" at 390px. */
118
+ @media (min-width: 1001px) {
119
+ .gemboss-editor-topbar--theme,
120
+ .gemboss-editor-topbar.gemboss-pageedit-top {
121
+ grid-template-columns: auto auto auto;
122
+ justify-content: space-between;
123
+ }
124
+ }
125
+ .gemboss-editor-tb-left { display: flex; align-items: center; gap: 12px; min-width: 0; }
126
+ /* justify-self stays: in the content-sized tracks above a track is exactly as wide as its cluster, so
127
+ these are inert there — but the <=1000px rules reflow this into two rows and explicitly rely on the
128
+ right cluster hugging its edge. Dropping them read as tidying and reintroduced six overlapping
129
+ controls at 390px, Preview sitting across Publish by 50px. */
130
+ .gemboss-editor-tb-center { display: flex; align-items: center; gap: 12px; justify-self: center; }
131
+ /* min-width: 0 only under --theme: it removes the min-content floor, which is what lets the centre
132
+ cluster give way instead of pushing into its neighbours — and is also what would let it overflow on
133
+ a surface that has not been measured for it. */
134
+ .gemboss-editor-topbar--theme .gemboss-editor-tb-center,
135
+ .gemboss-editor-topbar.gemboss-pageedit-top .gemboss-editor-tb-center { min-width: 0; }
136
+ /* The section editor genuinely does not fit at 1024: measured 1055px of content in a 1013px bar, so
137
+ something has to give and the only question is what. 159px of the left cluster is LABELS — the static
138
+ "Section editor" caption (79px) and the archetype chip "advanced list" (80px) — against 42px of
139
+ overflow. A label can go; a control cannot, and that distinction is the whole rule. The title
140
+ ellipsises rather than disappearing, because it is the one thing that says WHICH section you are in. */
141
+ .gemboss-editor-topbar.gemboss-pageedit-top .gemboss-pageedit-title {
142
+ min-width: 0;
143
+ overflow: hidden;
144
+ text-overflow: ellipsis;
145
+ white-space: nowrap;
146
+ }
147
+ @media (max-width: 1200px) {
148
+ .gemboss-editor-topbar.gemboss-pageedit-top .gemboss-editor-tb-name,
149
+ .gemboss-editor-topbar.gemboss-pageedit-top .gemboss-sec-type { display: none; }
150
+ }
151
+ .gemboss-editor-tb-right { display: flex; align-items: center; gap: 16px; min-width: 0; justify-self: end; }
152
+ .gemboss-editor-back {
153
+ font-size: 18px; line-height: 1; color: var(--gemboss-text); text-decoration: none; cursor: pointer;
154
+ width: 30px; height: 30px; display: inline-flex; align-items: center; justify-content: center; border-radius: var(--gemboss-radius-md);
155
+ transition: background var(--gemboss-transition-fast) ease, color var(--gemboss-transition-fast) ease;
156
+ }
157
+ .gemboss-editor-back:hover { background: var(--gemboss-hover); }
158
+ .gemboss-editor-back:active { background: var(--gemboss-active-bg); color: var(--gemboss-active-fg); }
159
+ .gemboss-editor-back:focus-visible { outline: 2px solid var(--gemboss-primary); outline-offset: 2px; }
160
+ .gemboss-editor-tb-name { font-family: var(--gemboss-serif); font-size: 15px; font-weight: 600; }
161
+ /* Auto-save indicator — every edit commits to the draft server-side; this makes that visible so the
162
+ draft reads as durably kept. Sits next to the "Theme editor" name in the (dark) editor topbar. */
163
+ .gemboss-editor-savestate { display: inline-flex; align-items: center; gap: 6px; font-size: var(--gemboss-fs-micro); font-weight: 500; line-height: 1; padding: 4px 8px; border-radius: var(--gemboss-radius-full); white-space: nowrap; transition: opacity .2s ease; }
164
+ .gemboss-editor-savestate.is-saving { color: rgba(255,255,255,.72); background: rgba(255,255,255,.08); }
165
+ .gemboss-editor-savestate.is-saved { color: var(--gemboss-success-border); background: rgba(111,211,154,.12); }
166
+ .gemboss-editor-savestate .gemboss-narr-spin { width: 10px; height: 10px; border-width: 1.5px; border-color: rgba(255,255,255,.28); border-top-color: rgba(255,255,255,.85); }
167
+ @media (max-width: 760px) {
168
+ .gemboss-editor-savestate.is-saved { display: none; } }
169
+ .gemboss-editor-tb-ghost { font-size: 13px; color: var(--gemboss-text); text-decoration: none; padding: 8px 12px; border: 1px solid var(--gemboss-divider); border-radius: 8px; white-space: nowrap; cursor: pointer; background: var(--gemboss-surface); transition: border-color var(--gemboss-transition-fast) ease, color var(--gemboss-transition-fast) ease, background var(--gemboss-transition-fast) ease; }
170
+ .gemboss-editor-tb-ghost:hover { border-color: var(--gemboss-primary); }
171
+ .gemboss-editor-tb-ghost:active { background: var(--gemboss-active-bg); border-color: var(--gemboss-primary); color: var(--gemboss-active-fg); }
172
+ .gemboss-editor-tb-ghost:focus-visible { outline: 2px solid var(--gemboss-primary); outline-offset: 2px; }
173
+ .gemboss-editor-tb-ghost:disabled { opacity: 0.5; cursor: default; border-color: var(--gemboss-divider); color: var(--gemboss-text-muted); }
174
+ .gemboss-editor-tb-publish {
175
+ background: var(--gemboss-primary); color: #fff; border: 0; border-radius: 8px;
176
+ padding: 8px 20px; font-size: 14px; font-weight: 600; cursor: pointer; white-space: nowrap;
177
+ transition: background var(--gemboss-transition-fast) ease, filter var(--gemboss-transition-fast) ease;
178
+ }
179
+ .gemboss-editor-tb-publish:hover { background: var(--gemboss-primary-dark); }
180
+ .gemboss-editor-tb-publish:active { background: var(--gemboss-primary-dark); filter: brightness(0.94); }
181
+ .gemboss-editor-tb-publish:focus-visible { outline: 2px solid var(--gemboss-primary); outline-offset: 2px; }
182
+ .gemboss-editor-tb-publish:disabled { opacity: 0.5; cursor: default; filter: none; }
183
+ .gemboss-editor-tb-undo { font-size: 15px; }
184
+ /* Split "Publish" control — primary go-live + caret opening the deploy-target menu.
185
+ The two buttons read as one pill: main takes the left radius, caret the right, a
186
+ hairline divider between them. */
187
+ .gemboss-editor-publish { position: relative; display: inline-flex; align-items: stretch; }
188
+ .gemboss-editor-publish-main { border-radius: 8px 0 0 8px; }
189
+ .gemboss-editor-publish-caret {
190
+ border-radius: 0 8px 8px 0;
191
+ padding: 8px 12px; font-size: 12px; line-height: 1;
192
+ border-left: 1px solid color-mix(in srgb, #fff 24%, transparent);
193
+ display: inline-flex; align-items: center; justify-content: center;
194
+ }
195
+ .gemboss-editor-publish-caret.active { filter: brightness(0.94); }
196
+ .gemboss-editor-menu-label {
197
+ padding: 6px 12px 2px; font-size: 11px; font-weight: 600; letter-spacing: 0.02em;
198
+ color: var(--gemboss-text-muted); white-space: nowrap;
199
+ }
200
+ /* ── Editor top-bar overflow ("⋯ More") menu — secondary actions on demand ──── */
201
+ .gemboss-editor-overflow { position: relative; display: inline-flex; }
202
+ .gemboss-editor-overflow-trigger { font-size: 18px; line-height: 1; }
203
+ .gemboss-editor-menu {
204
+ /* Surface comes from `gemboss-popover-surface`; this block is geometry and entry only. */
205
+ position: fixed;
206
+ z-index: 500; /* above the editor (z-index 400) */
207
+ min-width: 210px;
208
+ padding: 6px;
209
+ animation: gemboss-flyout-in var(--gemboss-transition-fast) ease both;
210
+ }
211
+ .gemboss-editor-menu-item {
212
+ display: flex;
213
+ align-items: center;
214
+ justify-content: space-between;
215
+ gap: 12px;
216
+ width: 100%;
217
+ padding: 8px 12px;
218
+ border: 0;
219
+ border-radius: var(--gemboss-radius-md);
220
+ background: transparent;
221
+ font: inherit;
222
+ font-size: 13px;
223
+ color: var(--gemboss-text);
224
+ text-align: left;
225
+ text-decoration: none;
226
+ white-space: nowrap;
227
+ cursor: pointer;
228
+ transition: background var(--gemboss-transition-fast) ease, color var(--gemboss-transition-fast) ease;
229
+ }
230
+ .gemboss-editor-menu-item:hover { background: var(--gemboss-hover); }
231
+ .gemboss-editor-menu-item:focus-visible { outline: 2px solid var(--gemboss-primary); outline-offset: -2px; background: var(--gemboss-hover); }
232
+ .gemboss-editor-menu-item:disabled { opacity: 0.45; cursor: default; }
233
+ .gemboss-editor-menu-item:disabled:hover { background: transparent; }
234
+ .gemboss-editor-menu-item.active { color: var(--gemboss-active-fg); }
235
+ .gemboss-editor-menu-ext { color: var(--gemboss-text-muted); }
236
+ .gemboss-editor-menu-check { color: var(--gemboss-active-fg); font-weight: 600; }
237
+ .gemboss-editor-menu-val {
238
+ font-size: 11px; color: var(--gemboss-text-muted);
239
+ border: 1px solid var(--gemboss-divider); border-radius: var(--gemboss-radius-sm); padding: 1px 8px;
240
+ }
241
+ .gemboss-editor-menu-sep { height: 1px; background: var(--gemboss-divider-soft); margin: 6px 6px; }
242
+ /* Panels float inside the canvas with an EQUAL inset on all four sides; the resizer (same
243
+ width, --gemboss-editor-inset) is the single gap between them — so the cards hug the topbar
244
+ and frame tightly and the preview's top gap matches its gap to the chat sidebar. */
245
+ .gemboss-editor-body { flex: 1; min-height: 0; display: flex; gap: 0; padding: var(--gemboss-editor-inset); box-sizing: border-box; }
246
+ /* Both panels become rounded, bordered cards (design-system surface). The preview's direct
247
+ child is the .gemboss-editor-preview-pane wrapper (it positions the section-outline overlay),
248
+ so the card styling lives on the WRAPPER — `overflow: hidden` also clips the iframe + the
249
+ outline panel to the rounded corners. (Targeting .gemboss-pv here would miss: it's now a
250
+ grandchild inside the wrapper.) */
251
+ .gemboss-editor-body > .gemboss-ec,
252
+ .gemboss-editor-body > .gemboss-editor-preview-pane {
253
+ border: 1px solid var(--gemboss-divider);
254
+ border-radius: 16px;
255
+ overflow: hidden;
256
+ }
257
+ .gemboss-editor-body > .gemboss-ec { border-right: 1px solid var(--gemboss-divider); }
258
+ /* The left column's tab row: Configure · Chat (04/10/2026). Until then every editor drew it with an INLINE style
259
+ (`tabStyle()` in EditorLeftPanel.tsx) laid over this class, so the class's own look (13px, 8px 6px, a hover, `.on`)
260
+ never reached the screen and the theme editor's copy of the row carried no class at all. The look below is that
261
+ inline style, written as CSS with the same tokens: the same computed values, one owner.
262
+ The LOOK hangs on the row (`.gemboss-ec-tabs > [role="tab"]`); `.gemboss-ec-tab` is what the pointer blocks size
263
+ (44px under touch here, the 44px overlay under a mouse in chat.css). The theme editor's tabs wear the look but not
264
+ `.gemboss-ec-tab` yet, so they stay 36px under touch: adding it is a visible change, left for its own decision. */
265
+ /* OFF THE SPACE SCALE, KNOWINGLY: the row's 10px sides and the tab's 7px padding are the inline style's numbers, carried
266
+ unchanged so the admin does not move a pixel (the typography gate never read them while they were inline strings).
267
+ Snapping to the scale (12px, 8px) makes the row 4px wider inside and each tab 2px taller: a visible change, left to
268
+ its own decision. Named here so it is one edit, not a hunt. */
269
+ .gemboss-ec-tabs {
270
+ --gemboss-ec-tabs-pad-x: 10px;
271
+ --gemboss-ec-tab-pad: 7px;
272
+ display: flex; gap: 4px; padding: 8px var(--gemboss-ec-tabs-pad-x); border-bottom: 1px solid var(--gemboss-divider); flex: none;
273
+ }
274
+ .gemboss-ec-tabs > [role="tab"] {
275
+ flex: 1; font: inherit; font-size: 12px; font-weight: 600; cursor: pointer; padding: var(--gemboss-ec-tab-pad); border-radius: 8px;
276
+ display: flex; align-items: center; justify-content: center; gap: 6px;
277
+ border: 1px solid transparent; background: transparent; color: var(--gemboss-text-muted); box-shadow: none;
278
+ }
279
+ .gemboss-ec-tabs > [role="tab"][aria-selected="true"] {
280
+ border-color: var(--gemboss-divider); background: var(--gemboss-surface); color: var(--gemboss-text);
281
+ box-shadow: inset 0 -2px 0 var(--gemboss-primary);
282
+ }
283
+ /* ── Preview canvas (left) ── */
284
+ /* Full-bleed: the storefront iframe floods the pane edge-to-edge (no inset, border or radius). */
285
+ .gemboss-pv { flex: 1; min-width: 0; display: flex; flex-direction: column; background: var(--gemboss-preview-canvas); overflow: hidden; }
286
+ .gemboss-pv-devices { display: inline-flex; gap: 2px; }
287
+ .gemboss-pv-device {
288
+ border: 1px solid var(--gemboss-divider); background: var(--gemboss-surface); cursor: pointer;
289
+ width: 30px; height: 28px; border-radius: 8px; color: var(--gemboss-text-muted); font-size: 14px;
290
+ /* Center the glyph/emoji both axes — without this the text glyphs (↶ ↷ ⌖ 🎬) sit on the
291
+ baseline of the 28px box and read as vertically off-center (the "icons not aligned" bug). */
292
+ display: inline-flex; align-items: center; justify-content: center; line-height: 1;
293
+ transition: border-color var(--gemboss-transition-fast) ease, color var(--gemboss-transition-fast) ease, background var(--gemboss-transition-fast) ease;
294
+ }
295
+ .gemboss-pv-device svg { display: block; }
296
+ /* The 36x32 box is what you SEE; the target is 44x44, the size CLAUDE.md §9.1 asks of a control and
297
+ what Apple HIG / WCAG 2.5.5 ask of an icon button. Growing the box itself would have made a bar that
298
+ already measured sixteen controls wide even wider, so the target is an overlay instead. It extends 4px
299
+ past a 36px box on each side, so the ROW GAP has to be at least 8px for two overlays to meet without
300
+ crossing. At 30px boxes and a 12px gap they sat 42px centre to centre and the overlays overlapped by 2px:
301
+ probing the four corners of each 44px target, six of eight resolved to the NEIGHBOUR, not the button.
302
+ A target that swallows the one beside it is worse than a small one, and it is the failure this was
303
+ meant to fix. 36px boxes with a 16px gap put them 52px apart, so the targets touch and stop. */
304
+ /* SCOPED to the theme editor's bar. PageEditor renders .gemboss-pv-device too, and it is a surface this
305
+ checkout cannot open — so it keeps the 30x28 box and the small target. That is not a decision, it is
306
+ an admission: the same 44px treatment belongs there and on the section editor, once there is a way to
307
+ measure what it does to their bars. Leaving them at 30x28 is a known gap, written down rather than
308
+ silently generalised from one surface that was measured to two that were not. */
309
+ /* ABOVE 1000px only, for the same specificity reason as the grid rule: below that the bar reflows into
310
+ two rows, and a wider box there pushed Back onto "+ Add section" at 390px in the page editor — which
311
+ the baseline shows was CLEAN before this change. A fix that breaks a working width is not a fix. */
312
+ /* THE OVERLAY STAYS BOUNDED TO ≥1001px, and this is now a measured refusal rather than an untested
313
+ caution. I split the box rule from the overlay — the box changes width and broke the reflow, the
314
+ overlay cannot — and measured the standalone icon buttons (☰ ⌖ ⋯, 38x38) with both editors open:
315
+ 420 / 480 / 560 / 768 clean, every target 44
316
+ 320 / 380 / 640 a neighbour's overlay WINS: the ⋯ measured 26x44 and the ⌖ 27x44,
317
+ NARROWER than the 38px box they already had
318
+ It is not a width threshold. Below 1001 the bar reflows and adds controls as it widens (7 at 420,
319
+ 11 at 640), so whether two icon buttons land within 44px of each other depends on which controls
320
+ that width renders — something no media query can ask. A target that swallows its neighbour is
321
+ worse than a small one; that is the failure this overlay exists to fix, and re-creating it two
322
+ widths down is not a trade. Below 1001 these buttons stay 38x38 and short, written down. */
323
+ @media (min-width: 1001px) {
324
+ .gemboss-editor-topbar--theme .gemboss-pv-device,
325
+ .gemboss-editor-topbar.gemboss-pageedit-top .gemboss-pv-device { width: 36px; height: 32px; position: relative; }
326
+ .gemboss-editor-topbar--theme .gemboss-pv-device::after,
327
+ .gemboss-editor-topbar.gemboss-pageedit-top .gemboss-pv-device::after {
328
+ content: "";
329
+ position: absolute;
330
+ left: 50%;
331
+ top: 50%;
332
+ width: 44px;
333
+ height: 44px;
334
+ transform: translate(-50%, -50%);
335
+ }
336
+ }
337
+ /* The editing tools, as one block so a narrow viewport can drop them together. Below 640px the bar was
338
+ pushing Publish and its deploy caret clean off the right edge — measured at 390px, the primary action
339
+ of the whole screen was unreachable. The tools live on in the overflow menu; Publish does not have
340
+ anywhere else to go. */
341
+ /* The device group is a SEGMENTED control: its options sit 2px apart on purpose, the way a segmented
342
+ control reads as one object. Overlaying 44px targets on options that close makes each target swallow
343
+ the ones beside it — probing the corners, two of three resolved to a neighbour. Segments belong to the
344
+ 24px tier (WCAG 2.5.8 AA), which at 36x32 they clear; the standalone icon buttons carry the 44px
345
+ target. Naming the two tiers separately is the point: one rule for both is what produced targets that
346
+ overlap. */
347
+ .gemboss-editor-topbar--theme .gemboss-pv-devices .gemboss-pv-device::after,
348
+ .gemboss-editor-topbar.gemboss-pageedit-top .gemboss-pv-devices .gemboss-pv-device::after { content: none; }
349
+ .gemboss-editor-tb-tools { display: flex; align-items: center; gap: 16px; min-width: 0; }
350
+ /* SAME 1200px as useNarrowTopbar, not 640. At 640 this rule could never fire — the hook has
351
+ already unmounted the block by then — while 641-1200px had no CSS guard at all, so on every load of a
352
+ 1024px laptop six controls painted and then vanished when the effect ran. One decision, one number. */
353
+ @media (max-width: 1200px) {
354
+ .gemboss-editor-tb-tools { display: none; }
355
+ }
356
+ .gemboss-pv-device:hover { border-color: var(--gemboss-primary); color: var(--gemboss-active-fg); }
357
+ .gemboss-pv-device:focus-visible { outline: 2px solid var(--gemboss-primary); outline-offset: 2px; }
358
+ .gemboss-pv-device.active { border-color: var(--gemboss-primary); color: var(--gemboss-active-fg); background: var(--gemboss-active-bg); }
359
+ /* ── Page picker (Shopify-style) ── replaces the flat page pills in the editor
360
+ top bar: a trigger (icon · current page · caret) opening a searchable popover.
361
+ The popover is position:fixed (anchored to the trigger in JS) so it escapes the
362
+ topbar's mobile horizontal-scroll container instead of being clipped by it. */
363
+ .gemboss-pagepicker { position: relative; display: inline-flex; }
364
+ .gemboss-pagepicker-trigger {
365
+ display: inline-flex; align-items: center; gap: 8px;
366
+ height: 32px; padding: 0 8px 0 12px; border-radius: 8px;
367
+ border: 1px solid var(--gemboss-divider); background: var(--gemboss-surface); cursor: pointer;
368
+ font-size: 13px; font-weight: 600; color: var(--gemboss-text); line-height: 1;
369
+ transition: border-color var(--gemboss-transition-fast) ease, background var(--gemboss-transition-fast) ease;
370
+ }
371
+ .gemboss-pagepicker-trigger:hover { border-color: var(--gemboss-primary); }
372
+ .gemboss-pagepicker-trigger[aria-expanded="true"] { border-color: var(--gemboss-primary); background: var(--gemboss-active-bg, var(--gemboss-danger-soft)); }
373
+ .gemboss-pagepicker-trigger-icon { display: inline-flex; color: var(--gemboss-text-muted); }
374
+ .gemboss-pagepicker-trigger-icon svg { display: block; }
375
+ .gemboss-pagepicker-trigger-label { max-width: 160px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
376
+ .gemboss-pagepicker-caret { font-size: 9px; color: var(--gemboss-text-muted); margin-left: 1px; }
377
+ .gemboss-pagepicker-menu {
378
+ /* Surface comes from `gemboss-popover-surface`; this block is geometry and entry only. */
379
+ position: fixed; z-index: 500;
380
+ padding: 8px; display: flex; flex-direction: column; gap: 6px;
381
+ animation: gemboss-pagepicker-in 140ms cubic-bezier(0.16, 1, 0.3, 1);
382
+ }
383
+ @media (prefers-reduced-motion: reduce) {
384
+ .gemboss-pagepicker-menu { animation: none; } }
385
+ .gemboss-pagepicker-search { position: relative; display: flex; align-items: center; }
386
+ .gemboss-pagepicker-search-icon {
387
+ position: absolute; left: 10px; display: inline-flex; color: var(--gemboss-text-muted); pointer-events: none;
388
+ }
389
+ /* Layout only — the look is `gemboss-field`. The left padding is this component's: it parks a search
390
+ icon there. `input.` for the specificity: at (0,1,0) this rule sat BEFORE `.gemboss-field`'s own
391
+ `padding: 0 12px` and lost, so the query ran under the magnifier (measured, /code-review #3206). */
392
+ input.gemboss-pagepicker-search-input { width: 100%; padding-left: 32px; }
393
+ .gemboss-pagepicker-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 1px; max-height: 320px; overflow-y: auto; }
394
+ .gemboss-pagepicker-item {
395
+ width: 100%; display: flex; align-items: center; gap: 12px;
396
+ border: 0; background: transparent; cursor: pointer; text-align: left;
397
+ padding: 8px 12px; border-radius: var(--gemboss-radius-md); font-size: var(--gemboss-fs-body); color: var(--gemboss-text);
398
+ }
399
+ .gemboss-pagepicker-item:hover, .gemboss-pagepicker-item:focus-visible { background: var(--gemboss-hover, rgba(0,0,0,0.05)); outline: none; }
400
+ .gemboss-pagepicker-item.active { background: var(--gemboss-active-bg, var(--gemboss-danger-soft)); color: var(--gemboss-active-fg); font-weight: 600; }
401
+ .gemboss-pagepicker-item-icon { display: inline-flex; color: var(--gemboss-text-muted); flex: 0 0 auto; }
402
+ .gemboss-pagepicker-item.active .gemboss-pagepicker-item-icon { color: var(--gemboss-active-fg); }
403
+ .gemboss-pagepicker-item-icon svg { display: block; }
404
+ .gemboss-pagepicker-item-label { flex: 1; min-width: 0; }
405
+ .gemboss-pagepicker-check { color: var(--gemboss-active-fg); font-size: 13px; flex: 0 0 auto; }
406
+ .gemboss-pagepicker-empty { padding: 12px 12px; font-size: 13px; color: var(--gemboss-text-muted); }
407
+ .gemboss-pagepicker-note { padding: 8px 12px 12px; font-size: 12px; line-height: 1.5; color: var(--gemboss-text-muted); border-bottom: 1px solid var(--gemboss-border, #e3e3e3); }
408
+ .gemboss-pagepicker-note-link { color: var(--gemboss-primary); font-weight: 600; text-decoration: none; }
409
+ .gemboss-pagepicker-note-link:hover { text-decoration: underline; }
410
+ /* Drill-down: back row + the "→" affordance on Products/Collections + resource thumbs. */
411
+ .gemboss-pagepicker-back {
412
+ display: flex; align-items: center; gap: 6px; width: 100%;
413
+ border: 0; background: transparent; cursor: pointer; text-align: left;
414
+ padding: 4px 6px 8px; margin-bottom: 2px; border-bottom: 1px solid var(--gemboss-divider-soft, #edeff2);
415
+ font-size: 13px; font-weight: 600; color: var(--gemboss-text);
416
+ }
417
+ .gemboss-pagepicker-back:hover { color: var(--gemboss-active-fg); }
418
+ .gemboss-pagepicker-back:focus-visible { outline: 2px solid var(--gemboss-primary); outline-offset: 2px; }
419
+ .gemboss-pagepicker-back-icon { display: inline-flex; color: var(--gemboss-text-muted); }
420
+ .gemboss-pagepicker-back-icon svg { display: block; }
421
+ .gemboss-pagepicker-drill { display: inline-flex; color: var(--gemboss-text-muted); flex: 0 0 auto; }
422
+ .gemboss-pagepicker-drill svg { display: block; }
423
+ .gemboss-pagepicker-thumb {
424
+ width: 30px; height: 30px; flex: 0 0 auto; border-radius: 8px; object-fit: cover;
425
+ border: 1px solid var(--gemboss-divider); background: var(--gemboss-panel-bg, #ffffff);
426
+ }
427
+ .gemboss-pagepicker-thumb--default { display: inline-flex; align-items: center; justify-content: center; color: var(--gemboss-text-muted); }
428
+ .gemboss-pagepicker-thumb--default svg { width: 16px; height: 16px; }
429
+ .gemboss-pv-stage { flex: 1; min-height: 0; overflow: hidden; display: flex; justify-content: center; padding: 0; }
430
+ /* position:relative so the double-buffered iframes stack and cross-fade in place */
431
+ .gemboss-pv-frame { background: var(--gemboss-surface); overflow: hidden; height: 100%; position: relative; }
432
+ /* Desktop preview fills the whole pane (full-bleed, no max-width). */
433
+ .gemboss-pv-frame.desktop { width: 100%; max-width: none; }
434
+ /* "No chat panel" preview scaling (theme / page / section editors, via usePreviewScale). When the
435
+ chat panel narrows the pane BELOW a comfortable desktop width, a plain-width render would trip a
436
+ tablet/mobile breakpoint — so we lay the iframe out at a fixed desktop canvas (--gemboss-pv-w) and
437
+ scale it DOWN to fit (--gemboss-pv-scale = pane / canvas), keeping the TRUE desktop design. A pane
438
+ that's already a usable desktop width (incl. a collapsed chat) clears the vars → plain full-bleed
439
+ 1:1 (crispest, no transform). Absolute + origin top-left so the scaled box lands flush in the pane
440
+ (not centered on its un-scaled width) — its host (.gemboss-pv-stage / .gemboss-pageedit-pv) is a
441
+ positioning context. Var fallbacks (100% / 1) === full-bleed. The `.desktop` part gates it:
442
+ mobile keeps its native 390px frame. */
443
+ .gemboss-pv-frame.gemboss-pv-fit.desktop {
444
+ position: absolute; top: 0; left: 0;
445
+ width: var(--gemboss-pv-w, 100%);
446
+ height: var(--gemboss-pv-h, 100%);
447
+ /* auto-fit scale (usePreviewScale, narrow panes) × the user's zoom (default 1 → unchanged). */
448
+ transform: scale(calc(var(--gemboss-pv-scale, 1) * var(--gemboss-pv-user-zoom, 1)));
449
+ transform-origin: top left;
450
+ }
451
+ .gemboss-pv-frame.mobile { width: 390px; flex: 0 0 390px; align-self: center; margin: 16px 0; border: 1px solid var(--gemboss-divider); border-radius: 20px; box-shadow: var(--gemboss-shadow-overlay); }
452
+ .gemboss-pv-frame.tablet { width: 768px; flex: 0 0 768px; align-self: center; margin: 16px 0; border: 1px solid var(--gemboss-divider); border-radius: 16px; box-shadow: var(--gemboss-shadow-overlay); }
453
+ .gemboss-pv-iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; display: block; background: var(--gemboss-surface); transition: opacity 180ms ease; }
454
+ .gemboss-pv-empty { margin: auto; text-align: center; max-width: 340px; display: flex; flex-direction: column; align-items: center; gap: 8px; }
455
+ .gemboss-pv-empty-art { font-size: 40px; line-height: 1; opacity: 0.7; }
456
+ .gemboss-pv-empty-title { font-family: var(--gemboss-serif); font-size: 18px; margin: 0; }
457
+ .gemboss-pv-empty-sub { font-size: 13px; color: var(--gemboss-text-muted); margin: 0; line-height: 1.5; }
458
+ .gemboss-pv-empty-cta { margin-top: 6px; font-size: 13px; font-weight: 600; color: var(--gemboss-active-fg); text-decoration: none; }
459
+ .gemboss-pv-empty-cta:hover { text-decoration: underline; }
460
+ .gemboss-pv-spin { width: 22px; height: 22px; border: 2px solid var(--gemboss-divider-soft); border-top-color: var(--gemboss-primary); border-radius: 50%; animation: gemboss-spin 0.7s linear infinite; }
461
+ /* Draggable split between chat and preview */
462
+ /* Resizer doubles as the gap between the two panels (its width = --gemboss-editor-inset). Its LOOK — the
463
+ grip pill, the 24px finger band, the drag cursor and the full-page shield over the preview iframe — is
464
+ `.ui-resizer` / `body.ui-resizing` in @gemboss/ui's ui.css since 01/10/2026, so gemcare's panels and
465
+ these editors are one component (Chris: "gemcare cần kế thừa từ gemboss ui design system thì cần
466
+ resize được panel"). Only the editor's GEOMETRY stays here: it is a flex item one seam wide. */
467
+ .gemboss-editor-resizer { flex: 0 0 var(--gemboss-editor-inset); align-self: stretch; }
468
+ body.ui-resizing .gemboss-pv-iframe { pointer-events: none; }
469
+ /* Collapse-chat toggle (top bar): hide the chat for a full-width preview. */
470
+ .gemboss-editor-chat-toggle {
471
+ width: 30px; height: 30px; display: inline-flex; align-items: center; justify-content: center;
472
+ border: 1px solid var(--gemboss-divider); background: var(--gemboss-surface); border-radius: var(--gemboss-radius-md); cursor: pointer;
473
+ font-size: 14px; line-height: 1; color: var(--gemboss-text-muted);
474
+ transition: border-color var(--gemboss-transition-fast) ease, color var(--gemboss-transition-fast) ease;
475
+ }
476
+ .gemboss-editor-chat-toggle:hover { border-color: var(--gemboss-primary); color: var(--gemboss-active-fg); }
477
+ .gemboss-editor-chat-toggle:focus-visible { outline: 2px solid var(--gemboss-primary); outline-offset: 2px; }
478
+ .gemboss-editor-chat-toggle.collapsed { border-color: var(--gemboss-primary); color: var(--gemboss-active-fg); }
479
+ .gemboss-editor-chat-toggle svg { display: block; }
480
+ /* Chat width is driven by --gemboss-chat-w (0 when collapsed); animate it shut and drop the resizer. */
481
+ .gemboss-editor-body .gemboss-ec { transition: flex-basis 280ms cubic-bezier(0.16, 1, 0.3, 1), width 280ms cubic-bezier(0.16, 1, 0.3, 1); }
482
+ /* …but NOT while dragging the splitter: the collapse transition would animate every
483
+ pointer move (280ms ease per move), so the panel rubber-bands toward the cursor and
484
+ the preview iframe reflows continuously — that's the lag. During a drag the width
485
+ must track the cursor 1:1. `will-change` promotes the panels so the per-frame width
486
+ change composites instead of triggering a full layout pass. */
487
+ body.ui-resizing .gemboss-editor-body .gemboss-ec { transition: none; will-change: flex-basis, width; }
488
+ body.ui-resizing .gemboss-editor-body .gemboss-pv { will-change: flex-basis; }
489
+ .gemboss-editor-body.is-chat-collapsed .gemboss-ec { border-width: 0; overflow: hidden; }
490
+ .gemboss-editor-body.is-chat-collapsed .gemboss-editor-resizer { display: none; }
491
+ /* Same thin scrollbar on the other internal scroll panels (page picker, sidebar nav) so the whole admin is consistent — not the chunky OS bar. */
492
+ .gemboss-pagepicker-list { scrollbar-width: thin; scrollbar-color: rgba(31,30,29,0.22) transparent; }
493
+ .gemboss-pagepicker-list::-webkit-scrollbar { width: 9px; }
494
+ .gemboss-pagepicker-list::-webkit-scrollbar-track { background: transparent; }
495
+ .gemboss-pagepicker-list::-webkit-scrollbar-thumb { background: rgba(31,30,29,0.2); border-radius: 6px; border: 2px solid transparent; background-clip: content-box; }
496
+ @media (max-width: 1000px) {
497
+ /* Stack chat over preview. The vertical resizer is meaningless when stacked, so
498
+ hide it and give the two cards a small gap of their own instead. */
499
+ .gemboss-editor-body { flex-direction: column; gap: var(--gemboss-editor-inset); }
500
+ }
501
+ @media (max-width: 1000px) {
502
+ .gemboss-editor-resizer { display: none; }
503
+ /* Collapse the chat by height here (the width-based collapse can't apply when stacked). */
504
+ .gemboss-editor-body.is-chat-collapsed .gemboss-ec { max-height: 0; min-height: 0; }
505
+ /* Topbar: reflow the 3-column grid into two rows — [back/toggle | actions] on top,
506
+ the page + device tabs below. Each cluster scrolls horizontally rather than
507
+ squashing or clipping (the action cluster grows past the viewport once
508
+ "Preview on store" adds its links). */
509
+ .gemboss-editor-topbar {
510
+ height: auto;
511
+ grid-template-columns: auto minmax(0, 1fr);
512
+ grid-template-areas:
513
+ "left right"
514
+ "center center";
515
+ gap: 8px 12px;
516
+ padding: 8px 12px;
517
+ }
518
+ .gemboss-editor-tb-left { grid-area: left; }
519
+ /* Keeps the base `justify-self: end` so the cluster hugs the right edge when it
520
+ fits; max-width:100% caps it at its grid cell so it scrolls internally (rather
521
+ than overflowing the topbar) once the actions outgrow the row. Content is
522
+ left-origin, so the scroll always reaches the last button (Publish). */
523
+ .gemboss-editor-tb-right {
524
+ /* overflow-x: auto forces overflow-y: auto, which clips the 44px target overlay's 6px overhang on
525
+ whatever icon buttons are left here — and this is the width range where a finger, not a mouse, is
526
+ doing the pointing. The padding gives the overhang somewhere to live. */
527
+ padding-block: 6px;
528
+ grid-area: right; min-width: 0; max-width: 100%;
529
+ overflow-x: auto; -webkit-overflow-scrolling: touch; scrollbar-width: none;
530
+ }
531
+ .gemboss-editor-tb-center {
532
+ grid-area: center; justify-self: stretch; justify-content: flex-start;
533
+ overflow-x: auto; -webkit-overflow-scrolling: touch; scrollbar-width: none;
534
+ }
535
+ .gemboss-editor-tb-right::-webkit-scrollbar,
536
+ .gemboss-editor-tb-center::-webkit-scrollbar { display: none; }
537
+ .gemboss-editor-tb-right > * { flex: 0 0 auto; }
538
+ /* The CTA does not scroll away. Everything else in this cluster may go off the end and be scrolled
539
+ back — that is what the overflow above is for — but the primary action of the editor is not
540
+ something a seller should have to discover by dragging a strip with no scrollbar. Sticky keeps it
541
+ parked at the right edge with the rest sliding under it; the background is what makes "under"
542
+ read as under rather than as overlapping text. */
543
+ .gemboss-editor-tb-right > .gemboss-editor-tb-publish {
544
+ position: sticky; right: 0; z-index: 1;
545
+ box-shadow: -8px 0 8px -6px color-mix(in srgb, var(--gemboss-text) 18%, transparent);
546
+ }
547
+ /* Reclaim width: the back arrow already carries context, and button labels
548
+ ("Preparing…", "Publishing…") carry the action status inline. */
549
+ .gemboss-editor-tb-name { display: none; }
550
+ .gemboss-editor-tb-ghost { padding: 6px 12px; font-size: 12px; }
551
+ .gemboss-editor-tb-publish { padding: 8px 16px; font-size: 13px; }
552
+ }
553
+ /* GONE: `@media (max-width: 380px)` that re-stacked this bar into three rows. It was written for the
554
+ PAGE editor — "at 320px the actions cluster is given 84px and the Publish button is 87px" — but it
555
+ selected `.gemboss-editor-topbar`, which is all THREE bars, and that is the §9.3 fault: the page and
556
+ section editors already have an owner for this (`.gemboss-pageedit-top` in the `max-width:900px`
557
+ block further down), equal specificity and later in the file, so it wins there regardless. The only
558
+ bar this block actually changed was the THEME editor's — the one bar whose clusters fit on one row.
559
+
560
+ Measured 2026-09-10 with all three open, natural cluster widths (children + gaps), at 320px where
561
+ the bar has 285px of usable width:
562
+
563
+ theme editor left 40px + gap 8 + right 213px = 261 → FITS, with 24px to spare
564
+ page editor left 238px + gap 8 + right 379px = 625 → does not fit, and its own block stacks it
565
+
566
+ So the theme bar was spending a whole row on a 40px back arrow across 285px of empty space, and its
567
+ height went 125 → 173px on a phone for nothing. Its second rule
568
+ (`.gemboss-pageedit .gemboss-editor-tb-right > :first-child { margin-inline-start: auto }`) was a
569
+ verbatim duplicate of the unconditional one at the `.gemboss-pageedit` rules below, so nothing is
570
+ lost with it either. `editor-topbar-controls-reach-44.test.ts` pins the absence. */
571
+ @media (max-width: 640px) {
572
+ /* The fixed 390px phone-preview frame would overflow the already-narrow pane and
573
+ clip on the right — let it shrink to fit while keeping the device chrome. */
574
+ .gemboss-pv-frame.mobile { width: 100%; max-width: 390px; flex: 0 1 auto; margin: 8px 0; }
575
+ .gemboss-pv-frame.tablet { width: 100%; max-width: 768px; flex: 0 1 auto; margin: 8px 0; }
576
+ }
577
+ /* ════ Editor — mobile single-pane (Chat | Preview tabs) ════════════════════════
578
+ The preview pane's flex display lives in CSS (was an inline style) so the mobile
579
+ media query can toggle which pane shows. Tabs hidden on desktop (both panes show
580
+ side by side). Appended last to win the duplicate base rules. */
581
+ .gemboss-editor-preview-pane { display: flex; }
582
+ .gemboss-editor-mobile-tabs { display: none; }
583
+ @media (max-width: 760px) {
584
+ .gemboss-editor-resizer { display: none; }
585
+ /* full-height single pane (overrides the ≤1000px stacked layout's max-height:48%) */
586
+ .gemboss-editor-body .gemboss-ec { flex: 1 1 auto; width: 100%; min-width: 0; max-height: none; border-right: none; }
587
+ .gemboss-editor-body .gemboss-editor-preview-pane { flex: 1 1 auto; }
588
+ .gemboss-editor-body[data-mobile-pane="chat"] .gemboss-editor-preview-pane { display: none; }
589
+ .gemboss-editor-body[data-mobile-pane="preview"] .gemboss-ec { display: none; }
590
+ .gemboss-editor-mobile-tabs {
591
+ display: flex;
592
+ flex: 0 0 auto;
593
+ gap: 6px;
594
+ padding: 6px;
595
+ padding-bottom: calc(6px + env(safe-area-inset-bottom));
596
+ background: var(--gemboss-surface);
597
+ border-top: 1px solid var(--gemboss-divider);
598
+ }
599
+ .gemboss-editor-mobile-tab {
600
+ flex: 1 1 0;
601
+ height: 38px;
602
+ border: 0;
603
+ border-radius: 8px;
604
+ background: transparent;
605
+ color: var(--gemboss-text-muted);
606
+ font: inherit;
607
+ font-size: 13px;
608
+ font-weight: 600;
609
+ cursor: pointer;
610
+ }
611
+ .gemboss-editor-mobile-tab.active {
612
+ background: var(--gemboss-active-bg);
613
+ color: var(--gemboss-active-fg);
614
+ }
615
+ }
616
+ /* ════ Editor status strip — draft/publish progress as a full-width line under the
617
+ top bar (was an inline span that overflowed its grid track and overlapped the
618
+ device/select buttons). ════ */
619
+ .gemboss-editor-statusbar {
620
+ /* Transient draft/publish feedback. FLOATS below the bar as an overlay — it used to be a
621
+ flex sibling of the topbar, so every "Preview on store" click inserted a ~24px strip that
622
+ shoved the whole canvas down (a visible jolt). Anchored top-right under the action buttons
623
+ it belongs to; pointer-events:none so it never eats a canvas click. */
624
+ position: absolute;
625
+ top: 60px;
626
+ right: 14px;
627
+ z-index: 480; /* above the canvas (400), below the overflow menu (500) */
628
+ max-width: min(380px, calc(100vw - 28px));
629
+ padding: 8px 16px;
630
+ font-size: 12px;
631
+ line-height: 1.4;
632
+ color: var(--gemboss-text-muted);
633
+ background: var(--gemboss-surface);
634
+ border: 1px solid var(--gemboss-divider);
635
+ border-radius: 12px;
636
+ box-shadow: var(--gemboss-shadow-raised);
637
+ pointer-events: none;
638
+ animation: gemboss-statusbar-in 0.18s cubic-bezier(0.16, 1, 0.3, 1) both;
639
+ }
640
+ .gemboss-editor-statusbar.done { background: #eaf6ef; color: #1f7a4d; border-color: var(--gemboss-success-border); }
641
+ .gemboss-editor-statusbar.error { background: #fdecea; color: var(--gemboss-danger); border-color: #f3c2bb; }
642
+ @media (pointer: coarse) {
643
+ .gemboss-pv-device { width: 38px; height: 38px; font-size: 16px; }
644
+ /* 44, not 40. Nó ở NGAY TRÊN chat toggle trong chính block này, trên chính thanh đó, và dừng ở 40
645
+ — đúng dấu vết mà comment dưới đây gọi tên: "raised … to 'match their neighbours' at 38 and 40 —
646
+ a relative target, and the neighbours were short too, so both stopped one tier below the
647
+ standard". Cái này bị bỏ lại khi hàng xóm của nó được nâng.
648
+ VÀ NÓ KHÔNG VÔ NGHĨA Ở KHỔ HẸP như chat toggle: `.gemboss-editor-back` không có `display: none`
649
+ ở bất kỳ đâu trong file này, nên nó render ở MỌI khổ, kể cả 320 và 390 — nơi 40px là một target
650
+ người ta chạm thật. Thanh này đã được đo có chỗ cho 44 (xem chat toggle ngay dưới: 38→44 trên
651
+ một thanh 52px "with room to spare").
652
+ `editor-topbar-controls-reach-44.test.ts` nhắc selector này 0 lần trước hôm nay — cổng canh đúng
653
+ thanh này không hề canh nó. Nay có. */
654
+ .gemboss-editor-back { width: 44px; height: 44px; font-size: 20px; }
655
+ /* 44, not 38. This is the THEME editor's own control and the passes that set 38 could not open
656
+ that bar — I could not either, until I stopped believing my own "no route renders it": the page
657
+ does not load the job server-side, so a made-up jobId renders the whole topbar. Measured there:
658
+ 38x38 on a 52px bar with room to spare.
659
+ RANGE, because the first version of this comment said "at every width" and that was wrong: the
660
+ ≤760px block below drops this control with `display: none`, so 320 and 390 render no element at
661
+ all. This 44 covers 768 and up. Lifting that hide means measuring the two phone widths first —
662
+ `editor-topbar-controls-reach-44.test.ts` fails if it moves. */
663
+ .gemboss-editor-chat-toggle { width: 44px; height: 44px; font-size: 16px; }
664
+ }
665
+ @media (pointer: coarse) {
666
+ /* Height, not only padding. Measured with all three editors OPEN (the page editor on a seeded
667
+ page, the section editor on a registry id) at 390, 1024 and 1366: these two stood at 34 and 41px
668
+ while every other control on that bar already cleared its tier. Padding alone had left them
669
+ short — a number nobody had gone back and measured.
670
+ Standalone buttons, so the 44px CONTROL tier applies. The segmented options beside them stay at
671
+ 24 on purpose: their overlays were removed because each target swallowed its neighbour, and that
672
+ decision is written three rules above this one. */
673
+ .gemboss-editor-tb-ghost { padding: 8px 16px; min-height: 44px; }
674
+ .gemboss-editor-tb-publish { padding: 12px 20px; min-height: 44px; }
675
+ /* Same bar, same reason: 38px tall while its neighbours cleared their tier. Height only — its
676
+ width is set by the page name it shows, so nothing moves sideways. */
677
+ .gemboss-pagepicker-trigger { min-height: 44px; }
678
+ }
679
+ @media (pointer: coarse) {
680
+ /* The chat panel — the ones this block used to walk past.
681
+ `.gemboss-ec-chip` is NOT here: it already declares `min-height: 44px` in its own rule, for every
682
+ pointer. My first draft listed it anyway, from reading the source for likely class names — and the
683
+ browser said the 30px buttons in that panel are `.gemboss-ec-starter`, a class I had not looked at.
684
+ Guessing which selector renders is how a fix lands beside the thing it was aimed at. */
685
+ .gemboss-ec-tab { min-height: 44px; }
686
+ }
687
+ /* ════ Editor top bar — phone declutter (≤760px) ════════════════════════════════ */
688
+ @media (max-width: 760px) {
689
+ /* The Chat | Preview tabs own pane-switching on a phone, so the chat-collapse
690
+ toggle (◫) is redundant — drop it to free space in the cramped toolbar. */
691
+ .gemboss-editor-chat-toggle { display: none; }
692
+ }
693
+ /* ════ Page editor shell (was a component-local <style> in PageEditor.tsx) ═════
694
+ Named, reusable editor chrome belongs here, not inline per editor (design
695
+ language B1). Body shell + topbar grid are shared from .gemboss-editor-* above;
696
+ these are the page-editor-specific picker / layers / settings drawers. */
697
+ .gemboss-pageedit{position:fixed;inset:0;display:flex;flex-direction:column;background:var(--gemboss-panel-bg,#ffffff);z-index:320}
698
+ .gemboss-pageedit-top{background:#fff;border-bottom:1px solid var(--gemboss-divider,#e5e7ea)}
699
+ .gemboss-pageedit .gemboss-editor-tb-center{display:flex;align-items:center;gap:12px;justify-content:center}
700
+ /* `margin-inline-start: auto` on the first child, NOT `justify-content: flex-end`.
701
+ They look identical when the cluster fits and behave oppositely when it does not. With flex-end the
702
+ overflow goes out the START edge, where no scrolling can reach it — the browser does not even report
703
+ it: measured 2026-09-09 at 320px, this cluster was 84px wide holding 361px of controls, and
704
+ `scrollWidth === clientWidth === 84`. "+ Add section" sat at offsetLeft -53, entirely off the left,
705
+ and Publish showed its last 50px as "blish". The <=1000px block above sets `overflow-x: auto` on this
706
+ element for exactly this width and says "content is left-origin, so the scroll always reaches the
707
+ last button (Publish)" — this rule, two classes and later in the file, was quietly making that untrue.
708
+ An auto start-margin right-aligns the cluster when there is room and collapses to 0 when there is not,
709
+ so the overflow lands on the END edge, where the scroll the block already installed can reach it. */
710
+ .gemboss-pageedit .gemboss-editor-tb-right{display:flex;align-items:center;gap:12px;min-width:0}
711
+ .gemboss-pageedit .gemboss-editor-tb-right > :first-child{margin-inline-start:auto}
712
+ .gemboss-pageedit .gemboss-editor-tb-left{display:flex;align-items:center;gap:12px;min-width:0}
713
+ .gemboss-pageedit-title{font-family:var(--gemboss-serif,inherit);font-size:15px;font-weight:600;color:var(--gemboss-text,#1b1c1e);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:32ch}
714
+ .gemboss-pageedit-modes{flex:none;display:flex}
715
+ /* Page notices stay in the layout so saved/preview messages cannot cover the mobile action menu. */
716
+ .gemboss-pageedit-notice{display:flex;flex-wrap:wrap;align-items:center;gap:12px;flex:0 0 auto;padding:12px 16px;background:var(--gemboss-surface);border-bottom:1px solid var(--gemboss-divider);color:var(--gemboss-text);font-size:13px;line-height:1.5}
717
+ .gemboss-pageedit-notice span{flex:1 1 240px;min-width:0}
718
+ .gemboss-pageedit-notice button{min-height:44px;padding:8px 12px;flex:0 0 auto;border:1px solid var(--gemboss-divider);border-radius:8px;background:var(--gemboss-surface);color:var(--gemboss-text);cursor:pointer}
719
+ @media(max-width:900px) {
720
+ .gemboss-pageedit-top{height:auto;grid-template-columns:minmax(0,1fr);grid-template-areas:"left" "right" "center";gap:8px;padding:8px 12px}
721
+ .gemboss-pageedit-top .gemboss-editor-tb-right{flex-wrap:wrap;overflow:visible}
722
+ .gemboss-pageedit-top .gemboss-editor-tb-right button{min-height:44px}
723
+ .gemboss-pageedit .gemboss-editor-tb-left{width:100%;overflow:hidden;flex-wrap:wrap}
724
+ .gemboss-pageedit-modes{flex-basis:100%}
725
+ .gemboss-pageedit .gemboss-editor-tb-right,.gemboss-pageedit .gemboss-editor-tb-center{justify-self:stretch;justify-content:flex-start;max-width:100%;overflow-x:auto}
726
+ }
727
+ /* Neutral type chip (section editor) — shows the humanized archetype, NOT a status. */
728
+ .gemboss-sec-type{font-size:11px;font-weight:700;letter-spacing:.02em;padding:2px 8px;border-radius:12px;background:#f1efe9;color:var(--gemboss-text-muted);text-transform:capitalize}
729
+ .gemboss-sec-insert-wrap{position:relative}
730
+ /* Surface comes from `gemboss-popover-surface`. This one carried `background:#fff` hardcoded, the
731
+ same shape the account menu carried in inline styles — found 25/09/2026 by the gate written for
732
+ this family, not by reading the file. */
733
+ .gemboss-sec-insert-menu{position:absolute;top:42px;right:0;width:280px;max-height:60vh;overflow-y:auto;padding:6px;z-index:80}
734
+ .gemboss-sec-insert-item{width:100%;display:flex;align-items:center;justify-content:space-between;gap:12px;padding:8px 12px;border:0;background:transparent;border-radius:8px;cursor:pointer;text-align:left;font-size:13px;color:var(--gemboss-text,#1b1c1e);transition:background .12s ease,color .12s ease}
735
+ .gemboss-sec-insert-item:hover{background:var(--gemboss-hover,rgba(0,0,0,.04))}
736
+ .gemboss-sec-insert-item:focus-visible{outline:2px solid var(--gemboss-primary, #1b1c1e);outline-offset:-2px;background:var(--gemboss-hover,rgba(0,0,0,.04))}
737
+ .gemboss-sec-insert-item:disabled{opacity:.6;cursor:default}
738
+ .gemboss-sec-insert-item:disabled:hover{background:transparent}
739
+ .gemboss-sec-insert-cat{font-size:var(--gemboss-fs-caption);color:var(--gemboss-text-muted,#686b71);text-transform:capitalize}
740
+ .gemboss-sec-insert-empty{display:block;padding:16px;font-size:13px;color:var(--gemboss-text-muted,#686b71);text-decoration:none}
741
+ .gemboss-pageedit-status{font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;padding:2px 8px;border-radius:12px;background:#eef1ff;color:#2b50d6}
742
+ .gemboss-pageedit-status.is-published{background:#e7f6ec;color:#1f8f4e}
743
+ /* Which of the two jobs this editor is doing. A page is one URL and gets the quiet neutral chip; a
744
+ template is a layout the storefront applies to every product or collection page, so it gets the
745
+ emphatic one plus a scope line saying what it reaches. The topbar used to read "Page editor" for
746
+ both, and `type` is not editable here, so nothing on screen told them apart. */
747
+ .gemboss-pageedit-kind{font-size:11px;font-weight:600;letter-spacing:.02em;padding:2px 8px;border-radius:12px;background:var(--gemboss-surface-2,#f1f2f5);color:var(--gemboss-text-muted,#686b71);border:1px solid var(--gemboss-divider,#e5e7ea);white-space:nowrap}
748
+ .gemboss-pageedit-kind.is-template{background:#fbf1df;color:#a9691a;border-color:#efd9ac;text-transform:uppercase;letter-spacing:.04em;font-weight:700}
749
+ .gemboss-pageedit-scope{font-size: var(--gemboss-fs-caption);color:var(--gemboss-text-muted,#686b71);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-width:0}
750
+ /* Below 1200 the bar already drops the static "Template editor" caption (see the rule above), so the
751
+ chip is the ONLY thing left saying which job this is — it survives every width. The scope sentence
752
+ is the label that goes, being the longest and the one the chip's tooltip repeats. */
753
+ @media (max-width:1200px) {
754
+ .gemboss-pageedit-scope{display:none}}
755
+ .gemboss-pageedit-pv{flex:1;min-width:0;background:var(--gemboss-preview-canvas);display:flex;justify-content:center;overflow:auto;position:relative}
756
+ .gemboss-pageedit-pv .gemboss-pv-frame iframe{width:100%;height:100%;border:0;background:#fff;display:block}
757
+ .gemboss-pageedit-chip{display:flex;align-items:center;gap:8px;margin:0 12px 8px;padding:6px 12px;border-radius:8px;background:rgba(86,97,245,.1);border:1px solid rgba(86,97,245,.3);font-size:12px;color:#3a44de}
758
+ .gemboss-pageedit-chip strong{text-transform:capitalize}
759
+ .gemboss-pageedit-chip button{margin-left:auto;border:0;background:transparent;color:#3a44de;cursor:pointer;font-size:12px}
760
+ /* Copy a winning test shipped onto this page. Amber, not the editor's indigo: this is not a mode the
761
+ seller turned on, it is a difference between what they are reading and what shoppers are served. */
762
+ .gemboss-pageedit-standing{display:flex;align-items:center;gap:12px;flex-wrap:wrap;padding:8px 16px;background:rgba(176,125,24,.09);border-bottom:1px solid rgba(176,125,24,.28);font-size:13px;line-height:1.5;color:#7a5710}
763
+ .gemboss-pageedit-standing code{font-size:12px;background:rgba(176,125,24,.14);border-radius:4px;padding:2px 4px}
764
+ .gemboss-pageedit-standing button{margin-left:auto;flex:0 0 auto;border:1px solid rgba(176,125,24,.4);background:#fff;color:#7a5710;border-radius:8px;padding:4px 12px;font:inherit;font-weight:600;cursor:pointer}
765
+ .gemboss-pageedit-standing button:disabled{opacity:.6;cursor:default}
766
+ .gemboss-picker-backdrop{position:fixed;inset:0;background:rgba(31,30,29,.22);z-index:70}
767
+ .gemboss-picker-head{flex:0 0 auto;height:52px;display:flex;align-items:center;justify-content:space-between;padding:0 16px;border-bottom:1px solid var(--gemboss-divider,#e5e7ea);font-weight:600;font-size:15px}
768
+ .gemboss-picker-head button{border:0;background:transparent;font-size:15px;cursor:pointer;color:var(--gemboss-text-muted,#686b71)}
769
+ .gemboss-picker-empty{padding:20px;text-align:center;color:var(--gemboss-text-muted,#686b71);font-size:13px}
770
+ .gemboss-layers{position:fixed;top:0;left:0;bottom:0;width:300px;background:#fff;border-right:1px solid var(--gemboss-divider,#e5e7ea);box-shadow:8px 0 30px rgba(31,30,29,.12);z-index:71;display:flex;flex-direction:column}
771
+ .gemboss-layers-list{flex:1;min-height:0;overflow-y:auto;padding:8px 12px;display:flex;flex-direction:column;gap:4px}
772
+ .gemboss-layer{display:flex;align-items:center;gap:4px;border:1px solid transparent;border-radius:8px;padding:0 4px 0 6px}
773
+ .gemboss-layer:hover{background:var(--gemboss-hover,rgba(0,0,0,.04))}
774
+ .gemboss-layer.is-sel{border-color:var(--gemboss-primary, #1b1c1e);background:rgba(86,97,245,.06)}
775
+ .gemboss-layer-main{flex:1;min-width:0;display:flex;flex-direction:column;align-items:flex-start;gap:1px;background:transparent;border:0;cursor:pointer;text-align:left;padding:8px 4px}
776
+ .gemboss-layer-type{font-size:13px;font-weight:600;color:var(--gemboss-text,#1b1c1e);text-transform:capitalize}
777
+ .gemboss-layer-head{font-size:var(--gemboss-fs-caption);color:var(--gemboss-text-muted,#686b71);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:195px}
778
+ .gemboss-layer-acts{display:flex;gap:0;flex:0 0 auto}
779
+ .gemboss-layer-acts button{width:26px;height:26px;border:0;background:transparent;border-radius:6px;cursor:pointer;color:var(--gemboss-text-muted,#686b71);font-size:13px;line-height:1}
780
+ .gemboss-layer-acts button:hover:not(:disabled){background:var(--gemboss-hover,rgba(0,0,0,.07))}
781
+ .gemboss-layer-acts button:disabled{opacity:.28;cursor:default}
782
+ .gemboss-layer-del:hover:not(:disabled){color:#c0392b}
783
+ .gemboss-settings{position:fixed;top:0;right:0;bottom:0;width:min(380px,100%);box-sizing:border-box;background:#fff;border-left:1px solid var(--gemboss-divider,#e5e7ea);box-shadow:-8px 0 30px rgba(31,30,29,.12);z-index:71;display:flex;flex-direction:column}
784
+ .gemboss-settings-body{flex:1;min-height:0;overflow-y:auto;padding:16px 20px;display:flex;flex-direction:column;gap:20px}
785
+ /* Group header inside the page-settings sheet: a section label with its own AI action on the right
786
+ (the SEO pair — title + meta description — is written from the page's copy, not typed by hand). */
787
+ .gemboss-settings-grouphead{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-top:4px;padding-top:16px;border-top:1px solid var(--gemboss-divider,#e5e7ea)}
788
+ .gemboss-settings-grouphead > span{font-size:var(--gemboss-fs-caption);font-weight:700;color:var(--gemboss-text,#1b1c1e)}
789
+ .gemboss-settings-gen{border:1px solid var(--gemboss-divider,#e5e7ea);background:var(--gemboss-surface,#fff);border-radius:999px;padding:4px 12px;font-size:12px;font-weight:600;font-family:inherit;color:var(--gemboss-text,#1b1c1e);cursor:pointer;white-space:nowrap}
790
+ .gemboss-settings-gen:hover:not(:disabled){background:var(--gemboss-hover,#eceef2)}
791
+ .gemboss-settings-gen:disabled{opacity:.55;cursor:default}
792
+ .gemboss-settings-field{display:flex;flex-direction:column;gap:6px}
793
+ /* The element panel's two <p>s. Without these they rendered identically, so a REFUSAL ("that element
794
+ could not be updated") looked exactly like the helper line above it and read as more instructions. */
795
+ .gemboss-settings-note{margin:0 0 4px;font-size:var(--gemboss-fs-caption);color:var(--gemboss-text-subdued,#6b675f);line-height:1.5}
796
+ .gemboss-settings-error{margin:0 0 8px;padding:8px 12px;border-radius:8px;font-size:var(--gemboss-fs-caption);line-height:1.45;color:#8a1c1c;background:#fdecec;border:1px solid #f5c2c2}
797
+ .gemboss-settings-field > span{font-size:var(--gemboss-fs-caption);font-weight:700;color:var(--gemboss-text,#1b1c1e)}
798
+ /* The fields in this panel wear `gemboss-field` (see its rule): their look is not this panel's. Only
799
+ the width is — a settings field fills its column. */
800
+ .gemboss-settings-field :is(input,textarea,select):not([type="color"]){width:100%}
801
+ .gemboss-settings-field small{font-size:var(--gemboss-fs-caption);color:var(--gemboss-text-muted,#686b71)}
802
+ .gemboss-settings-field textarea.gemboss-mono{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;line-height:1.5}
803
+ .gemboss-settings-handle span{color:var(--gemboss-text-muted,#686b71);white-space:nowrap}
804
+ .gemboss-settings-foot{flex:0 0 auto;display:flex;justify-content:flex-end;gap:12px;padding:12px 20px;border-top:1px solid var(--gemboss-divider,#e5e7ea)}
805
+ .gemboss-settings-cancel{background:transparent;border:1px solid var(--gemboss-divider,#e5e7ea);border-radius:8px;font-size:13px;font-weight:600;padding:8px 16px;cursor:pointer;color:var(--gemboss-text,#1b1c1e)}
806
+ .gemboss-settings-save{background:var(--gemboss-primary, #1b1c1e);color:#fff;border:0;border-radius:8px;font-size:13px;font-weight:700;padding:8px 20px;cursor:pointer}
807
+ .gemboss-settings-save:disabled{opacity:.6;cursor:default}
808
+ .gemboss-page-section-content :is(button,input,textarea){min-height:44px;min-width:44px}
809
+ .gemboss-pageedit .gemboss-pv-device{width:44px;height:44px;flex-shrink:0}
810
+ .gemboss-pageedit .gemboss-ec-dock-body :is(button,input,select){min-height:44px}
811
+ /* Global styles › Product-page bundle (BundleLookBlock): its controls are 44px (§9.1). Scoped to the form, not the
812
+ widget preview above it, whose buttons must keep the storefront's own size. */
813
+ .gemboss-bundle-look .gemboss-ei-body :is(button,input){min-height:44px}
814
+ .gemboss-bundle-look .gemboss-ei-body input[type=color]{min-width:44px}
815
+ .gemboss-page-draft-status{flex:0 0 auto;display:flex;align-items:center;justify-content:center;flex-wrap:wrap;gap:8px;padding:8px 12px;border-radius:8px;background:var(--gemboss-surface,#fff);color:var(--gemboss-text,#1b1c1e);font-size:12px;line-height:1.5;box-shadow:0 1px 6px #0001}
816
+ .gemboss-page-draft-status button{min-height:44px;min-width:44px;padding:8px 12px;border:1px solid var(--gemboss-border,#ccc);border-radius:6px;background:var(--gemboss-surface,#fff);cursor:pointer}
817
+ .gemboss-page-draft-status button:hover{background:var(--gemboss-hover,#eceef2)}
818
+ .gemboss-page-draft-status button:focus-visible{outline:2px solid #2563eb;outline-offset:2px}
819
+ .gemboss-page-draft-status[data-page-preview-state=ready]{pointer-events:none}
820
+ .gemboss-pageedit .gemboss-pageedit-pv{flex-direction:column;min-height:0;justify-content:flex-start}
821
+ .gemboss-page-draft-stage{flex:1;min-height:0;position:relative;overflow:hidden;display:flex;justify-content:center}
822
+ /* 44% is a PREFERENCE, not a ceiling, and it used to be both.
823
+ MEASURED at 320px (check:desktop-page-touchable): the body is 659px, so 44% gave the chat column
824
+ 286px — while its own fixed chrome needs 344px (tab row 61 + header 95 + banner 44 + composer 108
825
+ + mode help 36). The flexible transcript absorbed the whole shortfall and rendered at 24px: the
826
+ seller could see ONE line of the conversation they are having. `max-height:44%` is what stopped
827
+ the column from taking the room it needed, and `min-height:0` let the transcript go to nothing.
828
+ The floor is in px because the shortfall is in px — the chrome does not scale with the viewport —
829
+ and it yields to a percentage on a short screen so the preview never disappears entirely. */
830
+ @media(max-width:1000px) {
831
+ .gemboss-pageedit .gemboss-editor-body>.gemboss-ec{flex:0 0 auto;height:44%;min-height:min(520px,76%);max-height:76%}}
832
+ /* ==========================================================================
833
+ Element settings popup (ElementInspector) — the Divi/GemPages-style panel
834
+ that floats over the preview when a single section is selected. Uses the
835
+ editor's own design system (chat bubbles / page-pills / inputs) so it reads
836
+ as native. Floating card, docked top-right inside the (relative) preview pane.
837
+ ========================================================================== */
838
+ .gemboss-ei{position:absolute;top:12px;right:12px;z-index:6;width:320px;max-width:calc(100% - 24px);
839
+ display:flex;flex-direction:column;max-height:calc(100% - 24px);
840
+ background:var(--gemboss-surface);border:1px solid var(--gemboss-divider);border-radius:var(--gemboss-radius-xl);
841
+ box-shadow:var(--gemboss-shadow-overlay);overflow:hidden}
842
+ /* Docked in the Configure tab: fill the .gemboss-ec column, drop the floating chrome. */
843
+ .gemboss-ei--docked{position:static;top:auto;right:auto;width:100%;max-width:none;height:100%;max-height:none;
844
+ border:0;border-radius:0;box-shadow:none;background:transparent;flex:1;min-height:0}
845
+ .gemboss-ei-head{display:flex;align-items:center;gap:8px;padding:12px 12px;border-bottom:1px solid var(--gemboss-divider)}
846
+ .gemboss-ei-title{font-size: var(--gemboss-fs-body);font-weight:700;line-height:1.2;min-width:0;overflow:hidden}
847
+ .gemboss-ei-title small{display:block;font-weight:500;font-size:11px;color:var(--gemboss-text-muted);
848
+ font-family:ui-monospace,SFMono-Regular,Menlo,monospace;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
849
+ .gemboss-ei-act,.gemboss-ei-x{margin-left:auto;width:26px;height:26px;flex:none;border:0;background:transparent;cursor:pointer;
850
+ color:var(--gemboss-text-muted);border-radius:6px;display:grid;place-items:center;font-size:14px}
851
+ .gemboss-ei-x{margin-left:2px;font-size:13px}
852
+ .gemboss-ei-act:hover,.gemboss-ei-x:hover{background:var(--gemboss-hover);color:var(--gemboss-text)}
853
+ .gemboss-ei-act:disabled{opacity:.5;cursor:default}
854
+ .gemboss-ei-tabs{display:flex;gap:2px;margin:8px 12px 0;background:var(--gemboss-panel-bg);padding:4px;border-radius:8px}
855
+ .gemboss-ei-tab{flex:1;border:0;background:transparent;cursor:pointer;color:var(--gemboss-text-muted);font-weight:600;
856
+ font-size:12px;padding:6px 4px;border-radius:6px;display:flex;align-items:center;justify-content:center;gap:6px}
857
+ .gemboss-ei-tab.on{background:var(--gemboss-surface);color:var(--gemboss-text);box-shadow:var(--gemboss-shadow-resting)}
858
+ .gemboss-ei-body{flex:1;min-height:0;overflow-y:auto;scrollbar-width:thin;padding:2px 0 4px}
859
+ .gemboss-ei-body::-webkit-scrollbar{width:9px}
860
+ .gemboss-ei-body::-webkit-scrollbar-thumb{background:rgba(31,30,29,.2);border-radius:6px;border:2px solid var(--gemboss-surface);background-clip:content-box}
861
+ .gemboss-ei-pane{padding:4px 16px 6px}
862
+ .gemboss-ei-note{margin:0 0 8px;padding:8px 12px;border-radius:8px;background:var(--gemboss-hover,#eceef2);border:1px solid var(--gemboss-divider,#e5e7ea);color:var(--gemboss-text-muted,#686b71);font-size:12px;line-height:1.45}
863
+ .gemboss-ei-grp{border-bottom:1px solid var(--gemboss-divider-soft);padding:12px 0}
864
+ .gemboss-ei-grp:last-child{border-bottom:none}
865
+ .gemboss-ei-gh{font-size: var(--gemboss-fs-micro);letter-spacing:.1em;text-transform:uppercase;color:var(--gemboss-text-muted);
866
+ font-weight:700;margin-bottom:12px;display:flex;align-items:center;gap:6px;
867
+ width:100%;border:0;background:transparent;cursor:pointer;text-align:left;padding:0;-webkit-appearance:none}
868
+ .gemboss-ei-gh:hover{color:var(--gemboss-text)}
869
+ .gemboss-ei-chev{font-size:9px;width:10px;display:inline-block;color:var(--gemboss-text-muted);transition:transform .12s ease}
870
+ /* collapsed group: hide every sibling after the closed header (its body) — no JSX restructuring. */
871
+ .gemboss-ei-gh[data-open="closed"]{margin-bottom:0}
872
+ .gemboss-ei-gh[data-open="closed"] ~ *{display:none}
873
+ .gemboss-ei-fld{margin-bottom:12px}
874
+ .gemboss-ei-fld:last-child{margin-bottom:0}
875
+ /* Khép đường nối với plugin settings (Polaris), 2026-08-28. Nhãn của editor mờ và nhỏ hơn nhãn
876
+ Polaris một bậc, và nhãn là chỗ mắt bắt ra "hai bề mặt khác nhau" TRƯỚC cả hình dạng control.
877
+ 13px/500, màu chữ đủ tương phản — đúng như Polaris `Label`. Giữ margin-bottom để mật độ
878
+ panel không đổi. */
879
+ .gemboss-ei-fld>label{display:block;font-size:13px;font-weight:500;color:var(--gemboss-text);margin-bottom:6px}
880
+ /* LAYOUT ONLY. The look is `gemboss-field` — the same box as every other field in the admin. This class
881
+ used to carry its own border, radius, padding and a smaller font: a second field family beside the
882
+ Polaris one, which the labels above were already aligned to on 28/08/2026. */
883
+ .gemboss-ei-inp{width:100%}
884
+ .gemboss-ei-mono{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;line-height:1.5}
885
+ /* The inspector's selects keep their drawn caret — a native arrow beside a drawn one is the two-
886
+ families look this change removes. Only the caret and its lane live here. */
887
+ select.gemboss-ei-inp{appearance:none;background-image:linear-gradient(45deg,transparent 50%,var(--gemboss-text-muted) 50%),linear-gradient(135deg,var(--gemboss-text-muted) 50%,transparent 50%);
888
+ background-position:calc(100% - 15px) center,calc(100% - 10px) center;background-size:5px 5px,5px 5px;background-repeat:no-repeat;padding-right:32px}
889
+ /* Segmented: KHÔNG đổi. Tôi định đảo trạng thái chọn cho khớp `ButtonGroup variant="segmented"`
890
+ của Polaris (nhóm trắng có viền, ô chọn tô xám) vì ảnh chụp cho thấy bản này ngược lại. Ảnh đó
891
+ SAI: nó chụp khi chưa nạp gemboss-tokens.css, nên nhóm trông xám. Với token thật,
892
+ `--gemboss-panel-bg` là #ffffff — bản hiện tại đã là nhóm TRẮNG, và ô được chọn nổi lên bằng
893
+ bóng + chữ đậm. Đổi sang "tô xám" làm nền chọn thành trắng-trên-trắng: trạng thái chọn chỉ
894
+ còn khác mỗi độ đậm chữ. Đo trước khi sửa một thứ nhìn bằng mắt. */
895
+ .gemboss-ei-seg{display:flex;gap:2px;background:var(--gemboss-panel-bg);padding:4px;border-radius:8px}
896
+ .gemboss-ei-seg button{flex:1;border:0;background:transparent;color:var(--gemboss-text-muted);font-weight:600;
897
+ font-size: var(--gemboss-fs-micro);padding:6px 4px;border-radius:6px;cursor:pointer;white-space:nowrap}
898
+ .gemboss-ei-seg button.on{background:var(--gemboss-surface);color:var(--gemboss-text);box-shadow:var(--gemboss-shadow-resting)}
899
+ .gemboss-ei-seg button:disabled{cursor:default;opacity:.7}
900
+ .gemboss-ei-swatches{display:flex;gap:8px}
901
+ .gemboss-ei-sw{width:30px;height:30px;border-radius:8px;border:2px solid transparent;cursor:pointer;padding:0;
902
+ box-shadow:inset 0 0 0 1px rgba(31,30,29,.1)}
903
+ .gemboss-ei-sw.on{border-color:var(--gemboss-text);box-shadow:0 0 0 2px var(--gemboss-surface),0 0 0 3px var(--gemboss-primary)}
904
+ .gemboss-ei-sw:disabled{cursor:default;opacity:.7}
905
+ .gemboss-ei-apply{margin-top:2px;background:var(--gemboss-primary);color:#fff;border:0;border-radius:8px;
906
+ font-size: var(--gemboss-fs-caption);font-weight:700;padding:8px 16px;cursor:pointer}
907
+ .gemboss-ei-apply:hover{background:var(--gemboss-primary-dark)}
908
+ .gemboss-ei-apply:disabled{opacity:.6;cursor:default}
909
+ .gemboss-ei-hint{margin:8px 0 0;font-size:11px;color:var(--gemboss-text-muted)}
910
+ .gemboss-ei-empty{padding:24px 16px;text-align:center;font-size: var(--gemboss-fs-caption);color:var(--gemboss-text-muted);
911
+ display:flex;align-items:center;justify-content:center;gap:8px;flex-wrap:wrap}
912
+ .gemboss-ei-foot{flex:0 0 auto;display:flex;align-items:center;gap:8px;padding:8px 16px;border-top:1px solid var(--gemboss-divider);
913
+ font-size:11px;color:var(--gemboss-text-muted)}
914
+ .gemboss-ei-savedot{width:6px;height:6px;border-radius:50%;background:var(--gemboss-success);box-shadow:0 0 0 3px rgba(46,139,87,.18)}
915
+ .gemboss-ei-savedot.busy{background:var(--gemboss-primary);box-shadow:0 0 0 3px var(--gemboss-primary-soft)}
916
+ .gemboss-ei-foot.is-error{color:var(--gemboss-danger)}
917
+ .gemboss-ei-foot.is-error .gemboss-ei-savedot{background:var(--gemboss-danger);box-shadow:0 0 0 3px rgba(215,44,13,.16)}
918
+ /* draggable popup: header is the grab handle; a full-screen mask keeps mousemove firing over the iframe */
919
+ .gemboss-ei-drag{cursor:grab;user-select:none}
920
+ .gemboss-ei-drag:active{cursor:grabbing}
921
+ .gemboss-ei-dragmask{position:fixed;inset:0;z-index:9998;cursor:grabbing}
922
+ /* configurable elements (countdown / trust badges / promo …) inside the Content tab */
923
+ .gemboss-ei-block{border:1px solid var(--gemboss-divider);border-radius:12px;padding:12px 12px;margin-bottom:12px;background:var(--gemboss-panel-bg)}
924
+ .gemboss-ei-block:last-child{margin-bottom:0}
925
+ .gemboss-ei-block-head{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:8px;font-size:12px;font-weight:700;color:var(--gemboss-text)}
926
+ .gemboss-ei-block-x{border:0;background:transparent;cursor:pointer;color:var(--gemboss-text-muted);width:22px;height:22px;border-radius:6px;font-size:11px;flex:none}
927
+ /* Same pair, and here it is unmistakable: this rule sets the hover BACKGROUND and the danger colour on one line. 4.25:1 → 5.66:1. */
928
+ .gemboss-ei-block-x:hover{background:var(--gemboss-hover);color:var(--gemboss-danger-strong)}
929
+ .gemboss-ei-block-x:disabled{opacity:.5;cursor:default}
930
+ .gemboss-ei-block .gemboss-ei-inp{background:var(--gemboss-surface)}
931
+ .gemboss-ei-addrow{display:flex;flex-wrap:wrap;gap:6px;margin-top:2px}
932
+ .gemboss-ei-addchip{border:1px dashed var(--gemboss-divider);background:var(--gemboss-surface);color:var(--gemboss-text-muted);
933
+ border-radius:999px;font-size: var(--gemboss-fs-micro);font-weight:600;padding:6px 12px;cursor:pointer}
934
+ .gemboss-ei-addchip:hover{border-color:var(--gemboss-primary);color:var(--gemboss-active-fg);border-style:solid}
935
+ .gemboss-ei-addchip:disabled{opacity:.5;cursor:default}
936
+ /* image / media picker field */
937
+ .gemboss-ei-img{display:flex;gap:8px;align-items:flex-start}
938
+ .gemboss-ei-img-thumb{flex:none;width:52px;height:52px;border-radius:8px;border:1px solid var(--gemboss-divider);
939
+ background:var(--gemboss-panel-bg) center/cover no-repeat;display:grid;place-items:center;color:var(--gemboss-text-muted);font-size:16px}
940
+ .gemboss-ei-img-ctl{flex:1;min-width:0;display:flex;flex-direction:column;gap:6px}
941
+ .gemboss-ei-img-actions{display:flex;gap:6px}
942
+ .gemboss-ei-img-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:6px;margin-top:8px;padding:8px;
943
+ border:1px solid var(--gemboss-divider);border-radius:12px;background:var(--gemboss-panel-bg);max-height:180px;overflow-y:auto}
944
+ .gemboss-ei-img-cell{aspect-ratio:1;border:1px solid var(--gemboss-divider);border-radius:8px;cursor:pointer;padding:0;
945
+ background:var(--gemboss-surface) center/cover no-repeat}
946
+ .gemboss-ei-img-cell:hover{border-color:var(--gemboss-primary);box-shadow:0 0 0 2px var(--gemboss-primary-soft)}
947
+ .gemboss-ei-img-cell:disabled{opacity:.5;cursor:default}
948
+ .gemboss-ei-img-grid .gemboss-ei-hint{grid-column:1/-1;margin:0}
949
+ .gemboss-ei-gen{display:flex;flex-direction:column;gap:8px;margin-top:8px;padding:8px;border:1px solid var(--gemboss-divider);border-radius:12px;background:var(--gemboss-panel-bg)}
950
+ .gemboss-ei-gen .gemboss-ei-apply{align-self:flex-start;display:inline-flex;align-items:center;gap:6px}
951
+ .gemboss-ei-gen .gemboss-narr-spin{width:11px;height:11px}
952
+ /* honest "not editable here" note (from the capability contract's out_of_scope) */
953
+ .gemboss-ei-oos{margin:4px 16px 12px;padding:12px 12px;border:1px solid var(--gemboss-divider);border-left:3px solid var(--gemboss-text-muted);
954
+ border-radius:8px;background:var(--gemboss-panel-bg)}
955
+ .gemboss-ei-oos-h{font-size: var(--gemboss-fs-micro);letter-spacing:.1em;text-transform:uppercase;color:var(--gemboss-text-muted);font-weight:700;margin-bottom:8px}
956
+ .gemboss-ei-oos p{margin:0 0 6px;font-size:12px;color:var(--gemboss-text-muted);line-height:1.45}
957
+ .gemboss-ei-oos p:last-child{margin-bottom:0}
958
+ .gemboss-ei-oos b{color:var(--gemboss-text);font-weight:600}
959
+ /* Divi-style px spacing box (padding / margin edges) */
960
+ .gemboss-ei-edgebox{display:grid;grid-template-columns:repeat(4,1fr);gap:6px}
961
+ /* Repeater — the element's own list (accordion items, form fields, volume tiers). Each item is a
962
+ bordered card so its sub-fields read as belonging to that row rather than to the panel. */
963
+ .gemboss-ei-rep-item{border:1px solid var(--gemboss-border,#2a2a3a);border-radius:6px;padding:8px;margin-bottom:8px}
964
+ .gemboss-ei-rep-head{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:6px;font-size:12px;opacity:.8}
965
+ .gemboss-ei-rep-head>span:first-child{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
966
+ .gemboss-ei-rep-acts{display:flex;gap:2px;flex:0 0 auto}
967
+ .gemboss-ei-rep-acts button{background:none;border:0;color:inherit;cursor:pointer;padding:2px 6px;border-radius:4px;line-height:1}
968
+ .gemboss-ei-rep-acts button:hover:not(:disabled){background:var(--gemboss-border,#2a2a3a)}
969
+ .gemboss-ei-rep-acts button:disabled{opacity:.3;cursor:default}
970
+ .gemboss-ei-rep-add{width:100%;padding:6px;border:1px dashed var(--gemboss-border,#2a2a3a);border-radius:6px;background:none;color:inherit;cursor:pointer;font-size:12px}
971
+ .gemboss-ei-rep-add:hover:not(:disabled){background:var(--gemboss-border,#2a2a3a)}
972
+ .gemboss-ei-rep-add:disabled{opacity:.4;cursor:default}
973
+ .gemboss-ei-edge{display:flex;flex-direction:column;align-items:center;gap:4px}
974
+ /* The edge boxes keep their MONO, CENTRED digits — that is this control's own idea and the shared
975
+ field has no opinion about it. What they no longer keep is the box: `font-size:12px` at (0,1,1)
976
+ outranked both the field's 13px and its 16px phone rule, so these four were still the size iOS
977
+ zooms for, through the conversion. The tight lane is `gemboss-field--narrow`, which exists for
978
+ exactly this shape. (/code-review, 23/09/2026.) */
979
+ .gemboss-ei-edge input{width:100%;text-align:center;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-variant-numeric:tabular-nums}
980
+ .gemboss-ei-edge span{font-size: var(--gemboss-fs-micro);letter-spacing:.06em;color:var(--gemboss-text-muted);font-weight:700}
981
+ /* EditorChatDock: the Configure pane body inside the sized .gemboss-ec column (mirrors the chat scroll). */
982
+ .gemboss-ec-dock-body{flex:1;min-height:0;overflow-y:auto;padding:12px}
983
+ /* Shared FieldRenderer additions (Configure tab) — grouped rows + colour / suffix / range controls. */
984
+ .gemboss-ei-group{padding:12px 0}
985
+ .gemboss-ei-group + .gemboss-ei-group{border-top:1px solid var(--gemboss-divider)}
986
+ .gemboss-ei-group > .gemboss-ei-fld:last-child{margin-bottom:0}
987
+ .gemboss-ei-grouphead{margin:0 0 12px;font-size:var(--gemboss-fs-micro,11px);font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:var(--gemboss-text-muted)}
988
+ .gemboss-ei-color{display:flex;gap:8px;align-items:center}
989
+ .gemboss-ei-color .gemboss-ei-inp{flex:1;min-width:0}
990
+ .gemboss-ei-swatch{flex:none;width:34px;height:34px;padding:0;border:1px solid var(--gemboss-divider);border-radius:8px;background:var(--gemboss-surface);cursor:pointer}
991
+ .gemboss-ei-swatch::-webkit-color-swatch-wrapper{padding:2px}
992
+ .gemboss-ei-swatch::-webkit-color-swatch{border:none;border-radius:6px}
993
+ .gemboss-ei-swatch:disabled{cursor:default;opacity:.7}
994
+ .gemboss-ei-suffix{margin:4px 0 0;font-size:11px;color:var(--gemboss-text-muted)}
995
+ .gemboss-ei-fld > label > .gemboss-ei-rangeval{float:right;font-weight:700;color:var(--gemboss-text);font-variant-numeric:tabular-nums}
996
+ /* Slider: GIỮ NGUYÊN `accent-color`. Đã thử tự vẽ rãnh cho giống Polaris (phần chưa đi qua nhạt
997
+ hơn) và làm hỏng thứ quan trọng hơn: trên WebKit `accent-color` là thứ DUY NHẤT tô được phần
998
+ ĐÃ đi qua — không có `::-webkit-slider-progress`. Tự vẽ rãnh làm mất vệt tô, tức mất chỉ báo
999
+ giá trị, đổi lấy một khác biệt màu nhỏ. Đường nối này không đáng khép. */
1000
+ .gemboss-ei-range{width:100%;accent-color:var(--gemboss-primary);cursor:pointer}
1001
+ .gemboss-ei-range:disabled{cursor:default;opacity:.7}
1002
+ /* 460, not 400, and NOT `display: none`.
1003
+ 400 was picked by arithmetic and the arithmetic was short: at 401 the wordmark comes back, the
1004
+ action track drops to 240px and `Sign in` is left with 13 of its 44px reachable. Still 32/44 at
1005
+ 420. Measured clean from about 435, so this stops at the 460 this stylesheet already uses rather
1006
+ than at a number invented to sit one pixel past the last failure.
1007
+ The wordmark is CLIPPED, not removed: the mark beside it is `aria-hidden` and the "Preview"
1008
+ note is gone below 720, so `display: none` here would leave the try door's header with no
1009
+ accessible name at all — a bar a screen reader cannot announce. */
1010
+ @media (max-width:760px) {
1011
+ .gemboss-ei{position:static;width:auto;max-width:none;max-height:none;margin:8px;box-shadow:var(--gemboss-shadow-raised)}}
1012
+ /* Elements tab — the section's element tree as an outline (Model B). Indent comes from the row's
1013
+ inline paddingLeft (depth), so the list stays flat in the DOM and every row is one tab stop. */
1014
+ .gemboss-ei-tree{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:1px}
1015
+ .gemboss-ei-tnode{display:flex;align-items:center;gap:4px;flex-wrap:wrap;border-radius:6px;padding-right:4px}
1016
+ .gemboss-ei-tnode:hover{background:var(--gemboss-surface)}
1017
+ .gemboss-ei-tnode.on{background:var(--gemboss-primary-soft,var(--gemboss-surface));box-shadow:inset 2px 0 0 var(--gemboss-primary)}
1018
+ .gemboss-ei-tname{flex:1;min-width:0;display:flex;align-items:baseline;gap:6px;border:0;background:transparent;cursor:pointer;
1019
+ text-align:left;padding:6px 2px;color:var(--gemboss-text);font-size:12px;font-weight:600}
1020
+ .gemboss-ei-tname small{font-weight:400;font-size:10px;color:var(--gemboss-text-muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
1021
+ .gemboss-ei-tacts{flex:none;display:flex;gap:1px;opacity:0;transition:opacity .12s}
1022
+ .gemboss-ei-tnode:hover .gemboss-ei-tacts,.gemboss-ei-tnode.on .gemboss-ei-tacts,.gemboss-ei-tacts:focus-within{opacity:1}
1023
+ .gemboss-ei-tacts button{border:0;background:transparent;cursor:pointer;color:var(--gemboss-text-muted);
1024
+ font-size:11px;line-height:1;padding:4px 6px;border-radius:6px}
1025
+ .gemboss-ei-tacts button:hover:not(:disabled){background:var(--gemboss-surface);color:var(--gemboss-text)}
1026
+ .gemboss-ei-tacts button:disabled{opacity:.3;cursor:default}
1027
+ .gemboss-ei-tadd{flex-basis:100%;padding:4px 2px 6px}
1028
+ /* ── the try door's own bar ────────────────────────────────────────────────────
1029
+ Same frame as .gemboss-editor-topbar (52px, three columns) with a different set
1030
+ of children: a visitor has no store to publish and no draft to undo, so the bar
1031
+ carries identity, one honest label, and the only two actions that mean anything
1032
+ to them. See GuestTopbar.tsx for why it is a separate component. */
1033
+ .gemboss-editor-topbar--guest { grid-template-columns: 1fr auto 1fr; }
1034
+ /* Inline SVG now (GuestTopbar.tsx). The kite that used to live here was a rhombus standing in for
1035
+ a brilliant cut; nothing is left to style but the box it sits in. */
1036
+ .gemboss-guest-mark { flex: 0 0 auto; display: block; }
1037
+ .gemboss-guest-brand { font-weight: 700; letter-spacing: -0.02em; font-size: 15px; margin-left: 8px; }
1038
+ .gemboss-guest-note {
1039
+ font-size: var(--gemboss-fs-caption); color: var(--gemboss-text-muted);
1040
+ border: 1px solid var(--gemboss-divider-soft); border-radius: 999px; padding: 4px 12px; white-space: nowrap;
1041
+ }
1042
+ .gemboss-editor-topbar--guest .gemboss-editor-tb-right { display: flex; align-items: center; gap: 16px; justify-content: flex-end; }
1043
+ .gemboss-guest-signin { font-size: var(--gemboss-fs-body); font-weight: 600; color: var(--gemboss-text-muted); text-decoration: none; }
1044
+ .gemboss-guest-signin:hover { color: var(--gemboss-text); }
1045
+ .gemboss-guest-keep {
1046
+ border: 0; border-radius: 999px; background: #5661f5; color: #fff; cursor: pointer;
1047
+ font: inherit; font-size: var(--gemboss-fs-caption); font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase;
1048
+ padding: 8px 20px; white-space: nowrap;
1049
+ }
1050
+ .gemboss-guest-keep:hover { background: #3a44de; }
1051
+ @media (max-width: 720px) {
1052
+ .gemboss-guest-note { display: none; }
1053
+ }
1054
+ /* THE FRAME IS DECIDED IN TWO PLACES AND THEY HAVE TO AGREE.
1055
+ Above 1000px the shared bar is a three-column grid and the three-track template above matches it.
1056
+ At or below 1000px the shared block reflows the bar to two AREAS ("left right" / "center center")
1057
+ — but that template above is unconditional and later in this file, so it kept forcing THREE tracks
1058
+ onto TWO areas. Measured on the try door's own markup, and it is not a near-miss:
1059
+
1060
+ origin/main, 320px `Sign in` resolved to x = -133..-89 — the whole link OFF the left edge of
1061
+ the screen, invisible and unclickable — and `Create account & keep` to
1062
+ x = -75..143, half of it gone. Same at 375. At 390 the wordmark sits UNDER
1063
+ `Sign in` (elementFromPoint returns .gemboss-guest-signin).
1064
+ with the ≤380 stack the CTA comes back on screen and the wordmark goes under IT instead.
1065
+ removed
1066
+
1067
+ Two different failures, one cause, and the first one is live on a PUBLIC door. Matching the tracks
1068
+ to the areas ends both. The wordmark goes at the same time on the narrowest phones — the mark
1069
+ alone is the identity there, and its ~100px is what buys the CTA room to sit on screen whole
1070
+ instead of in a strip the visitor has to drag. */
1071
+ @media (max-width: 1000px) {
1072
+ .gemboss-editor-topbar--guest { grid-template-columns: auto minmax(0, 1fr); }
1073
+ }
1074
+ @media (max-width: 460px) {
1075
+ .gemboss-guest-brand {
1076
+ position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
1077
+ overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0;
1078
+ }
1079
+ /* The remaining 13px at 320. `justify-content: flex-end` on this cluster puts any overflow on the
1080
+ START edge, where `scrollLeft` is clamped to 0 and no drag can reach it — so an overflowing
1081
+ `Sign in` is not merely tight, it is GONE. Rather than make it scrollable, take the width back:
1082
+ a tighter gap and tighter CTA padding buy 16px, and the cluster then fits without overflowing
1083
+ at all. Nothing about the CTA's own tap target changes — it keeps 9px of block padding. */
1084
+ .gemboss-editor-topbar--guest .gemboss-editor-tb-right { gap: 8px; }
1085
+ .gemboss-editor-topbar--guest .gemboss-guest-keep { padding-inline: 12px; }
1086
+ }
1087
+ /* Those 16px covered 401 and 420 and left 320 six pixels short — `Sign in` reachable 38 of 44. The
1088
+ last of it comes off the BAR, not off the controls: 8px side padding and an 8px column gap, which
1089
+ is what `.gemboss-platform-topbar` already uses below 460 in this same stylesheet. Taking it from
1090
+ the button instead would have shrunk a tap target to buy room for a link. */
1091
+ @media (max-width: 400px) {
1092
+ .gemboss-editor-topbar--guest { padding-inline: 8px; column-gap: 8px; }
1093
+ }
1094
+ /* Editor tự quản chiều cao (`height:100dvh; overflow:hidden`) và nội dung của nó là fixed, nên chỗ
1095
+ chừa ở đây không giúp gì mà chỉ đẻ thêm một vùng cuộn. */
1096
+ .gemboss-platform-main:has(.gemboss-editor) { padding-bottom: 0; }