/* Editor chrome — consumes --pl-* tokens from design-system.css */

/* ─── Top bar ─────────────────────────────────────────────────────────────── */
.ed-top {
  /* min-height keeps the 60px interior while the safe-area inset grows the box
     so top-bar content is never clipped under a notch / status bar. */
  height: auto;
  min-height: 60px;
  background: #fff;
  border-bottom: 1px solid var(--pl-line);
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: env(safe-area-inset-top, 0px) 18px 0;
  gap: 16px;
  z-index: 10;
}

/* SCOPED TO THE TOP BAR ON PURPOSE (2026-08-26 reachability sweep).
   `ed-brand` is ALSO the root class of the Logo/Branding tool panel
   (src/components/Editor/BrandingPanel.tsx). Unscoped, every rule in this
   block leaked onto that panel — including the ≤768px
   `.ed-brand { overflow: hidden }` further down, whose job is to ellipsise
   the project name in a narrow top bar. On a phone that turned the Logo
   panel's root into a 370px box sealing 988px of form: the size slider,
   company name, tagline and colour inputs were unreachable, the three
   "Bottom …" placement buttons sat under the fixed rail — and because the
   overflow was swallowed by the hidden box, `.ed-panel` (the real scroller)
   reported scrollHeight === clientHeight. A scroll audit sees a healthy
   panel; the customer sees a form with no bottom half. */
.ed-top .ed-brand {
  display: flex;
  align-items: center;
  gap: 10px;
  min-width: 230px;
}

.ed-top .ed-brand .mk {
  width: 30px;
  height: 30px;
  border-radius: var(--pl-radius-sm, 9px);
  background: linear-gradient(135deg, var(--pl-clay), var(--pl-gold));
  display: flex;
  align-items: center;
  justify-content: center;
  color: #fff;
  font-family: var(--pl-font-display);
  font-weight: 700;
}

.ed-top .ed-brand .pname {
  font-size: 14px;
  font-weight: 600;
  color: var(--pl-cocoa);
}

.ed-top .ed-brand .pname small {
  display: block;
  font-size: 11px;
  color: var(--pl-muted);
  font-weight: 400;
}

/* ─── T12: guest reminder band — mounted directly under EditorToolbar
   whenever config.isGuest. Quiet by design: a slim cream strip, not a modal
   or a dismissible alert — it's just always there for the duration of a
   guest session. The link's OWN box stays visually inline with the 32px
   band; negative-margin padding grows its actual tap target to the 44px
   touch-target floor without inflating the band. ────────────────────────── */
.ed-guestbar {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  flex-wrap: wrap;
  min-height: 32px;
  padding: 4px 16px;
  background: var(--pl-cream, #FBF5EE);
  border-bottom: 1px solid var(--pl-line);
  font-size: 12.5px;
  color: var(--pl-cocoa);
  text-align: center;
}

.ed-guestbar__link {
  display: inline-block;
  color: var(--pl-clay, #C26A4A);
  font-weight: 700;
  text-decoration: none;
  padding: 14px 6px; /* real hit area ≥44px tall without growing the band */
  margin: -14px -6px;
  min-height: 44px; /* T12 security review: explicit floor, not just padding-derived */
}

.ed-guestbar__link:hover,
.ed-guestbar__link:focus-visible {
  text-decoration: underline;
}

/* ─── Team-journey fix wave (Task 4, 2026-09-22): the resume band — same slot
   and same quiet cream treatment as the guest reminder above, because it is
   the same kind of message (a standing statement of fact about this session,
   not something blocking). It replaced a whole interstitial screen, so it has
   to fit on a phone: the text wraps, and the two controls keep the 44px tap
   floor via the same negative-margin trick .ed-guestbar__link uses rather
   than by growing the band. ─────────────────────────────────────────────── */
.ed-resumebar {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  /* ONE line (the team, 30 Sep 2026: it ran to four on a phone). The
     sentence gives way first (ellipsis, below); Start new and × never do.
     Wrapping would push them onto a second line instead of shortening the
     sentence, so it is allowed only for a failed start-over's error
     (.ed-resumebar--error), the one thing that needs a line of its own. */
  flex-wrap: nowrap;
  min-height: 32px;
  padding: 4px 16px;
  background: var(--pl-cream, #FBF5EE);
  border-bottom: 1px solid var(--pl-line);
  font-size: 12.5px;
  color: var(--pl-cocoa);
  text-align: center;
}

.ed-resumebar__text {
  flex: 0 1 auto;
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.ed-resumebar__sep {
  color: var(--pl-muted);
  flex-shrink: 0;
}

.ed-resumebar__link {
  display: inline-block;
  border: 0;
  background: none;
  color: var(--pl-clay, #C26A4A);
  font-weight: 700;
  font-size: inherit;
  font-family: inherit;
  text-decoration: underline;
  cursor: pointer;
  padding: 14px 6px; /* real hit area ≥44px tall without growing the band */
  margin: -14px -6px;
  min-height: 44px;
  flex-shrink: 0;
  white-space: nowrap;
}

.ed-resumebar__link:hover,
.ed-resumebar__link:focus-visible {
  color: var(--pl-clay-dark, #A8542F);
}

.ed-resumebar__warn {
  color: var(--pl-muted);
}

/* ─── Review round 1 (I2): the failed-carry-on notice, rendered INSIDE the
   Options screens' own column (their `notice` slot) rather than as a band
   above them — those screens are full-stage 100vh panels and a sibling
   stacked above one pushes its CTA off the bottom of a phone. Clay-tinted
   like .ed-lockbar, because this one is reporting a failure rather than
   stating a fact. ──────────────────────────────────────────────────────── */
.ed-resumefail {
  padding: 16px 18px;
  display: flex;
  flex-direction: column;
  gap: 8px;
  text-align: center;
  border: 1px solid var(--pl-clay, #C26A4A);
}

.ed-resumefail__title {
  margin: 0;
  font-weight: 700;
  font-size: 14px;
  color: var(--pl-clay-dark, #A8542F);
}

.ed-resumefail__reason {
  margin: 0;
  font-size: 13px;
  color: var(--pl-cocoa);
}

.ed-resumefail__retry {
  align-self: center;
  min-height: 44px;
}

.ed-resumefail__note {
  margin: 0;
  font-size: 12px;
  line-height: 1.6;
  color: var(--pl-muted);
}

.ed-resumebar--error {
  flex-wrap: wrap;
}

.ed-resumebar__error {
  flex-basis: 100%;
  color: #d32f2f;
  font-weight: 600;
}

.ed-resumebar__dismiss {
  border: 0;
  background: none;
  cursor: pointer;
  color: var(--pl-muted);
  font-size: 16px;
  line-height: 1;
  padding: 14px 10px;
  margin: -14px -4px -14px 0;
  min-height: 44px;
  min-width: 44px;
  flex-shrink: 0;
}

.ed-resumebar__dismiss:hover,
.ed-resumebar__dismiss:focus-visible {
  color: var(--pl-cocoa);
}

/* ─── Audit-fix Task 2: locked-project banner — mounted in the same slot as
   the guest band above (directly under EditorToolbar) whenever the editor
   is not `editable` (an ordered project, or a staff/QA ?pad_readonly=1
   view). Deliberately more assertive than the guest reminder — clay-tinted,
   not cream — since this one is blocking (nothing typed/dragged from here
   down actually saves), not just a nudge. ─────────────────────────────── */
.ed-lockbar {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  flex-wrap: wrap;
  min-height: 32px;
  padding: 6px 16px;
  background: var(--pl-cream-2, #F4E9DC);
  border-bottom: 1px solid var(--pl-clay, #C26A4A);
  font-size: 12.5px;
  font-weight: 600;
  color: var(--pl-clay-dark, #A8542F);
  text-align: center;
}

/* ─── Task 15 (audit-fix wave 4 — save conflicts + session expiry): the
   blocking reload modal. Unlike .ed-lockbar above — a banner that still
   lets the customer look around read-only — this covers the ENTIRE screen
   and can't be dismissed except by the Reload button: a same-session save
   conflict or an expired nonce both mean this client's state can no longer
   be trusted to keep saving safely, and only a fresh page load (fresh
   nonce, fresh server state) fixes that. z-index sits above .ed-preview's
   9999 (the previous highest in this file) so it interrupts even the
   full-screen book preview. ─────────────────────────────────────────── */
.ed-blocking-overlay {
  position: fixed;
  inset: 0;
  z-index: 10000;
  background: rgba(43, 33, 24, 0.72);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
}

.ed-blocking-modal {
  background: #fff;
  border-radius: var(--pl-radius-lg, 18px);
  padding: 28px 24px;
  max-width: 380px;
  width: 100%;
  text-align: center;
  box-shadow: 0 20px 60px rgba(0, 0, 0, .35);
}

.ed-blocking-modal__text {
  font-size: 15px;
  font-weight: 600;
  color: var(--pl-cocoa, #3B2E26);
  line-height: 1.5;
  margin: 0 0 20px;
}

.ed-blocking-modal__btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  padding: 0 28px;
  border: none;
  border-radius: var(--pl-radius-pill, 30px);
  background: var(--pl-clay, #C26A4A);
  color: #fff;
  font-size: 15px;
  font-weight: 700;
  cursor: pointer;
}

.ed-blocking-modal__btn:hover,
.ed-blocking-modal__btn:focus-visible {
  background: var(--pl-clay-dark, #A8542F);
}

/* ─── Step progress ───────────────────────────────────────────────────────── */
.ed-steps {
  display: flex;
  align-items: center;
  gap: 6px;
}

/* The order page's "← Keep editing" is the way back on a PHONE, where the
   stepper is hidden (the mobile block below). On a computer the stepper is
   on screen and "Personalize" in it does the same thing, so the team asked
   for the duplicate to go (23 Sep 2026) — and to stay on phones. This query
   is the exact complement of the one that hides .ed-steps: whenever one of
   them is hidden, the other is shown. Change both together. */
@media (min-width: 769px) and (min-height: 501px) {
  .ed-order-keep-editing { display: none; }
}

.ed-step {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 7px 14px;
  border-radius: var(--pl-radius-pill, 30px);
  font-size: 13px;
  font-weight: 600;
  color: var(--pl-muted);
}

.ed-step-b {
  width: 20px;
  height: 20px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 11px;
  background: #EFE6D9;
  color: var(--pl-muted);
}

.ed-step.done {
  color: var(--pl-sage);
}

.ed-step.done .ed-step-b {
  background: var(--pl-sage);
  color: #fff;
}

.ed-step.active {
  color: #fff;
  background: var(--pl-clay);
}

.ed-step.active .ed-step-b {
  background: #fff;
  color: var(--pl-clay);
}

.ed-step-sep {
  width: 18px;
  height: 2px;
  background: var(--pl-line);
}

/* ─── Icon rail ───────────────────────────────────────────────────────────── */
.ed-rail {
  width: 74px;
  background: #fff;
  border-right: 1px solid var(--pl-line);
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 14px 0;
  gap: 6px;
}

.ed-tool {
  width: 54px;
  height: 54px;
  /* Owner-QA fix (2026-07-29, Finding 4): the editor page also enqueues the
     active theme's stylesheet, and Flatsome's global `button { margin-right:
     1em; … }` bled 10px of phantom margin onto every tab — at 320px the
     six-tab mobile bar overflowed by exactly 6×10px and the last tab sat
     clipped off-screen (the universal `*{margin:0}` reset loses to the
     `button` type selector, so it must be zeroed here at class level). */
  margin: 0;
  border-radius: var(--pl-radius-md, 14px);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 3px;
  font-size: 10px;
  color: var(--pl-muted);
  cursor: pointer;
  font-weight: 600;
}

.ed-tool .i {
  font-size: 19px;
}

.ed-tool:hover {
  background: var(--pl-cream-2);
}

.ed-tool.on {
  background: var(--pl-clay);
  color: #fff;
}

/* ─── Panel ───────────────────────────────────────────────────────────────── */
.ed-panel {
  width: 266px;
  background: #fff;
  border-right: 1px solid var(--pl-line);
  display: flex;
  flex-direction: column;
  min-height: 0;
  /* Owner-bugs wave: the panel is a fixed-height column inside a row that is
     `overflow: hidden`, so a tool panel taller than the window used to be
     CLIPPED — no scrollbar, no wheel response, nothing. On a 1920x1080 desktop
     the Dates panel hid 2,725px of itself: the customer could see five of the
     twenty-three holidays and reach none of the rest.
     `overflow-y: auto` was declared for this element only inside the
     ≤768px bottom-sheet block below, which is why the same panel scrolled
     perfectly on a phone and not at all on the machine the shop owner tested
     on. Declaring it HERE makes the sheet rule a (harmless) repeat rather
     than the only place the panel can scroll. */
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
}

/* P0-2 sheet grab-handle — mobile-only chrome; desktop's side panel has no
   handle at all (see the mobile media block below for the real rules). */
.ed-sheet-grip { display: none; }

.ed-panel .ph {
  padding: 18px 18px 12px;
}

.ed-panel .ph h3 {
  font-family: var(--pl-font-display);
  font-size: 18px;
  font-weight: 600;
}

/* P3 T6: Photos-sheet header (screen 1a) — title + re-runnable Autofill +
   hide-used toggle, replacing the old plain "Your photos" row and the big
   .ed-auto CTA below it (that CTA now lives ONLY on the dedicated
   auto-design step screen). flex-wrap keeps this 320px-safe: at the
   narrowest supported width the actions row drops to its own line instead
   of clipping/overflowing. */
.ed-panel .ph.ed-bin-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 6px 10px;
}

.ed-bin-head__actions {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}

/* 44px-tall tap target (mobile-first convention used throughout this file). */
.ed-bin-head__autofill {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 0 14px;
  border: 1px solid var(--pl-gold, #D8A657);
  border-radius: var(--pl-radius-pill, 30px);
  background: linear-gradient(135deg, #FBE8D7, #F6D6B6);
  color: var(--pl-cocoa, #3B2E26);
  font-size: 13px;
  font-weight: 700;
  white-space: nowrap;
  cursor: pointer;
}

.ed-bin-head__autofill:disabled {
  opacity: .6;
  cursor: wait;
}

/* Label wraps the checkbox so the whole thing (not just the 16px box) is a
   44px-tall tap target. */
.ed-bin-head__hide {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-height: 44px;
  font-size: 12px;
  color: var(--pl-muted, #8A7D70);
  white-space: nowrap;
  cursor: pointer;
}

.ed-bin-head__hide input {
  width: 16px;
  height: 16px;
  cursor: pointer;
}

.ed-prog {
  margin: 0 18px 12px;
  font-size: 12px;
  color: var(--pl-muted);
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.ed-prog .bar {
  height: 6px;
  background: #EFE6D9;
  border-radius: 6px;
  flex: 1;
  margin-left: 10px;
  overflow: hidden;
}

.ed-prog .bar i {
  display: block;
  height: 100%;
  width: 66%;
  background: var(--pl-sage);
  border-radius: 6px;
}

/* ─── Photo thumbnails ────────────────────────────────────────────────────── */
.ed-thumb {
  position: relative;
  border-radius: 10px;
  overflow: hidden;
  aspect-ratio: 1;
  border: 2px solid transparent;
  cursor: grab;
}

.ed-thumb img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.ed-thumb.placed img {
  opacity: .45;
}

.ed-thumb.placed::after {
  content: "✓";
  position: absolute;
  top: 5px;
  right: 5px;
  width: 18px;
  height: 18px;
  background: var(--pl-sage);
  color: #fff;
  border-radius: 50%;
  font-size: 11px;
  display: flex;
  align-items: center;
  justify-content: center;
}

/* ─── Photo bin (curation grid: upload screen + photos sidebar) ───────────── */
.ed-bin {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 8px;
  width: 100%;
}

/* P3 T6 review (Minor fold-in): shown instead of an empty .ed-bin grid when
   "hide used" filters every photo out — distinguishes "you have photos, all
   placed" from the true empty-pool case (no images at all), which renders
   nothing. */
.ed-bin__all-placed {
  margin: 0;
  padding: 14px 8px;
  font-size: 12px;
  text-align: center;
  color: var(--pl-muted, #8A7D70);
}

.ed-bin__tile.placed img {
  opacity: .5;
}

/* P3 T6: per-photo used-COUNT badge (screen 1a) — replaces the old boolean
   "placed" checkmark with how many spread frames use this photo. Sits just
   BELOW the top-left remove button (not on top of it): on touch devices
   `.ed-bin__remove` is always visible (see the `@media (hover: none)` rule
   below), so stacking them at the exact same spot would permanently hide
   the badge for ~95% of traffic (mobile-first constraint). Informational
   only — `pointer-events: none` so it can never swallow a tap meant for the
   tile or the remove button. */
.ed-bin__used {
  position: absolute;
  top: 27px;
  left: 5px;
  min-width: 16px;
  height: 16px;
  padding: 0 3px;
  border-radius: 999px;
  background: var(--pl-sage, #8C9A78);
  color: #fff;
  font-size: 10px;
  font-weight: 700;
  line-height: 16px;
  text-align: center;
  z-index: 1;
  pointer-events: none;
}

/* × remove button — appears on hover / focus / touch */
.ed-bin__remove {
  position: absolute;
  top: 5px;
  left: 5px;
  width: 20px;
  height: 20px;
  border: none;
  border-radius: 50%;
  background: rgba(59, 46, 38, .72);
  color: #fff;
  font-size: 15px;
  line-height: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  opacity: 0;
  transition: opacity .12s ease, background .12s ease;
  z-index: 2;
}

.ed-bin__tile:hover .ed-bin__remove,
.ed-bin__tile:focus-within .ed-bin__remove,
.ed-bin__remove:focus-visible {
  opacity: 1;
}

.ed-bin__remove:hover {
  background: var(--pl-clay, #C26A4A);
}

/* Touch devices have no hover — keep the remove button visible. */
@media (hover: none) {
  .ed-bin__remove {
    opacity: 1;
  }
}

/* "Add more" tile — mirrors the thumbnail aspect, dashed/ghost styling */
.ed-bin__add {
  aspect-ratio: 1;
  border: 2px dashed var(--pl-line, #ECE0D2);
  border-radius: 10px;
  background: var(--pl-cream, #FBF5EE);
  color: var(--pl-muted, #8A7D70);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  cursor: pointer;
  transition: border-color .12s ease, color .12s ease;
}

.ed-bin__add:hover {
  border-color: var(--pl-clay, #C26A4A);
  color: var(--pl-clay, #C26A4A);
}

.ed-bin__add-plus {
  font-size: 22px;
  line-height: 1;
}

.ed-bin__add-label {
  font-size: 11px;
}

/* ─── Stage ───────────────────────────────────────────────────────────────── */

/* The column that holds the canvas stage AND the spread pager underneath it
   (App.tsx wraps <SpreadCanvas> + <PageJumpRow> in this). It is a flex item of
   the `overflow: hidden` main row, so its height is the window's — and BOTH
   `min-height: 0` declarations in this block are what make the pager
   reachable. See .ed-stage below for the failure they prevent. */
.ed-stagecol {
  display: flex;
  flex-direction: column;
  flex: 1;
  min-width: 0;
  min-height: 0;
}

.ed-stage {
  flex: 1;
  background: var(--pl-stage);
  display: flex;
  flex-direction: column;
  min-width: 0;
  /* REACHABILITY, not decoration (2026-08-26, the owner's wall-calendar
     report: "I cannot move on to the next page… I have to press ctrl+minus
     to zoom out completely").

     `.ed-stage` is a flex item in a COLUMN container (.ed-stagecol), and a
     flex item defaults to `min-height: auto` — its content-based minimum.
     Its content is the canvas, whose CSS height fitCanvasToContainer() sets
     from the height of .ed-canvas-wrap, which is .ed-stage's own flex child.
     So without this line the measurement is CIRCULAR: the wrap is as tall as
     the canvas, the canvas is fitted to the wrap, `scale = min(…, 1)` comes
     out 1, and a stage taller than the window is a stable fixed point that
     no resize can ever break out of.

     A 12x18 wall calendar page is 1296px at the editor's 72 DPI. On a
     1920x1080 window .ed-stage measured 1396px tall and the
     "‹ Spread 1 of 12 ›" pager landed at y=1451 — 371px below the fold, in a
     row that is `overflow: hidden`, so it could not be scrolled to either.
     Twelve months, and no way to reach month 2. An 8x8 album spread is 576px
     tall and fits, which is why this shipped unnoticed and why three audits
     that measured scroll ranges all passed: nothing overflowed a scroller —
     the control had been pushed out of the window entirely. */
  min-height: 0;
}

.ed-stbar {
  height: 48px;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  border-bottom: 1px solid rgba(59, 46, 38, .07);
}

.ed-stbar .ed-stbar-btn {
  width: 34px;
  height: 34px;
  border-radius: var(--pl-radius-sm, 9px);
  background: #fff;
  border: 1px solid var(--pl-line);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 14px;
  color: #5a5048;
  cursor: pointer;
}

.ed-stbar .ed-stbar-btn:hover {
  border-color: var(--pl-clay);
  color: var(--pl-clay);
}

.ed-zoom {
  display: flex;
  align-items: center;
  gap: 8px;
  background: #fff;
  border: 1px solid var(--pl-line);
  border-radius: var(--pl-radius-sm, 9px);
  padding: 4px 6px;
  font-size: 13px;
  font-weight: 600;
}

.ed-zoom span {
  cursor: pointer;
  width: 24px;
  text-align: center;
  color: #5a5048;
}

.ed-canvas-wrap {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 26px;
  min-height: 0;
  position: relative;
  /* The backstop behind the fit. fitCanvasToContainer() should always shrink
     the page to this box, but a fit can be momentarily stale (a size change
     mid-render, a container measured at 0 and re-fitted on the next frame) —
     and when it is, the overflow must be something the customer can scroll
     rather than something that silently clips or, worse, pushes the pager
     out. `auto` costs nothing while the page fits: no bar, no range. */
  overflow: auto;
}

/* ─── Spread / page ───────────────────────────────────────────────────────── */
.ed-spread {
  display: flex;
  background: #fff;
  box-shadow: var(--pl-shadow-lg, 0 22px 50px rgba(59,46,38,.22));
  border-radius: 4px;
  overflow: hidden;
  max-height: 100%;
  position: relative;
}

.ed-page {
  width: 330px;
  height: 330px;
  padding: 14px;
  display: flex;
  gap: 8px;
}

/* ─── Pill toolbar (P3 T4, screen 1b) ───────────────────────────────────────
   Contextual toolbar for a selected canvas element. Mobile: fills `.ed-rail`
   in place of the tab buttons (nothing floats over the canvas). Desktop
   (`.ed-pill--floating`): a small positioned pill near the selection —
   replaces the old SelectionToolbar/.ed-floatbar. */
.ed-pill {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: space-around;
  gap: 4px;
  width: 100%;
  flex: 1 1 auto;
  min-width: 0;
}

.ed-pill--floating {
  position: absolute;
  transform: translateX(-50%);
  width: auto;
  justify-content: flex-start;
  background: var(--pl-cocoa);
  border-radius: 12px;
  padding: 5px;
  box-shadow: 0 10px 24px rgba(0, 0, 0, .25);
  z-index: 60;
}

.ed-pill__btn {
  min-width: 48px;
  height: 48px;
  border-radius: var(--pl-radius-sm, 9px);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  font-size: 10px;
  font-weight: 600;
  color: var(--pl-muted);
  cursor: pointer;
  background: none;
  border: none;
  padding: 0 4px;
  flex-shrink: 0;
}

.ed-pill__btn .i { font-size: 17px; line-height: 1; }
.ed-pill__btn .lab { font-size: 10px; line-height: 1; }

.ed-pill__btn:hover { background: var(--pl-cream-2); }

.ed-pill__btn--done {
  background: var(--pl-clay);
  color: #fff;
}
.ed-pill__btn--done:hover { background: var(--pl-clay); }

/* Floating (desktop) mode reuses the dark SelectionToolbar chrome the pill
   replaces — smaller buttons, light-on-dark text. */
.ed-pill--floating .ed-pill__btn {
  width: 40px;
  min-width: 40px;
  height: 40px;
  color: var(--pl-cream-2);
}
.ed-pill--floating .ed-pill__btn:hover { background: rgba(255, 255, 255, .12); }
.ed-pill--floating .ed-pill__btn--done { border-radius: var(--pl-radius-sm, 9px); }

.ed-pill__menu {
  position: absolute;
  bottom: calc(100% + 6px);
  right: 0;
  display: flex;
  flex-direction: column;
  background: #fff;
  border: 1px solid var(--pl-line);
  border-radius: 12px;
  box-shadow: 0 8px 28px rgba(59, 46, 38, .18);
  /* C2 fix: was 61 (below .ed-cta-row's 98) — the mobile bar-mode menu sits
     right above the tab rail, i.e. inside/behind the CTA row's band, and
     got partially painted over by it. 101 clears both .ed-cta-row (98) and
     .ed-rail (100) unconditionally, on top of the mobile-only bottom-offset
     fix below that additionally moves the menu clear of that band. */
  z-index: 101;
  min-width: 160px;
  overflow: hidden;
}

.ed-pill__menu button {
  display: block;
  padding: 0 16px;
  margin: 0;
  min-height: 44px;
  line-height: 44px;
  text-align: left;
  background: none;
  border: none;
  border-bottom: 1px solid var(--pl-line);
  font-size: 14px;
  color: var(--pl-cocoa);
  cursor: pointer;
}
.ed-pill__menu button:last-child { border-bottom: none; }

/* Non-interactive hint caption shown over the canvas bottom edge while an
   element is selected (screen 1b: drag/pinch/tap affordance). Mobile only —
   desktop keeps its existing (non-gesture) editing hints.
   C2 fix: the opaque .ed-cta-row (z-index 98) occupies the band between the
   tab rail and the panel — bottom offset must clear it too (+ --mob-cta-h),
   matching .ed-panel's own stacking formula, or the hint sits directly
   behind the CTA row and never shows. */
.ed-hintline {
  position: fixed;
  left: 0;
  right: 0;
  bottom: calc(var(--mob-rail-h, 58px) + var(--mob-cta-h, 54px) + env(safe-area-inset-bottom, 0px) + 4px);
  z-index: 97;
  text-align: center;
  font-size: 11px;
  color: var(--pl-muted);
  pointer-events: none;
  text-shadow: 0 1px 2px rgba(255, 255, 255, .8);
}

/* ─── Filmstrip ───────────────────────────────────────────────────────────── */
.ed-film {
  width: 118px;
  background: #fff;
  border-left: 1px solid var(--pl-line);
  padding: 14px 12px;
  overflow: auto;
}

.ed-film .lab {
  font-size: 11px;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--pl-muted);
  font-weight: 700;
  margin-bottom: 10px;
  text-align: center;
}

.ed-pg {
  border-radius: 8px;
  overflow: hidden;
  border: 2px solid transparent;
  margin-bottom: 10px;
  cursor: pointer;
  position: relative;
}

.ed-pg img {
  width: 100%;
  height: 62px;
  object-fit: cover;
  display: block;
}

.ed-pg .n {
  position: absolute;
  bottom: 3px;
  right: 4px;
  font-size: 9px;
  background: rgba(0, 0, 0, .5);
  color: #fff;
  padding: 1px 5px;
  border-radius: 8px;
}

.ed-pg.on {
  border-color: var(--pl-clay);
}

.ed-pg-thumb {
  width: 100%;
  height: 62px;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}

.ed-addpg {
  border: 2px dashed var(--pl-line);
  border-radius: 8px;
  height: 50px;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--pl-muted);
  font-size: 20px;
  cursor: pointer;
}

.ed-addpg:hover {
  border-color: var(--pl-clay);
  color: var(--pl-clay);
}

/* ─── Right-zone actions ──────────────────────────────────────────────────── */
.ed-actions { min-width: 230px; justify-content: flex-end; display: flex; align-items: center; gap: 10px; position: relative; }
.ed-top .ed-brand .mk{border:none;cursor:pointer}

/* Mobile-only elements — hidden by default; the ≤768px block below reveals them */
.ed-top__back, .ed-top__ovf, .ed-ovfmenu, .ed-cta-row { display: none; }
/* THE TWO CTA LABELS NEED A DEFAULT, NOT ONLY A MOBILE RULE.
   The long/short pair was introduced with the mobile chrome redesign, but the
   show/hide rules were written only INSIDE the mobile media query — so above
   768px neither applied and both rendered, giving a desktop buy button that
   read "Review & add to cart · ₹499Review · ₹499". Caught by walking a
   purchase at 1920px; every check until then had been at phone widths.
   Desktop is the default here and mobile overrides it. */
.ed-top__cta-long { display: inline; }
.ed-top__cta-short { display: none; }
/* The design step's CTA pair (team, 30 Sep 2026): "Review & add to cart" on
   a desktop, "Preview" on a phone — flipped in the mobile block below. */
.ed-top__cta.ed-top__cta--mob { display: none; }


/* Autosave "Saved"/"Saving…" indicator */
.ed-savedot { font-size: 12.5px; color: var(--pl-muted); background: none; border: none; display: flex; align-items: center; gap: 6px; padding: 0; margin: 0; }
.ed-savedot--ok { color: var(--pl-sage); cursor: pointer; }
/* P1 T8c: visible save-failure state — a retry button in the same slot. */
.ed-savedot--err { color: var(--pl-clay, #C26A4A); cursor: pointer; font-weight: 600; }

/* Undo/redo (P0-6) — desktop shows both side by side; mobile shows only
   undo here (redo lives in the overflow menu, see the ≤768px block below). */
.ed-top__undo, .ed-top__redo { width: 34px; height: 34px; border-radius: 9px; border: 1px solid var(--pl-line); background: #fff; cursor: pointer; padding: 0; margin: 0; font-size: 15px; }
.ed-top__undo:disabled, .ed-top__redo:disabled { opacity: .35; cursor: default; }

/* ─── Coming soon placeholder ─────────────────────────────────────────────── */
.ed-coming-soon {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 32px 18px;
  text-align: center;
  color: var(--pl-muted);
  font-size: 13px;
}

/* ─── Quick-layout picker ─────────────────────────────────────────────────── */
.ed-qlayout-panel {
  display: flex;
  flex-direction: column;
  min-height: 0;
  flex: 1;
}

.ed-qlayout-intro {
  margin: 0 18px 12px;
  font-size: 12px;
  line-height: 1.5;
  color: var(--pl-muted, #8A7D70);
}

.ed-qlayout-scroll {
  flex: 1;
  overflow-y: auto;
  padding: 0 14px 18px;
  min-height: 0;
}

.ed-qlayout-group {
  margin-bottom: 16px;
}

.ed-qlayout-group__head {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--pl-muted, #8A7D70);
  margin: 4px 4px 8px;
}

.ed-qlayout-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 8px;
}

.ed-qlayout {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 4px;
  padding: 6px;
  background: #fff;
  border: 2px solid var(--pl-line, #ECE0D2);
  border-radius: var(--pl-radius-sm, 9px);
  cursor: pointer;
  transition: border-color .12s ease, box-shadow .12s ease;
}

.ed-qlayout:hover {
  border-color: var(--pl-clay, #C26A4A);
}

.ed-qlayout.on {
  border-color: var(--pl-clay, #C26A4A);
  box-shadow: 0 0 0 1px var(--pl-clay, #C26A4A) inset;
}

.ed-qlayout__svg {
  width: 100%;
  aspect-ratio: 2 / 1;
  display: block;
  background: var(--pl-cream-2, #F4E9DC);
  border-radius: 4px;
}

.ed-qlayout__label {
  font-size: 11px;
  font-weight: 600;
  color: var(--pl-muted, #8A7D70);
  text-align: center;
}

.ed-qlayout.on .ed-qlayout__label {
  color: var(--pl-clay, #C26A4A);
}

/* ─── Background colour picker ────────────────────────────────────────────── */
.ed-bg-swatches {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: 8px;
}

.ed-bg-swatch {
  aspect-ratio: 1;
  border-radius: var(--pl-radius-sm, 9px);
  border: 2px solid var(--pl-line, #ECE0D2);
  cursor: pointer;
  padding: 0;
  position: relative;
  transition: transform .1s ease, border-color .12s ease;
}

.ed-bg-swatch:hover {
  transform: scale(1.06);
}

.ed-bg-swatch.on {
  border-color: var(--pl-clay, #C26A4A);
}

.ed-bg-swatch.on::after {
  content: "✓";
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 14px;
  font-weight: 700;
  color: var(--pl-clay, #C26A4A);
  text-shadow: 0 0 3px rgba(255, 255, 255, .9);
}

/* ─── Upload screen (full-stage, shown when step === 'upload') ────────────── */
.ed-upload-screen {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  /* Owner-bugs wave — THE bug the shop owner hit first. This screen grows
     with the photo pool: at thirty photos it is ~1,720px tall, and it lived
     inside an `overflow: hidden` row with no scroller of its own, so both
     ends were simply cut off. Worse, `justify-content: center` splits the
     overflow ACROSS BOTH ENDS, so photos 1-3 were unreachable above the fold
     and 22-30 below it — and the "Continue" button sat at y=1706 on a
     1080px-tall screen (y=1357 on a 430px phone), off-screen and untappable.
     `overflow-y: auto` gives it a scroller; `justify-content: flex-start`
     plus the auto margins on `__inner` below keep the content optically
     centred while there IS spare room and let it start at the top (instead
     of overflowing upward, out of reach) once there is not. */
  justify-content: flex-start;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  background: var(--pl-stage, #E7DCCD);
  /* No bottom padding: Continue is pinned to the bottom edge (see __footer),
     and padding here would leave a strip of photos showing under it. The
     footer carries the bottom breathing room instead. */
  padding: 32px 24px 0;
  min-height: 0;
}

.ed-upload-screen__inner {
  width: 100%;
  max-width: 560px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 24px;
  /* Safe centering (see .ed-upload-screen above): auto margins absorb spare
     room to centre the block, and collapse to 0 when the content is taller
     than the box — so nothing is ever pushed off the top where no scrollbar
     can reach it. */
  margin-top: auto;
  margin-bottom: auto;
}

.ed-upload-screen__headline {
  font-family: var(--pl-font-display, 'Fraunces', Georgia, serif);
  font-size: 30px;
  font-weight: 600;
  color: var(--pl-cocoa, #3B2E26);
  text-align: center;
  margin: 0;
  line-height: 1.2;
}

.ed-upload-screen__sub {
  font-size: 15px;
  color: var(--pl-muted, #8A7D70);
  text-align: center;
  margin: -16px 0 0;
  line-height: 1.5;
}

.ed-upload-screen__zone {
  width: 100%;
  background: #fff;
  border-radius: var(--pl-radius-lg, 18px);
  box-shadow: var(--pl-shadow, 0 12px 30px rgba(59,46,38,.12));
  padding: 8px 8px 16px;
}

.ed-upload-screen__footer {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  width: 100%;
  /* PINNED (owner, 30 Sep 2026: "pin continue to the bottom"). With twelve
     photos in, Continue sat at y=971 on an 844px phone, under the photo
     grid — the only next step on this screen, below the fold. Sticky, not
     fixed: while the content is short it sits where it always did, and it
     only pins once the photos push it past the bottom of the scroller. The
     stage-coloured shadow fades the photos out as they pass under it. */
  position: sticky;
  bottom: 0;
  z-index: 2;
  padding: 14px 0 24px;
  background: var(--pl-stage, #E7DCCD);
  box-shadow: 0 -14px 14px -6px var(--pl-stage, #E7DCCD);
}

.ed-upload-screen__bin {
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.ed-upload-screen__bin-label {
  margin: 0;
  font-size: 13px;
  font-weight: 600;
  color: var(--pl-cocoa, #3B2E26);
}

/* ─── Order review screen (shown when step === 'order') ─────────────────────  */
.ed-order-screen {
  flex: 1;
  display: flex;
  align-items: flex-start;
  justify-content: center;
  background: var(--pl-stage, #E7DCCD);
  padding: 40px 24px 60px;
  overflow-y: auto;
  min-height: 0;
}

.ed-order-inner {
  width: 100%;
  max-width: 680px;
  display: flex;
  flex-direction: column;
  gap: 24px;
}

.ed-order-header {
  text-align: center;
}

.ed-order-headline {
  font-family: var(--pl-font-display, 'Fraunces', Georgia, serif);
  font-size: 28px;
  font-weight: 600;
  color: var(--pl-cocoa, #3B2E26);
  margin: 0 0 8px;
  line-height: 1.2;
}

.ed-order-sub {
  font-size: 15px;
  color: var(--pl-muted, #8A7D70);
  margin: 0;
  line-height: 1.5;
}

/* ── Summary card ── */
.ed-order-card {
  background: #fff;
  border: 1px solid var(--pl-line, #ECE0D2);
  border-radius: var(--pl-radius-lg, 18px);
  box-shadow: var(--pl-shadow-sm, 0 2px 10px rgba(58,51,46,.06));
  padding: 24px;
}

.ed-order-card__title {
  font-size: 12px;
  font-weight: 700;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--pl-muted, #8A7D70);
  margin-bottom: 14px;
}

.ed-order-card__rows {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin-bottom: 20px;
}

.ed-order-card__row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  font-size: 14px;
}

.ed-order-card__label {
  color: var(--pl-muted, #8A7D70);
}

.ed-order-card__value {
  font-weight: 600;
  color: var(--pl-cocoa, #3B2E26);
}

/* ── Price breakdown inside card ── */
.ed-order-price {
  border-top: 1px solid var(--pl-line, #ECE0D2);
  padding-top: 16px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.ed-order-price__row {
  display: flex;
  justify-content: space-between;
  font-size: 13px;
  color: var(--pl-muted, #8A7D70);
}

.ed-order-price__total {
  display: flex;
  justify-content: space-between;
  font-size: 18px;
  font-weight: 700;
  color: var(--pl-cocoa, #3B2E26);
  border-top: 1px solid var(--pl-line, #ECE0D2);
  padding-top: 10px;
  margin-top: 4px;
}

/* P3 T7 (1e, Q5): reassurance that this estimate isn't a live-repricing
   trap — carted/ordered items keep their price_snapshot untouched; this
   panel only ever recomputes a PRE-cart estimate. */
.ed-order-fineprint {
  font-size: 11px;
  color: var(--pl-muted, #8A7D70);
  margin: 10px 0 0;
  text-align: center;
}

/* Product Model Unification Task 4 review fix round 1 (finding 2/5): why
   Add to cart is currently disabled — a visible required option with no
   answer, or a text option now over its size's character limit. */
.ed-order-fineprint--warn {
  color: #d32f2f;
  font-weight: 600;
}

/* ─────────────────────────────────────────────────────────────────────────
   Product Model Unification Task 8A — tier quantity picker + visible ladder
   (components/Editor/TierQuantityPicker.tsx). Rendered ONLY for a
   `tier`-priced product, so none of this affects any product shipping today.
   Mobile-first: ≈95% of this store's orders are placed on a phone, so every
   interactive target here is at least 44px and the band chips WRAP rather
   than scroll off the edge of a narrow screen.
   ───────────────────────────────────────────────────────────────────────── */
.ed-qty {
  margin-top: 18px;
  padding-top: 16px;
  border-top: 1px solid var(--pl-line, #ECE0D2);
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.ed-qty__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
}

.ed-qty__title {
  font-size: 12px;
  font-weight: 700;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--pl-muted, #8A7D70);
}

.ed-qty__min {
  font-size: 12px;
  font-weight: 600;
  color: var(--pl-cocoa, #3B2E26);
  background: var(--pl-sand, #F6EFE6);
  border-radius: 999px;
  padding: 3px 10px;
}

.ed-qty__stepper {
  display: flex;
  align-items: stretch;
  gap: 8px;
}

.ed-qty__step {
  flex: 0 0 auto;
  width: 52px;
  min-height: 48px;
  font-size: 22px;
  line-height: 1;
  font-weight: 700;
  color: var(--pl-cocoa, #3B2E26);
  background: #fff;
  border: 1px solid var(--pl-line, #ECE0D2);
  border-radius: var(--pl-radius-md, 12px);
  cursor: pointer;
  touch-action: manipulation;
}

.ed-qty__step:disabled {
  opacity: .4;
  cursor: default;
}

.ed-qty__input {
  flex: 1 1 auto;
  min-width: 0;
  min-height: 48px;
  text-align: center;
  font-size: 20px;
  font-weight: 700;
  color: var(--pl-cocoa, #3B2E26);
  background: #fff;
  border: 1px solid var(--pl-line, #ECE0D2);
  border-radius: var(--pl-radius-md, 12px);
  padding: 0 8px;
}

.ed-qty__bands-label {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--pl-muted, #8A7D70);
}

.ed-qty__bands {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.ed-qty__band {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  min-height: 48px;
  padding: 6px 12px;
  background: #fff;
  border: 1px solid var(--pl-line, #ECE0D2);
  border-radius: var(--pl-radius-md, 12px);
  cursor: pointer;
  touch-action: manipulation;
}

.ed-qty__band:disabled {
  opacity: .5;
  cursor: default;
}

.ed-qty__band--active {
  border-color: var(--pl-cocoa, #3B2E26);
  background: var(--pl-sand, #F6EFE6);
  box-shadow: inset 0 0 0 1px var(--pl-cocoa, #3B2E26);
}

.ed-qty__band-qty {
  font-size: 14px;
  font-weight: 700;
  color: var(--pl-cocoa, #3B2E26);
}

.ed-qty__band-rate {
  font-size: 11px;
  color: var(--pl-muted, #8A7D70);
  white-space: nowrap;
}

.ed-qty__summary {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding-top: 10px;
  border-top: 1px solid var(--pl-line, #ECE0D2);
}

.ed-qty__summary-row {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  font-size: 13px;
  color: var(--pl-muted, #8A7D70);
}

.ed-qty__summary-row--total {
  font-size: 15px;
  font-weight: 700;
  color: var(--pl-cocoa, #3B2E26);
}

.ed-qty__saving {
  margin: 2px 0 0;
  font-size: 12px;
  font-weight: 600;
  color: #2e7d32;
}

/* "99 calendars cost the same as 100 — Make it 100" (2026-09-30). */
.ed-qty__makeit {
  margin-left: 4px;
  padding: 6px 12px;
  min-height: 32px;
  border: 1.5px solid #2e7d32;
  border-radius: var(--pl-radius-pill, 30px);
  background: #fff;
  color: #2e7d32;
  font: inherit;
  font-weight: 700;
  cursor: pointer;
}

/* ── Page thumbnail grid ── */
.ed-order-pages {
  background: #fff;
  border: 1px solid var(--pl-line, #ECE0D2);
  border-radius: var(--pl-radius-lg, 18px);
  box-shadow: var(--pl-shadow-sm, 0 2px 10px rgba(58,51,46,.06));
  padding: 20px 24px;
}

/* Task 5 (team journey fixes, 2026-09-22) — "Need to skip this area / If need
   to edit / Click". The strip is now a disclosure: the whole row is the
   control, full-bleed inside the card and 44px tall so it is a real touch
   target on the phones ~95% of these orders come from. */
.ed-order-pages__toggle {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  min-height: 44px;
  background: none;
  border: none;
  padding: 0;
  margin: 0;
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.ed-order-pages__label {
  font-size: 12px;
  font-weight: 700;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--pl-muted, #8A7D70);
}

.ed-order-pages__count {
  font-size: 12px;
  font-weight: 700;
  color: var(--pl-cocoa, #3B2E26);
  background: var(--pl-cream-2, #F4E9DC);
  border-radius: 999px;
  padding: 2px 9px;
}

.ed-order-pages__chev {
  margin-left: auto;
  font-size: 14px;
  color: var(--pl-muted, #8A7D70);
}

.ed-order-pages__grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(80px, 1fr));
  gap: 10px;
  margin-top: 14px;
}

/* `display: grid` above outranks the `hidden` attribute's UA `display: none`,
   so a collapsed strip would still draw without this line. */
.ed-order-pages__grid[hidden] {
  display: none;
}

/* P3 T7 (1e): tiles are tappable (onEditSpread) — reset the native <button>
   chrome the markup switched to so it still lays out exactly like the
   plain <div> it replaced. */
.ed-order-pg {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 5px;
  background: none;
  border: none;
  padding: 0;
  margin: 0;
  font: inherit;
  color: inherit;
  cursor: pointer;
  width: 100%;
}

.ed-order-pg__thumb {
  width: 80px;
  height: 80px;
  border-radius: 8px;
  border: 1px solid var(--pl-line, #ECE0D2);
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}

/* P0-3: real spread renders are 2:1 (two facing pages), not square — sized by
   width so the grid column drives it, not the fixed square .ed-order-pg__thumb. */
.ed-order-pg__thumb--real {
  width: auto;
  aspect-ratio: 2 / 1;
  height: auto;
  position: relative;
  overflow: hidden;
  background: var(--pl-cream-2, #F4E9DC);
}

.ed-order-pg__thumb--real img {
  width: 100%;
  height: 100%;
  /* The whole sheet, never a crop of it (2026-09-30: a 9x6 month lost its
     top and bottom to a 2:1 box). The box takes the sheet's shape now
     (thumbAspect); contain covers any render that still differs. */
  object-fit: contain;
  display: block;
}

.ed-order-pg__loading {
  font-size: 12px;
  color: var(--pl-muted, #8A7D70);
}

.ed-order-pg__warn {
  position: absolute;
  left: 4px;
  bottom: 4px;
  background: rgba(176,48,48,.92);
  color: #fff;
  font-size: 10px;
  font-weight: 700;
  padding: 2px 6px;
  border-radius: 6px;
}

/* Customer-critical wave (C3): a low-resolution photo on this surface —
   amber, not red: it is a warning the customer may choose to accept, never
   a gate like the empty-frame one above. */
.ed-order-pg__warn--soft {
  background: rgba(184, 120, 20, .94);
}
.ed-order-softnote {
  margin: 0 0 10px;
  padding: 10px 12px;
  border-radius: 10px;
  background: rgba(184, 120, 20, .10);
  color: #7a4f0a;
  font-size: 13px;
  line-height: 1.45;
}

/* Customer-critical wave (C3): the photo-tile badge for a small copy. */
.ed-bin__soft {
  position: absolute;
  left: 4px;
  right: 4px;
  bottom: 4px;
  background: rgba(184, 120, 20, .94);
  color: #fff;
  font-size: 10px;
  font-weight: 700;
  line-height: 1.2;
  padding: 2px 5px;
  border-radius: 6px;
  text-align: center;
  pointer-events: none;
  z-index: 2;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.ed-bin--strip .ed-bin__soft { font-size: 9px; padding: 1px 3px; }
.ed-upload-screen__soft {
  margin: 0 0 10px;
  padding: 10px 12px;
  border-radius: 10px;
  background: rgba(184, 120, 20, .10);
  color: #7a4f0a;
  font-size: 13px;
  line-height: 1.45;
}

/* Customer-critical wave (finding 5): the in-app "remove this photo?"
   confirm and the "Photo removed · Undo" toast. Both sit above every bar
   the editor pins to the bottom (rail 100 / pager 99 / sheet 99 / spread
   grid 200) — a confirm the customer cannot reach is worse than none. */
.ed-confirm {
  position: fixed;
  inset: 0;
  z-index: 310;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  padding: 16px;
  background: rgba(59, 46, 38, .38);
}
.ed-confirm__card {
  width: 100%;
  max-width: 420px;
  background: #fff;
  border-radius: 16px;
  padding: 18px 18px 14px;
  box-shadow: 0 14px 40px rgba(59, 46, 38, .28);
  margin-bottom: env(safe-area-inset-bottom, 0px);
}
.ed-confirm__title {
  margin: 0 0 6px;
  font-size: 17px;
  font-weight: 700;
  color: var(--pl-cocoa, #3B2E26);
}
.ed-confirm__body {
  margin: 0 0 14px;
  font-size: 14px;
  line-height: 1.45;
  color: var(--pl-muted, #6d5f53);
}
.ed-confirm__actions {
  display: flex;
  gap: 10px;
  justify-content: flex-end;
}
.ed-confirm__actions .pl-btn { min-height: 44px; }
.ed-confirm__danger { background: #b03030 !important; border-color: #b03030 !important; color: #fff !important; }
.ed-undo-toast {
  position: fixed;
  left: 50%;
  transform: translateX(-50%);
  bottom: 24px;
  z-index: 305;
  display: flex;
  align-items: center;
  gap: 14px;
  min-height: 48px;
  padding: 6px 8px 6px 16px;
  border-radius: 12px;
  background: var(--pl-cocoa, #3B2E26);
  color: #fff;
  font-size: 14px;
  box-shadow: 0 10px 30px rgba(59, 46, 38, .32);
  max-width: calc(100vw - 24px);
}
.ed-undo-toast button {
  min-height: 40px;
  min-width: 64px;
  padding: 0 14px;
  border: none;
  border-radius: 9px;
  background: rgba(255, 255, 255, .14);
  color: #ffd9c4;
  font-weight: 700;
  font-size: 14px;
  letter-spacing: .02em;
  text-transform: uppercase;
  cursor: pointer;
}
/* (the phone-shell offset for .ed-undo-toast lives inside the main mobile
   media block below, beside .ed-pagejump — editorScroll.test.ts reads that
   block as THE phone shell and must find it first in the file.) */

.ed-order-pg__label {
  font-size: 10px;
  font-weight: 600;
  color: var(--pl-muted, #8A7D70);
  text-align: center;
}

/* ── Action row ── */
.ed-order-actions {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
}

.ed-order-actions .pl-btn {
  min-width: 260px;
  justify-content: center;
  text-align: center;
}

/* Task 5 — the Preview button the gate puts next to its own refusal. It is
   a plain ghost button (the column above already sizes and centres it); what
   this rule adds is emphasis, because it is the ONLY thing on the screen
   that clears the block, and it must not read as quieter than "Keep
   editing" sitting right below it. */
.ed-order-preview-cta {
  border-color: var(--pl-clay, #C26A4A);
  color: var(--pl-clay, #C26A4A);
  font-weight: 700;
}

/* ─── DragDrop zone warm restyle ────────────────────────────────────────────  */
.ed-dz {
  border: 2px dashed var(--pl-line, #ECE0D2);
  border-radius: 14px;
  padding: 28px 20px 22px;
  text-align: center;
  cursor: pointer;
  background: var(--pl-cream, #FBF5EE);
  transition: border-color 0.2s, background 0.2s;
  user-select: none;
}

.ed-dz:hover {
  border-color: var(--pl-clay, #C26A4A);
  background: var(--pl-cream-2, #F4E9DC);
}

.ed-dz--dragging {
  border-color: var(--pl-clay, #C26A4A);
  background: var(--pl-cream-2, #F4E9DC);
}

.ed-dz__icon {
  font-size: 36px;
  margin: 0 0 10px;
  line-height: 1;
}

.ed-dz__title {
  font-size: 15px;
  font-weight: 700;
  color: var(--pl-cocoa, #3B2E26);
  margin: 0 0 4px;
}

.ed-dz__hint {
  font-size: 13px;
  color: var(--pl-muted, #8A7D70);
  margin: 0;
}

.ed-dz__progress-bar {
  width: 100%;
  height: 6px;
  background: var(--pl-line, #ECE0D2);
  border-radius: 3px;
  overflow: hidden;
  margin-bottom: 10px;
}

.ed-dz__progress-fill {
  height: 100%;
  background: var(--pl-clay, #C26A4A);
  border-radius: 3px;
  transition: width 0.3s;
}

.ed-dz__progress-label {
  font-size: 12px;
  color: var(--pl-muted, #8A7D70);
  margin: 0;
}

/* Other sources row (Google Photos, when configured) */
.ed-dz__sources {
  display: flex;
  gap: 8px;
  padding: 12px 8px 0;
  justify-content: center;
  flex-wrap: wrap;
}

/* "Upload from phone" in My photos (2026-09-30): the picker was a dead end
   with no photos in it. */
.ed-dz-btnwrap { margin: 4px 8px 10px; }
.ed-dz-btn { width: 100%; min-height: 44px; }

/* Replace's picker, headed "My photos" (the team, 30 Sep 2026). */
.ed-replace-head {
  margin: 8px 8px 2px;
  font-family: var(--pl-font-display, 'Fraunces', Georgia, serif);
  font-size: 16px;
  font-weight: 600;
  color: var(--pl-cocoa, #3B2E26);
}

/* Google Photos off → nothing to show; don't keep its padding. */
.ed-dz__sources:empty {
  display: none;
}

.ed-dz__src-btn {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 8px 14px;
  border: 1.5px solid var(--pl-line, #ECE0D2);
  border-radius: 24px;
  background: #fff;
  font-size: 12px;
  font-weight: 600;
  color: var(--pl-cocoa, #3B2E26);
  cursor: pointer;
  transition: border-color 0.15s, background 0.15s, color 0.15s;
}

.ed-dz__src-btn:hover:not(:disabled) {
  border-color: var(--pl-clay, #C26A4A);
  color: var(--pl-clay, #C26A4A);
  background: var(--pl-cream, #FBF5EE);
}

.ed-dz__src-btn:disabled {
  opacity: 0.52;
  cursor: not-allowed;
}

.ed-dz__count {
  font-size: 11px;
  color: var(--pl-muted, #8A7D70);
  text-align: center;
  margin: 10px 0 0;
}

/* Upload error toasts (P0-7): rejected files / failed uploads with retry */
.ed-dz__toasts {
  margin-bottom: 10px;
}

.ed-dz__toast {
  display: flex;
  align-items: center;
  gap: 8px;
  background: #FDF0F0;
  border: 1px solid #E0B4B4;
  color: #B03030;
  border-radius: 10px;
  padding: 8px 10px;
  font-size: 12.5px;
  margin-bottom: 8px;
}

.ed-dz__toast-msg {
  flex: 1 1 auto;
  text-align: left;
}

.ed-dz__toast-btn {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1px solid #E0B4B4;
  border-radius: 8px;
  background: #fff;
  color: #B03030;
  font-size: 12.5px;
  font-weight: 700;
  cursor: pointer;
  padding: 0 10px;
}

.ed-dz__toast-dismiss {
  padding: 0;
  width: 44px;
}

/* ═══════════════════════════════════════════════════════════════════════════
   RESPONSIVE / MOBILE  (≤ 768px)
   ─────────────────────────────────────────────────────────────────────────
   Layout goals:
   • Icon rail  → fixed horizontal bottom tab bar
   • Context panel → bottom sheet that slides up above the tab bar
   • Top bar → condensed: subtitle hidden; step labels hidden, circles only
   • Stage / canvas-wrap → padded-bottom so it's never hidden behind bars
   • Screens (upload / order) → single-column, full-width buttons
   • Filmstrip (.ed-film) is not rendered by App, so no extra rule needed there.
   NOTE: pointer-events on the canvas are NOT blocked — the bottom bars
   sit BELOW the canvas area via padding, not as an overlay on top of it.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── Constants used in multiple rules ── */
:root {
  --mob-rail-h: 58px;       /* height of the bottom tab bar          */
  --mob-cta-h: 54px;        /* height of the fixed mobile CTA row    */
  --mob-panel-max-h: 52vh;  /* max-height of the bottom-sheet panel  */
  /* Height of the pinned spread pager band. The .ed-pagejump row is 40px and
     its circular Prev/Next buttons are 44px, so 48 leaves the 44px tap
     targets a 2px breathing edge top and bottom. Every mobile bottom offset
     that must sit ABOVE the pager derives from this one number. */
  --mob-pager-h: 48px;      /* height of the pinned spread pager      */
  /* The tool sheet's DEFAULT detent — the state a customer arrives in. It is
     `position: fixed` and overlays the stage, so the canvas has to be fitted
     above it or the bottom of the design is simply hidden. See .ed-stagecol. */
  --mob-sheet-peek-h: 96px;
}

/* Customer-critical wave (2026-09-03, the review's finding 7): the second
   clause is a LANDSCAPE PHONE — 930×430 is wider than 768 but cannot hold
   the desktop chrome (a 280px step bar, the rail, a canvas strip with no
   photo drawn, the cart button off-screen at x=1051). Must agree with
   isMobileViewport() in services/detentSheet.ts (MOBILE_MAX_HEIGHT_PX). */
@media (max-width: 768px), (max-height: 500px) {

  /* ── Top bar: single-line, nothing wraps (P0-1 two-bar shell) ── */
  .ed-top {
    min-height: 56px;
    padding: env(safe-area-inset-top, 0px) 8px 0;
    gap: 8px;
    flex-wrap: nowrap;            /* structurally: nothing can wrap */
  }
  .ed-steps { display: none; }    /* stepper is desktop-only (P0-1); .ed-order-keep-editing uses this query's complement */
  .ed-top .ed-brand { min-width: 0; flex: 1 1 auto; overflow: hidden; }
  .ed-top .ed-brand .mk { display: none; }
  .ed-top .ed-brand .pname,
  .ed-top .ed-brand .pname small {
    display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }
  .ed-top__back, .ed-top__ovf {
    display: flex; align-items: center; justify-content: center;
    width: 44px; height: 44px; flex-shrink: 0; margin: 0; padding: 0;
    border: none; background: none; font-size: 22px; color: var(--pl-cocoa);
    border-radius: 10px; cursor: pointer;
  }
  .ed-top__undo { width: 44px; height: 44px; flex-shrink: 0; }
  .ed-top__redo { display: none; }   /* mobile redo lives in the overflow menu */
  /* MF3: grow the save-dot's hit area to a real 44px tap target without
     changing its visual size (font-size/dot glyph untouched). */
  .ed-savedot { min-height: 44px; display: inline-flex; align-items: center; }
  .ed-top__preview { display: none; }
  /* ─── THE CHROME BUDGET, 2026-09-09 ────────────────────────────────────
     Four fixed bars stacked at the bottom of a phone came to 248px — 44% of
     a 320x568 screen — and left the design 33%. The pager moved into the
     sheet's grip and the price row moved into the top bar WHILE DESIGNING;
     both `--mob-*-h` variables go to 0 in those states so every calc() in
     this file that measures the bar stack (sheet offset, stage padding,
     undo toast, overflow menu) collapses with them, consistently, from one
     place. See App.tsx's pagerInSheet / ctaInTopBar. */
  :root.pad-mob-pager-in-sheet { --mob-pager-h: 0px; }
  :root.pad-mob-designing { --mob-cta-h: 0px; }
  /* No tab bar outside the design step (team, 30 Sep 2026 — App.tsx's
     mobileTools), so nothing stacks on top of one. */
  :root.pad-mob-tools-off { --mob-rail-h: 0px; }

  /* ── The phone's calendar editor (the team, 7 Oct 2026) ──────────────────
     "Square Calendar edit area must be constant with center position" and
     "Options edit area must be constant with bottom position". From the
     bottom up: the tool bar (Replace · Rotate · Filters · Adjust), a FIXED
     tool area for the open tool's controls, the month pager, and the month
     centred in what is left. Nothing moves when a tool opens or closes — the
     stage reserves the tool area always (App.tsx `--mob-sheet-h`), so the
     canvas is fitted once. The stage's ⤢ "fit" bar goes too: on this layout
     the month is always fitted, and it cost 48px of the month. */
  :root { --mob-caltool-h: 150px; }
  :root.pad-mob-caltools .ed-stbar { display: none; }
  :root.pad-mob-caltools .ed-canvas-wrap { align-items: center; }
  :root.pad-mob-caltools .ed-pagejump {
    bottom: calc(var(--mob-rail-h) + var(--mob-cta-h) + var(--mob-caltool-h) + env(safe-area-inset-bottom, 0px));
  }
  /* A tool from the ⋯ menu keeps the ordinary sheet, standing on the pager. */
  :root.pad-mob-caltools .ed-panel {
    bottom: calc(var(--mob-rail-h) + var(--mob-cta-h) + var(--mob-caltool-h) + var(--mob-pager-h) + env(safe-area-inset-bottom, 0px));
  }
  :root.pad-mob-caltools .ed-panel.ed-panel--tool {
    bottom: calc(var(--mob-rail-h) + var(--mob-cta-h) + env(safe-area-inset-bottom, 0px));
    height: var(--mob-caltool-h);
    max-height: var(--mob-caltool-h);
    border-radius: 0;
    box-shadow: none;
    transition: none;
    overflow-y: auto;
    background: #fff;
  }
  .ed-panel--tool .ed-editpanel--only { padding: 10px 16px 8px; }
  .ed-panel--tool .ed-editpanel--only .ed-editpanel__label { margin: 6px 0 2px; }
  .ed-panel--tool .ed-editpanel--adjust .ed-editpanel__label { margin: 0; font-size: 10px; }
  .ed-panel--tool .ed-editpanel--adjust .ed-editpanel__slider { margin: 0 0 4px; }
  .ed-panel--tool .ed-editpanel--filters { padding-top: 22px; }
  .ed-caltool-replace { padding: 14px 12px 8px; text-align: center; }
  .ed-caltool-replace__hint { margin: 0 0 10px; font-size: 13px; color: var(--pl-cocoa, #3B2E26); }
  .ed-caltool-replace .ed-dz-btnwrap { margin: 0 8px 8px; }
  .ed-caltool-replace .ed-bin--strip { padding: 0 4px 4px; }
  .ed-caltool-replace .ed-bin--strip .ed-bin__tile { width: 48px; height: 48px; }
  .ed-cta-row.ed-cta-row--design { display: none; }

  /* The buy button while designing. Compact so it survives a 320px bar
     next to the title, undo and the overflow dots. */
  .ed-top__cta {
    display: inline-flex; align-items: center;
    min-height: 36px; padding: 0 12px;
    font-size: 12px; font-weight: 700; white-space: nowrap; flex-shrink: 0;
  }
  .ed-top__cta-long { display: none; }
  .ed-top__cta-short { display: inline; }
  .ed-top__cta.ed-top__cta--desk { display: none; }
  .ed-top__cta.ed-top__cta--mob { display: inline-flex; }

  /* THE SMALLEST PHONE STILL HAS TO FIT. At 320px the top bar now carries
     back, title, undo, the save state, the buy button AND the overflow dots,
     and the dots were being clipped off the right edge. The save state is
     the one item that can lose its word without losing its meaning — the
     coloured dot alone still says "saved" — so below 360px it keeps the dot
     and drops the label. The FAILED state keeps its full wording: a dot that
     silently means "your work did not save" is exactly the wrong thing to
     shorten. */
  @media (max-width: 360px) {
    /* The "Saved" badge goes entirely on the smallest phones. Six things
       were competing for a 320px bar — back, title, undo, save state, the
       buy button, the overflow dots — and the casualty was the project
       title, clipped to a single letter. Of the six, the SUCCESS badge is
       the only one that is pure reassurance rather than a control, and
       saving is automatic. The FAILED and IN-PROGRESS states stay: a
       silently unsaved calendar is the one thing here worth the width.
       (Clipping it instead of hiding it was tried first and rendered as
       "SAV" wrapped under the dot.) */
    .ed-savedot--ok { display: none; }
    .ed-top .ed-brand { min-width: 0; }
    .ed-top .ed-brand .pname {
      min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
    }
    .ed-top .ed-brand .pname small { display: none; }
    .ed-top__cta { padding: 0 9px; }
  }

  /* The sheet's grip row now carries the pager. It was 48px of drag bar and
     a close button; the pager fits in it exactly, and because the grip is
     sticky the pager stays put while the panel scrolls — which the pinned
     band never did. */
  .ed-sheet-grip { justify-content: space-between; padding: 0 2px; }
  .ed-sheet-grip__bar { position: absolute; top: 5px; left: 50%; transform: translateX(-50%); }
  .ed-sheet-grip__pager { flex: 1 1 auto; min-width: 0; padding-top: 6px; }
  .ed-sheet-grip__pager .ed-pagejump {
    position: static; height: auto; border-top: 0; background: transparent; z-index: auto;
  }
  .ed-sheet-grip__close { position: static; flex-shrink: 0; width: 40px; height: 44px; }

  /* Sticky under the sheet's 48px grip, so the row you switch tools with
     never scrolls away from the controls it is switching. */
  .ed-editpanel__tabs {
    position: sticky; top: 48px; z-index: 2;
    padding: 6px 8px 8px; margin: 0 -2px 4px;
    background: #fff;
  }
  .ed-actions { min-width: 0; flex-shrink: 0; gap: 2px; }
  .ed-ovfmenu {
    display: flex; flex-direction: column; position: absolute; top: 48px; right: 0;
    background: #fff; border: 1px solid var(--pl-line); border-radius: 12px;
    box-shadow: 0 8px 28px rgba(59,46,38,.18); z-index: 120; min-width: 180px; overflow: hidden;
  }
  .ed-ovfmenu > * {
    display: block; padding: 0 16px; margin: 0; min-height: 44px; line-height: 44px; text-align: left;
    background: none; border: none; border-bottom: 1px solid var(--pl-line);
    font-size: 14px; color: var(--pl-cocoa); text-decoration: none; cursor: pointer;
  }
  .ed-ovfmenu > *:last-child { border-bottom: none; }
  .ed-ovfmenu > button:disabled { opacity: .4; cursor: default; }
  /* A calendar's tools, moved here from the bottom tabs (the team, 30 Sep
     2026): a grid of the same icons at the top of the menu, three a row, so
     seven tools cost three rows instead of seven. */
  .ed-ovfmenu.ed-ovfmenu--tools { min-width: 264px; }
  .ed-ovfmenu > .ed-ovfmenu__tools {
    display: grid; grid-template-columns: repeat(3, 1fr); gap: 2px;
    padding: 6px; line-height: normal; min-height: 0; cursor: default;
  }
  .ed-ovfmenu__tool {
    display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px;
    min-height: 64px; padding: 8px 4px; border: 0; border-radius: 10px; background: none;
    font-size: 11px; font-weight: 600; line-height: 1.2; text-align: center; color: var(--pl-cocoa); cursor: pointer;
  }
  .ed-ovfmenu__tool:active, .ed-ovfmenu__tool:hover { background: var(--pl-cream, #FBF5EE); }
  .ed-ovfmenu__tool-i { font-size: 20px; line-height: 1; }
  .ed-cta-row {
    display: flex; align-items: center; justify-content: space-between; gap: 10px;
    position: fixed; left: 0; right: 0;
    bottom: calc(var(--mob-rail-h) + env(safe-area-inset-bottom, 0px));
    height: var(--mob-cta-h); padding: 0 12px;
    background: #fff; border-top: 1px solid var(--pl-line); z-index: 98;
  }
  .ed-cta-row__price { font-size: 13px; font-weight: 700; color: var(--pl-cocoa); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  /* Owner-bugs wave, rebuilt for review round 1 (I2): when the sale is
     blocked this slot carries the price AND the reason, stacked, instead of
     handing the whole slot to the reason. Task 5's preview gate made a
     blocked sale the DEFAULT state of the order step, so the old behaviour
     hid the price for the whole of it.

     MEASURED at 360px (node-canvas, DM Sans metrics), because the first cut
     of this comment guessed and guessed wrong — the button is far wider than
     it looks:

       row content            336px  (360 - 2x12 padding)
       .ed-cta-row__btn       146.6  (15px/600 "Add to cart" + 26px padding
                                      each side — it is a full .pl-btn)
       gap                     10
       → this slot            179.4

       price, 13px/700        141.7  ("₹1,200 · 12 months")       one line
       reason, 11px/600       164.7  ("⚠ Preview: 0 of 12 months") one line
                              172.4  worst case ("11 of 12", 100 surfaces)

     Vertically: 16px of price + 1px gap + up to 26px of reason = 43px inside
     the 54px bar. A longer FALLBACK reason (an unanswered option, a blank
     sheet — neither has a short form) clamps at two lines rather than
     pushing the button off the row. `min-width: 0` is what lets either line
     ellipsise inside the flex row at all. */
  .ed-cta-row__stack {
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: 1px;
    min-width: 0;
    flex: 1 1 auto;
    overflow: hidden;
  }
  .ed-cta-row__why {
    font-size: 11px;
    line-height: 1.2;
    font-weight: 600;
    color: var(--pl-clay, #C26A4A);
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    max-height: 27px;
  }
  .ed-cta-row__btn { min-height: 44px; line-height: normal; white-space: nowrap; flex-shrink: 0; }

  /* C2 fix: mobile-only — in bar mode .ed-pill fills .ed-rail, so the "More"
     menu's `bottom: calc(100% + 6px)` (base rule) pops it up right into the
     .ed-cta-row band above the rail, where the row's opaque background
     partially paints over it. Add --mob-cta-h so the menu clears the CTA
     row entirely, same as .ed-panel's bottom formula below. Scoped to
     mobile only — the desktop floating pill has no CTA row to clear. */
  .ed-pill__menu {
    bottom: calc(100% + var(--mob-cta-h) + 6px);
  }

  /* ── Icon rail → fixed horizontal bottom tab bar ── */
  .ed-rail {
    /* Override the desktop left column */
    position: fixed;
    bottom: 0;
    left: 0;
    right: 0;
    z-index: 100;
    /* height = tab-bar height + home-bar safe area so the tab row never sits
       under the iOS home indicator / Android gesture bar. */
    height: calc(var(--mob-rail-h) + env(safe-area-inset-bottom, 0px));

    /* Horizontal layout — full-width, large tap targets, centered */
    flex-direction: row;
    align-items: center;
    justify-content: space-around;
    width: auto;
    border-right: none;
    border-top: 1px solid var(--pl-line);
    /* reserve the home-bar inset as extra bottom padding */
    padding: 4px 6px max(4px, env(safe-area-inset-bottom, 0px));
    gap: 2px;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;

    /* Prevent the tab bar from covering anything interactable */
    background: #fff;
  }

  /* Tab tiles: full tap targets (≥44px square) */
  .ed-tool {
    min-width: 48px;
    width: auto;
    flex: 1 1 0;
    max-width: 84px;
    height: 48px;
    border-radius: var(--pl-radius-sm, 9px);
    flex-shrink: 0;
  }
  /* The calendar bar's three (PhotoToolsBar): 84px is a seventh of a phone,
     and "ADD PHOTO" wrapped onto a second line under the bar (2026-09-30). */
  .ed-tool.ed-tool--wide {
    max-width: 140px;
    white-space: nowrap;
  }

  /* ── Context panel → bottom sheet above the rail ── */
  .ed-panel {
    /* Take it out of the flex row and fix it above the tab bar.
       Bottom offset = rail height + home-bar inset so the sheet rests directly
       on top of the (inset-aware) tab bar. */
    position: fixed;
    /* + --mob-pager-h: the sheet rests on top of the PINNED SPREAD PAGER,
       not on top of the CTA row. Without that term the sheet's bottom edge
       and the pager occupied the same 48px band, so at the DEFAULT `peek`
       detent — a 96px photo strip, the state a customer arrives in — all
       three pager buttons hit-tested to the strip. The one control that
       moves between the twelve months was invisible and untappable in the
       editor's resting state. */
    bottom: calc(var(--mob-rail-h) + var(--mob-cta-h) + var(--mob-pager-h) + env(safe-area-inset-bottom, 0px));
    left: 0;
    right: 0;
    z-index: 99;

    width: auto;
    max-height: var(--mob-panel-max-h);
    border-right: none;
    border-top: 1px solid var(--pl-line);
    border-radius: 14px 14px 0 0;
    box-shadow: 0 -6px 24px rgba(59, 46, 38, 0.12);
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    /* the real <div class="ed-sheet-grip"> handle replaces the old reserved
       handle space (P0-2: needs real touch handlers, not a CSS ::before) */
    padding-top: 0;
  }

  /* Grab handle at the top of the bottom-sheet panel — swipe down dismisses,
     swipe up expands the Photos peek strip; the ⌄ button is the explicit
     tap-target doing the same dismiss without a gesture (P0-2). */
  .ed-sheet-grip {
    display: flex; align-items: center; justify-content: center; position: sticky;
    top: 0; z-index: 1; background: #fff;
    min-height: 48px; touch-action: none; flex-shrink: 0;
  }
  .ed-sheet-grip__bar { width: 40px; height: 4px; border-radius: 3px; background: var(--pl-line); }
  .ed-sheet-grip__close {
    position: absolute; right: 4px; top: 0; width: 44px; height: 44px;
    border: none; background: none; font-size: 18px; color: var(--pl-muted); cursor: pointer;
  }

  /* P3 T2 — three-detent sheet system (peek 96 / half 45dvh / full). */
  .ed-panel--peek { height: 96px; max-height: 96px; overflow-y: hidden; }
  .ed-panel--half { height: 45vh; height: 45dvh; max-height: 45dvh; }
  .ed-panel--full { height: calc(100vh - 56px); height: calc(100dvh - 56px); max-height: none; }
  .ed-panel { transition: height 160ms ease-out; will-change: transform; }
  /* Peek strip: 44px tiles so grip(48) + strip(48) = 96 exactly.
     (T2 review C2) No box-sizing:border-box is declared anywhere in this
     stylesheet, so width/height are content-box — a nonzero border adds
     directly on top of the 44px tap target. `.ed-thumb` carries a reserved
     `border: 2px solid transparent` (editor.css ~226) and `.ed-bin__expand`
     a `border: 1px dashed …` (~1283); both are zeroed here, peek-scoped
     only, so the border-box stays exactly 44px. The row's own wrapper
     (App.tsx, the photosPeek branch) uses `padding: '0 8px'` — no vertical
     padding — so the only vertical padding left is this strip's own
     bottom 4px: 44 (tile) + 4 (strip pad-bottom) = 48px, matching the
     grip's 48px 1:1 for the frozen 96px PEEK_H. */
  .ed-panel--peek .ed-bin--strip { padding: 0 8px 4px; }
  .ed-panel--peek .ed-bin--strip .ed-bin__tile { width: 44px; height: 44px; }
  .ed-panel--peek .ed-bin--strip .ed-thumb { border-width: 0; }
  .ed-panel--peek .ed-bin__expand { min-width: 44px; min-height: 44px; padding: 0 6px; border-width: 0; }

  /* Compact horizontal-scrolling photo strip used by the peek detent. */
  .ed-bin--strip { display: flex; overflow-x: auto; -webkit-overflow-scrolling: touch; gap: 8px; padding-bottom: 4px; }
  .ed-bin--strip .ed-bin__tile { flex: 0 0 auto; width: 72px; height: 72px; }

  /* Trailing "expand" tile next to the peek strip — makes the swipe-up
     gesture discoverable without requiring it. */
  .ed-bin__expand {
    flex: 0 0 auto;
    display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 2px;
    min-width: 44px; min-height: 44px;
    padding: 4px 8px;
    border: 1px dashed var(--pl-line, #ECE0D2);
    border-radius: 10px;
    background: var(--pl-cream, #FBF5EE);
    color: var(--pl-muted, #8A7D70);
    font-size: 10px;
    font-weight: 600;
    cursor: pointer;
  }

  /* ── Stage / canvas area: add bottom padding so canvas is never
     hidden behind the fixed rail + panel ──
     We give enough bottom padding to clear both the rail bar AND a
     partially-open panel.  The panel is at most --mob-panel-max-h tall,
     but that's only when open; we use a comfortable constant here so
     the canvas centre is visible without the panel.
     (If the panel is open the user scrolls / swipes — the canvas is not
      obscured because the panel overlaps only the bottom ~52 vh.) */
  /* Clear the fixed tab bar (rail height) PLUS the mobile CTA row PLUS the
     home-bar safe-area inset so nothing on the stage is hidden behind the
     bars or the notch.

     This padding used to sit on `.ed-stage`. `.ed-pagejump` is .ed-stage's
     SIBLING, not its child, so the padding protected the canvas and pushed
     the pager down INTO the bars instead: on a 430x930 phone the pager
     rendered at y=882-926 and .ed-rail (position: fixed, z-index 100) covers
     872-930 — every one of the three buttons hit-tested to a rail tab, so
     tapping "›" opened a tool panel instead of turning the month. Moving the
     padding one level up, onto the column that holds BOTH, keeps the canvas
     exactly where it was and puts the pager above the bars. */
  /* + --mob-sheet-peek-h (2026-09-09): THE BOTTOM OF THE DESIGN WAS HIDDEN.
     The padding cleared the three fixed bars but not the tool sheet, which is
     also fixed, sits ON TOP of that stack, and is 96px tall at its default
     `peek` detent. fitCanvasToContainer() correctly fits the canvas to its
     container — but the container ran on underneath the sheet, so the bottom
     96px of every design was simply behind it.
     On a 12x18 wall calendar that is exactly where the dates are: the owner's
     phone screenshot shows the year "2027" sliced in half by the photo strip.
     Reserving the peek height makes the whole sheet fit the space a customer
     can actually see. Expanding the sheet to half/full still overlays, but
     that is a deliberate act with an obvious way back. */
  .ed-stagecol {
    padding-bottom: calc(
      var(--mob-rail-h) + var(--mob-cta-h) + var(--mob-pager-h)
      + var(--mob-sheet-h, var(--mob-sheet-peek-h)) + env(safe-area-inset-bottom, 0px) + 4px
    );
  }

  .ed-stage {
    padding-bottom: 0;
  }

  /* PINNED, not merely in flow. On a phone the tool sheet is `position: fixed`
     and overlays the stage by design, so a pager that only sat at the bottom
     of the stage column would be buried under it — including at the default
     `peek` detent. Anchoring the pager to the same bar stack the sheet is
     anchored to (rail + CTA row) and lifting the sheet by exactly
     --mob-pager-h means one number keeps them from ever colliding, at any
     detent. z-index 99 matches the sheet's; a later sibling at the same
     index wins, and the two no longer overlap in any case.
     .ed-stagecol reserves the same height in padding, so the canvas is not
     underneath it either. */
  .ed-pagejump {
    position: fixed;
    left: 0;
    right: 0;
    bottom: calc(var(--mob-rail-h) + var(--mob-cta-h) + env(safe-area-inset-bottom, 0px));
    height: var(--mob-pager-h);
    z-index: 99;
    border-top: 1px solid rgba(59, 46, 38, .07);
    background: #fff;
  }

  /* Customer-critical wave (finding 5): the "Photo removed · Undo" toast
     sits just above the pager band — the same bar stack, one more step. */
  .ed-undo-toast {
    bottom: calc(var(--mob-rail-h) + var(--mob-cta-h) + var(--mob-pager-h) + env(safe-area-inset-bottom, 0px) + 10px);
  }

  /* Canvas padding reduced on mobile (less breathing room needed) */
  .ed-canvas-wrap {
    padding: 12px;
  }

  /* Stage toolbar: smaller buttons / reduced gap */
  .ed-stbar {
    height: 42px;
    gap: 5px;
  }

  .ed-stbar .ed-stbar-btn {
    width: 30px;
    height: 30px;
    font-size: 13px;
  }

  .ed-zoom {
    font-size: 12px;
    padding: 3px 5px;
    gap: 5px;
  }

  /* ── Upload screen: single-column, tighter padding ── */
  .ed-upload-screen {
    padding: 20px 14px 0;
  }

  /* The pinned Continue (see the base rule): it carries the bottom inset the
     scroller used to, so it clears the iPhone home bar, and it is a full-width
     thumb target like the order screen's buttons. */
  .ed-upload-screen__footer {
    padding: 12px 0 calc(var(--mob-rail-h) + env(safe-area-inset-bottom, 0px) + 12px);
  }
  .ed-upload-screen__footer .pl-btn {
    width: 100%;
  }

  .ed-upload-screen__headline {
    font-size: 22px;
  }

  .ed-upload-screen__sub {
    font-size: 13px;
    margin-top: -12px;
  }

  /* Source buttons: stack on very narrow screens */
  .ed-dz__sources {
    flex-direction: column;
    align-items: stretch;
    padding: 10px 0 0;
  }

  .ed-dz__src-btn {
    justify-content: center;
    padding: 10px 14px;
  }

  /* ── Order review screen: single-column, tighter padding ──
     (this screen has a CTA row, so it also clears --mob-cta-h) */
  .ed-order-screen {
    padding: 20px 12px calc(var(--mob-rail-h) + var(--mob-cta-h) + env(safe-area-inset-bottom, 0px) + 20px);
  }

  /* ONE Add to cart on a phone (the team, 30 Sep 2026: "This page three ADD
     TO CART Buttons"). The fixed bottom bar keeps it — price and button,
     always in thumb reach, and it already carries the short reason when the
     sale is blocked. The top bar's and the in-page one go; the in-page
     reason line and "Preview months" stay. */
  .ed-top__cta.ed-top__cta--cart { display: none; }
  .ed-order-actions .pl-btn.ed-order-addcart { display: none; }

  .ed-order-headline {
    font-size: 22px;
  }

  .ed-order-card {
    padding: 16px;
  }

  /* Order CTA buttons: full-width on mobile */
  .ed-order-actions .pl-btn {
    min-width: 0;
    width: 100%;
  }

  /* Page thumbnail grid: smaller tiles. minmax widened to 96px (from 64px) so
     the 2:1 real-render tiles (.ed-order-pg__thumb--real) get enough column
     width to stay legible — the old 64px min was sized for square LayoutPreview
     tiles only. */
  .ed-order-pages__grid {
    grid-template-columns: repeat(auto-fill, minmax(96px, 1fr));
    gap: 8px;
  }

  /* Fixed square sizing is scoped OFF real spread tiles — those size from the
     grid column via aspect-ratio (see .ed-order-pg__thumb--real above). */
  .ed-order-pg__thumb:not(.ed-order-pg__thumb--real) {
    width: 64px;
    height: 64px;
  }

  /* ── Drag-drop zone: tighter on mobile ── */
  .ed-dz {
    padding: 20px 14px 16px;
  }

  .ed-dz__icon {
    font-size: 28px;
  }

  .ed-dz__title {
    font-size: 14px;
  }
}

/* ─── Edit Photo panel ────────────────────────────────────────────────────── */
.ed-editpanel {
  display: flex;
  flex-direction: column;
  overflow-y: auto;
  flex: 1;
  min-height: 0;
}

.ed-editpanel__header {
  padding: 18px 18px 10px;
  border-bottom: 1px solid var(--pl-line, #ECE0D2);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
}

.ed-editpanel__title {
  font-family: var(--pl-font-display, 'Fraunces', Georgia, serif);
  font-size: 17px;
  font-weight: 600;
  color: var(--pl-cocoa, #3B2E26);
}

/* Undo/redo (P0-6) — same visual language as the top bar's undo slot. */
.ed-editpanel__header-actions { display: flex; gap: 6px; flex-shrink: 0; }
.ed-editpanel__hbtn {
  width: 34px; height: 34px; border-radius: 9px; border: 1px solid var(--pl-line, #ECE0D2);
  background: #fff; color: var(--pl-cocoa, #3B2E26); font-size: 15px; cursor: pointer;
  padding: 0; margin: 0; display: flex; align-items: center; justify-content: center;
}
.ed-editpanel__hbtn:disabled { opacity: .35; cursor: default; }

.ed-editpanel__section {
  padding: 14px 18px;
  border-bottom: 1px solid var(--pl-line, #ECE0D2);
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.ed-editpanel__label {
  font-size: 12px;
  font-weight: 600;
  color: var(--pl-muted, #8A7D70);
  letter-spacing: 0.04em;
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.ed-editpanel__value {
  font-size: 12px;
  font-weight: 700;
  color: var(--pl-cocoa, #3B2E26);
  font-variant-numeric: tabular-nums;
}

.ed-editpanel__slider {
  -webkit-appearance: none;
  appearance: none;
  width: 100%;
  height: 4px;
  border-radius: 4px;
  background: var(--pl-line, #ECE0D2);
  outline: none;
  cursor: pointer;
  accent-color: var(--pl-clay, #C26A4A);
}

.ed-editpanel__slider::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: var(--pl-clay, #C26A4A);
  cursor: pointer;
  border: 2px solid #fff;
  box-shadow: 0 1px 4px rgba(0,0,0,.2);
}

.ed-editpanel__slider::-moz-range-thumb {
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: var(--pl-clay, #C26A4A);
  cursor: pointer;
  border: 2px solid #fff;
  box-shadow: 0 1px 4px rgba(0,0,0,.2);
}

.ed-editpanel__row {
  display: flex;
  gap: 8px;
}

.ed-editpanel__btn {
  flex: 1;
  height: 34px;
  border-radius: var(--pl-radius-sm, 9px);
  border: 1px solid var(--pl-line, #ECE0D2);
  background: #fff;
  color: var(--pl-cocoa, #3B2E26);
  font-size: 12px;
  font-weight: 600;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 4px;
  transition: border-color 0.15s, background 0.15s, color 0.15s;
}

.ed-editpanel__btn:hover {
  border-color: var(--pl-clay, #C26A4A);
  color: var(--pl-clay, #C26A4A);
}

.ed-editpanel__btn--outline {
  border: 1.5px solid var(--pl-clay, #C26A4A);
  color: var(--pl-clay, #C26A4A);
  background: var(--pl-cream, #FBF5EE);
}

.ed-editpanel__btn--outline:hover {
  background: var(--pl-cream-2, #F4E9DC);
}

.ed-editpanel__btn--active {
  background: var(--pl-clay, #C26A4A);
  color: #fff;
  border-color: var(--pl-clay, #C26A4A);
}

.ed-editpanel__btn--active:hover {
  background: #a85a3c;
  border-color: #a85a3c;
  color: #fff;
}

/* ── Filter preset swatches ──────────────────────────────────────────────── */
.ed-editpanel__presets {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

.ed-editpanel__preset-btn {
  flex: 1 1 calc(33% - 4px);
  min-width: 0;
  height: 30px;
  border-radius: var(--pl-radius-sm, 9px);
  border: 1.5px solid var(--pl-line, #ECE0D2);
  background: var(--pl-cream, #FBF5EE);
  color: var(--pl-cocoa, #3B2E26);
  font-size: 11px;
  font-weight: 600;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: border-color 0.15s, background 0.15s, color 0.15s;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  padding: 0 4px;
}

.ed-editpanel__preset-btn:hover {
  border-color: var(--pl-clay, #C26A4A);
  color: var(--pl-clay, #C26A4A);
}

.ed-editpanel__preset-btn--active {
  background: var(--pl-clay, #C26A4A);
  color: #fff;
  border-color: var(--pl-clay, #C26A4A);
}

.ed-editpanel__preset-btn--active:hover {
  background: #a85a3c;
  border-color: #a85a3c;
  color: #fff;
}

/* ── Segmented control (Fill / Fit) ─────────────────────────────────────── */
.ed-editpanel__segmented {
  display: flex;
  border: 1.5px solid var(--pl-line, #ECE0D2);
  border-radius: var(--pl-radius-sm, 9px);
  overflow: hidden;
}

.ed-editpanel__seg-btn {
  flex: 1;
  height: 32px;
  border: none;
  background: var(--pl-cream, #FBF5EE);
  color: var(--pl-muted, #8A7D70);
  font-size: 12px;
  font-weight: 600;
  cursor: pointer;
  transition: background 0.15s, color 0.15s;
}

.ed-editpanel__seg-btn + .ed-editpanel__seg-btn {
  border-left: 1.5px solid var(--pl-line, #ECE0D2);
}

.ed-editpanel__seg-btn--active {
  background: var(--pl-clay, #C26A4A);
  color: #fff;
}

.ed-editpanel__seg-btn:hover:not(.ed-editpanel__seg-btn--active) {
  background: var(--pl-cream-2, #F4E9DC);
  color: var(--pl-cocoa, #3B2E26);
}

/* ── Border color swatches ───────────────────────────────────────────────── */
.ed-editpanel__swatches {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
  align-items: center;
}

.ed-editpanel__swatch {
  width: 26px;
  height: 26px;
  border-radius: 50%;
  border: 2px solid transparent;
  cursor: pointer;
  flex-shrink: 0;
  transition: border-color 0.15s, transform 0.15s;
  position: relative;
  box-shadow: 0 1px 3px rgba(0,0,0,.18);
}

.ed-editpanel__swatch:hover {
  transform: scale(1.12);
}

.ed-editpanel__swatch--active {
  border-color: var(--pl-clay, #C26A4A);
  transform: scale(1.12);
}

.ed-editpanel__swatch--custom {
  background: conic-gradient(
    #ff0000, #ffff00, #00ff00, #00ffff, #0000ff, #ff00ff, #ff0000
  );
  font-size: 15px;
  font-weight: 700;
  color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  text-shadow: 0 1px 2px rgba(0,0,0,.5);
}

/* ── Rounded-corners toggle ──────────────────────────────────────────────── */
.ed-editpanel__toggle-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-top: 2px;
}

.ed-editpanel__toggle-label {
  font-size: 12px;
  font-weight: 600;
  color: var(--pl-muted, #8A7D70);
}

.ed-editpanel__toggle {
  width: 38px;
  height: 22px;
  border-radius: 11px;
  border: none;
  background: var(--pl-line, #ECE0D2);
  cursor: pointer;
  padding: 2px;
  display: flex;
  align-items: center;
  transition: background 0.2s;
  flex-shrink: 0;
}

.ed-editpanel__toggle--on {
  background: var(--pl-clay, #C26A4A);
  justify-content: flex-end;
}

.ed-editpanel__toggle-thumb {
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 1px 4px rgba(0,0,0,.22);
  flex-shrink: 0;
}

/* ── Edit Photo panel: mobile bottom-sheet adjustments ── */
@media (max-width: 768px), (max-height: 500px) {

  /* Panel content should scroll within the sheet — already handled by
     .ed-panel overflow-y:auto; ensure the editpanel itself doesn't double-scroll */
  .ed-editpanel {
    overflow-y: visible;
    flex: none;
  }

  /* Slightly tighter header */
  .ed-editpanel__header {
    padding: 12px 14px 8px;
  }

  .ed-editpanel__title {
    font-size: 15px;
  }

  /* Undo/redo tap targets meet the 44px mobile minimum (P0-6). */
  .ed-editpanel__hbtn { width: 44px; height: 44px; }

  /* Tighter section padding */
  .ed-editpanel__section {
    padding: 10px 14px;
    gap: 6px;
  }

  /* Sliders: full-width, easier to grab on touch (taller track + bigger thumb) */
  .ed-editpanel__slider {
    height: 6px;
  }

  .ed-editpanel__slider::-webkit-slider-thumb {
    width: 22px;
    height: 22px;
  }

  .ed-editpanel__slider::-moz-range-thumb {
    width: 22px;
    height: 22px;
  }

  /* Action button rows: full-width, tap-friendly (min 44px) */
  .ed-editpanel__row {
    gap: 6px;
  }

  .ed-editpanel__btn {
    height: 44px;
    font-size: 13px;
    border-radius: 10px;
  }

  /* Segmented control (Fill/Fit): full-width, taller segments */
  .ed-editpanel__segmented {
    width: 100%;
  }

  .ed-editpanel__seg-btn {
    height: 44px;
    font-size: 13px;
  }

  /* Filter preset buttons: wrap, tap-friendly */
  /* FILTERS SCROLL SIDEWAYS, like every phone photo editor. Stacked, the six
     presets were six full-width rows — 342px, over a third of the whole Edit
     Photo panel, for the control people touch least. Instagram, Google Photos
     and Canva all put filters in a horizontal strip for exactly this reason:
     it costs one row of height no matter how many presets there are, and we
     can add a seventh without the panel growing. */
  .ed-editpanel__presets {
    flex-wrap: nowrap;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    scroll-snap-type: x proximity;
    gap: 8px;
    padding-bottom: 4px;
    /* Bleed to the sheet's edges so a half-visible chip signals "scrollable"
       instead of looking like a gap. */
    margin: 0 -12px; padding-left: 12px; padding-right: 12px;
  }

  .ed-editpanel__preset-btn {
    flex: 0 0 auto;
    width: 78px;
    height: 44px;
    font-size: 12px;
    border-radius: 10px;
    padding: 0 6px;
    scroll-snap-align: start;
  }

  /* Border color swatches: larger tap targets */
  .ed-editpanel__swatch {
    width: 36px;
    height: 36px;
  }

  .ed-editpanel__swatch--custom {
    width: 36px;
    height: 36px;
    font-size: 17px;
  }

  /* Toggle row: ensure toggle button is easy to tap */
  .ed-editpanel__toggle {
    width: 46px;
    height: 28px;
    border-radius: 14px;
  }

  .ed-editpanel__toggle-thumb {
    width: 22px;
    height: 22px;
  }
}

/* ── Extra-small screens (≤ 380px): tighter tab tiles ── */
@media (max-width: 380px) {
  .ed-tool {
    width: 38px;
  }
}

/* ─── P3 T3: page-jump row (screen 1a) ────────────────────────────────────
   Replaces the old inline spread-nav block. One 40px-high row, mobile and
   desktop alike; Prev/Next are 44px circular hit targets (mobile-first
   minimum) that visually sit centered in the row. */
.ed-pagejump {
  height: 40px;
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 14px;
  border-top: 1px solid rgba(59, 46, 38, .07);
  background: #fff;
}

.ed-pagejump__nav {
  width: 44px;
  height: 44px;
  border-radius: 50%;
  background: #fff;
  border: 1px solid var(--pl-line, #ECE0D2);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  font-size: 14px;
  cursor: pointer;
  flex-shrink: 0;
}

.ed-pagejump__nav:disabled {
  cursor: not-allowed;
  opacity: .4;
}

.ed-pagejump__label {
  min-height: 44px;
  padding: 0 12px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: none;
  background: none;
  font-size: 13px;
  font-weight: 600;
  color: var(--pl-muted, #8A7D70);
  cursor: pointer;
}

/* ─── P3 T3: all-spreads overview grid (screen 1a) ────────────────────────
   Full-screen overlay opened from PageJumpRow's "${label} ⌄" button — real
   spread thumbnails (server-owned via spreadThumbs), tap-to-jump, and
   inline add/remove-spread tiles. */
.ed-spreadgrid {
  position: fixed;
  inset: 0;
  z-index: 200;
  background: var(--pl-cream, #FBF5EE);
  display: flex;
  flex-direction: column;
}

.ed-spreadgrid__header {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 14px 16px;
  border-bottom: 1px solid var(--pl-line, #ECE0D2);
}

.ed-spreadgrid__title {
  font-family: var(--pl-font-display, 'Fraunces', Georgia, serif);
  font-size: 15px;
  font-weight: 600;
  color: var(--pl-cocoa, #3B2E26);
}

.ed-spreadgrid__close {
  width: 44px;
  height: 44px;
  flex-shrink: 0;
  border-radius: 50%;
  border: 1px solid var(--pl-line, #ECE0D2);
  background: #fff;
  font-size: 16px;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
}

.ed-spreadgrid__grid {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
  align-content: start;
  gap: 16px;
  padding: 16px;
  padding-bottom: calc(16px + env(safe-area-inset-bottom, 0px));
}

.ed-spreadgrid__tile {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  min-width: 0;
  padding: 0;
  border: none;
  background: none;
  font: inherit;
  cursor: pointer;
}

.ed-spreadgrid__thumb {
  position: relative;
  width: 100%;
  aspect-ratio: 2 / 1;
  border-radius: 10px;
  border: 2px solid var(--pl-line, #ECE0D2);
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--pl-cream-2, #F4E9DC);
}

.ed-spreadgrid__thumb img {
  width: 100%;
  height: 100%;
  object-fit: contain; /* the whole sheet — see .ed-order-pg__thumb--real img */
  display: block;
}

.ed-spreadgrid__placeholder {
  font-size: 14px;
  color: var(--pl-muted, #8A7D70);
}

.ed-spreadgrid__tile--current .ed-spreadgrid__thumb {
  border-color: var(--pl-clay, #C26A4A);
}

.ed-spreadgrid__remove {
  position: absolute;
  top: 4px;
  right: 4px;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  background: rgba(59, 46, 38, .72);
  color: #fff;
  font-size: 14px;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}

.ed-spreadgrid__label {
  font-size: 12px;
  font-weight: 600;
  color: var(--pl-muted, #8A7D70);
  text-align: center;
}

.ed-spreadgrid__thumb--add {
  display: flex;
  align-items: center;
  justify-content: center;
  border-style: dashed;
  font-size: 28px;
  line-height: 1;
  color: var(--pl-clay, #C26A4A);
  background: transparent;
}

@media (max-width: 480px) {
  .ed-spreadgrid__grid {
    grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
    gap: 12px;
  }
}

/* ─── P3 T8: book-mockup preview (screen 1d) ──────────────────────────────
   Full-screen dark viewer replacing the flat spread-image preview: the
   current spread renders as two page halves joined at a center gutter
   (perspective + a subtle gutter shadow + a short rotateY flip on spread
   change), with a page-dot row and swipe/keyboard navigation. "Cover &
   spine" toggles a wrapper view — the cover render split into Back / Spine
   / Front panels (concept preview, not a pixel replica of the M2 print
   sheet). Read-only — no save-queue interaction. */
.ed-preview {
  position: fixed;
  inset: 0;
  z-index: 9999;
  background: #17130f;
  display: flex;
  flex-direction: column;
}

.ed-preview__header {
  flex-shrink: 0;
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 14px 60px;
}

.ed-preview__close {
  position: absolute;
  left: 12px;
  top: 50%;
  transform: translateY(-50%);
  width: 44px;
  height: 44px;
  flex-shrink: 0;
  border-radius: 50%;
  border: 1px solid rgba(255, 255, 255, .25);
  background: rgba(255, 255, 255, .08);
  color: #fff;
  font-size: 16px;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
}

.ed-preview__heading {
  display: flex;
  flex-direction: column;
  align-items: center;
  color: #fff;
  text-align: center;
  min-width: 0;
}

.ed-preview__title {
  font-family: var(--pl-font-display, 'Fraunces', Georgia, serif);
  font-size: 15px;
  font-weight: 600;
  margin: 0;
}

.ed-preview__subtitle {
  font-size: 12px;
  color: rgba(255, 255, 255, .6);
  margin-top: 2px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.ed-preview__stage {
  flex: 1;
  min-height: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  padding: 20px;
}

.ed-preview__empty {
  color: rgba(255, 255, 255, .5);
  font-size: 14px;
}

/* Book mode — two page halves of the current spread's render, joined at a
   center gutter. */
.ed-preview__book {
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  perspective: 1400px;
}

.ed-preview__pages {
  position: relative;
  display: flex;
  width: min(100%, 720px);
  max-height: 100%;
  aspect-ratio: 2 / 1;
  box-shadow: 0 24px 60px rgba(0, 0, 0, .55);
  border-radius: 3px;
  overflow: hidden;
}

.ed-preview__page {
  position: relative;
  width: 50%;
  height: 100%;
  background-repeat: no-repeat;
  background-size: 200% 100%;
  background-color: #fff;
}

/* Single-page products (calendar, frame, collage, poster). The inline
   aspect-ratio BookPreview measures from the render wins over the 2/1 above;
   these rules stop the page being SPLIT as well as squashed — one page, whole
   image, no fake seam. Width is capped so a tall portrait page cannot grow
   past the viewport. */
.ed-preview__pages--single {
  width: auto;
  max-width: min(100%, 720px);
  height: min(100%, 78vh);
}

.ed-preview__page--single {
  width: 100%;
  height: 100%;
  background-size: 100% 100%;
  background-position: 0 0;
  animation: ed-preview-flip 420ms ease-out;
  transform-origin: center center;
}

.ed-preview__page--left {
  background-position: 0 0;
}

.ed-preview__page--right {
  background-position: 100% 0;
  transform-origin: left center;
  animation: ed-preview-flip 420ms ease-out;
}

.ed-preview__page::after {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
}

.ed-preview__page--left::after {
  background: linear-gradient(to left, rgba(0, 0, 0, .08) 0%, rgba(0, 0, 0, 0) 12%);
}

.ed-preview__page--right::after {
  background: linear-gradient(to right, rgba(0, 0, 0, .08) 0%, rgba(0, 0, 0, 0) 12%);
}

@keyframes ed-preview-flip {
  from {
    transform: rotateY(-16deg);
    opacity: .55;
  }
  to {
    transform: rotateY(0deg);
    opacity: 1;
  }
}

@media (prefers-reduced-motion: reduce) {
  .ed-preview__page--right {
    animation: none;
  }
}

/* Page-dot row */
.ed-preview__dots {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 4px 16px 12px;
  flex-wrap: wrap;
}

.ed-preview__dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: rgba(255, 255, 255, .3);
}

.ed-preview__dot--active {
  background: var(--pl-clay, #C26A4A);
}

.ed-preview__dot--ellipsis {
  width: auto;
  height: auto;
  border-radius: 0;
  background: none;
  color: rgba(255, 255, 255, .5);
  font-size: 11px;
  line-height: 1;
}

/* Bottom pills */
.ed-preview__pills {
  flex-shrink: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  padding: 10px 16px calc(12px + env(safe-area-inset-bottom, 0px));
}

.ed-preview__pill {
  flex: 1 1 0;
  min-width: 0;
  min-height: 44px;
  padding: 0 10px;
  border-radius: var(--pl-radius-pill, 30px);
  border: 1px solid rgba(255, 255, 255, .25);
  background: rgba(255, 255, 255, .08);
  color: #fff;
  font-size: 14px;
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  cursor: pointer;
}

.ed-preview__pill:disabled {
  opacity: .35;
  cursor: not-allowed;
}

.ed-preview__pill--toggle {
  background: var(--pl-clay, #C26A4A);
  border-color: var(--pl-clay, #C26A4A);
}

/* "Continue to order ›" in place of the last page's Next (team, 30 Sep 2026). */
.ed-preview__pill--go {
  background: var(--pl-clay, #C26A4A);
  border-color: var(--pl-clay, #C26A4A);
}

/* 320px check: the toggle pill breaks onto its own full-width row first,
   Previous/Next share the row beneath it. */
@media (max-width: 360px) {
  .ed-preview__pill--toggle {
    order: -1;
    flex-basis: 100%;
  }
}

/* "Continue to order" does not fit half a phone's row (the theme sets button
   labels in capitals; at 390px it read "CONTINUE TO OR…"). On a phone it
   takes its own full-width row above Previous, like the toggle above. */
@media (max-width: 480px) {
  .ed-preview__pill--go {
    order: -1;
    flex-basis: 100%;
  }
}

/* Wrapper mode ("Cover & spine") — cover render split into Back / Spine /
   Front panels, a concept preview of the print wrapper (not the pixel-exact
   M2 print sheet — see the BookPreview.tsx doc-comment). */
.ed-preview__wrapper {
  width: 100%;
  max-width: 720px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
}

.ed-preview__wrapper-panels {
  display: flex;
  width: 100%;
  aspect-ratio: 3.2 / 1;
  box-shadow: 0 24px 60px rgba(0, 0, 0, .55);
  border-radius: 3px;
  overflow: hidden;
}

.ed-preview__panel {
  border: 1px solid rgba(255, 255, 255, .35);
  background-repeat: no-repeat;
  background-color: #fff;
}

.ed-preview__panel--back,
.ed-preview__panel--front {
  flex: 1 1 0;
  background-size: 200% 100%;
}

.ed-preview__panel--back {
  background-position: 0 0;
}

.ed-preview__panel--front {
  background-position: 100% 0;
}

.ed-preview__panel--spine {
  flex: 0 0 28px;
  width: 28px;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}

.ed-preview__spine-title {
  writing-mode: vertical-rl;
  color: #fff;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .04em;
  white-space: nowrap;
  max-height: 100%;
  text-shadow: 0 1px 3px rgba(0, 0, 0, .6);
}

.ed-preview__wrapper-captions {
  display: flex;
  gap: 6px;
  color: rgba(255, 255, 255, .7);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: .04em;
  text-transform: uppercase;
}

/* ─────────────────────────────────────────────────────────────────────────
   PHOTO TOOLS — ONE AT A TIME, 2026-09-09.
   Selecting a photo used to open 1304px of controls in a 380px sheet: three
   and a half screens, with Border 1064px down. Every option present, none
   findable. Google Photos' 2025 editor and Canva both solve it the same way
   and it is now simply what a phone photo editor looks like — a row of tabs,
   one tool on screen, its slider directly beneath it.
   ───────────────────────────────────────────────────────────────────────── */
.ed-editpanel__tabs {
  display: flex; gap: 4px;
  padding: 6px 0 8px; margin-bottom: 4px;
  border-bottom: 1px solid var(--pl-line);
}
.ed-editpanel__tab {
  flex: 1 1 0; min-width: 0;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 2px;
  min-height: 46px; padding: 4px 2px;
  border: 1px solid transparent; border-radius: 10px;
  background: none; color: var(--pl-muted);
  font-size: 11px; font-weight: 600; cursor: pointer;
}
.ed-editpanel__tab-icon { font-size: 16px; line-height: 1; }
.ed-editpanel__tab-label {
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%;
}
.ed-editpanel__tab--active {
  background: var(--pl-blush, #f7ece7);
  border-color: var(--pl-clay, #b4694a);
  color: var(--pl-cocoa, #3b2e26);
}

/* Auto-enhance switch (1.9.0) — sits above the filter presets. */
.ed-autoenh { display: flex; align-items: center; gap: 10px; margin: 4px 0 10px; }
.ed-autoenh__label { font-weight: 600; font-size: 14px; }
.ed-autoenh__seg { display: inline-flex; border: 1px solid var(--pl-line, #e7ddd0); border-radius: 999px; overflow: hidden; }
.ed-autoenh__btn { min-height: 44px; min-width: 56px; border: 0; background: transparent; font: inherit; cursor: pointer; }
.ed-autoenh__btn[aria-pressed="true"] { background: var(--pl-clay, #b45f3a); color: #fff; }
.ed-autoenh__custom { font-size: 12px; color: var(--pl-muted, #8a7d70); }


/*# sourceMappingURL=editor.bundle.css.map*/