/* G.5 — Photo Book editor refresh: image-based theme picker, in-editor
 * theme modal, drafts dashboard, ARIA live regions, toolbar buttons.
 *
 * Built on top of mbit_commerce/storefront.css. Adds-only; doesn't
 * override existing rules.
 */

/* ---- I.8 (2026-05-25) — Local design-token bridge -----------------
 * The editor stylesheet referenced `var(--text-strong, #1a1a1a)` in 7
 * places (Flachansicht tab label, .pb-spread-layout select, caption
 * inputs, etc.). The variable was NEVER defined upstream — so every
 * reference resolved to the fallback `#1a1a1a` (near-black). Against
 * the storefront's dark-first surfaces (--bg-elev-1 = #161b22) that
 * rendered all of those labels invisible: the user's 2026-05-25
 * screenshot showed an empty pill where "Flachansicht" should sit.
 *
 * Define the token here mapping to the existing --text-primary
 * (already AAA-compliant against the design system's surface tokens
 * in both dark + light modes). Keep the per-rule fallback so any
 * external consumer that strips this file still gets a usable color.
 */
:root {
  --text-strong: var(--text-primary, #f5f5f7);
}

/* ---- visually-hidden helper (for ARIA live regions) ---------------- */
.visually-hidden {
  position: absolute !important;
  width: 1px !important;
  height: 1px !important;
  padding: 0 !important;
  margin: -1px !important;
  overflow: hidden !important;
  clip: rect(0, 0, 0, 0) !important;
  white-space: nowrap !important;
  border: 0 !important;
}

/* ---- Theme picker (image-based) ------------------------------------ */
.theme-card { display: block; cursor: pointer; }
.theme-card .theme-preview {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 400 / 280;
  object-fit: cover;
  border-radius: var(--radius-md);
  border: 2px solid var(--stroke-soft);
  transition: border-color var(--t-fast) var(--ease), box-shadow var(--t-fast) var(--ease), transform var(--t-fast) var(--ease);
}
.theme-card:hover .theme-preview { transform: translateY(-2px); border-color: var(--stroke-strong); }
.theme-card input:checked + .theme-preview,
.theme-card input:checked ~ .theme-preview { /* defensive: handle source order changes */
  border-color: var(--accent);
  box-shadow: 0 0 0 2px var(--accent), var(--shadow-card);
}
.theme-card input:focus-visible + .theme-preview,
.theme-card input:focus-visible ~ .theme-preview { box-shadow: var(--shadow-glow); }
.theme-card-label {
  display: block;
  margin-top: var(--s-2);
  text-align: center;
  font-size: 0.9rem;
}
.theme-card-label strong { display: block; }
.theme-card-label small {
  display: block;
  color: var(--text-muted);
  font-size: 0.75rem;
  margin-top: 2px;
}

/* ---- Photo Book editor — toolbar additions ------------------------- */
.pb-editor-toolbar {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2);
  margin-top: var(--s-2);
}
.pb-editor-toolbar button {
  padding: 0.5rem 0.85rem;
  border: 1px solid var(--stroke-strong);
  background: var(--bg-elev-2);
  color: var(--text);
  border-radius: var(--radius-md);
  font-size: 0.85rem;
  cursor: pointer;
  transition: all var(--t-fast) var(--ease);
  min-height: 44px; /* WCAG 2.5.5 target size */
}
.pb-editor-toolbar button:hover { background: var(--bg-elev-1); border-color: var(--accent); }
.pb-editor-toolbar button:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.pb-editor-toolbar button:disabled { opacity: 0.5; cursor: not-allowed; }

/* ---- Theme-change modal (in-editor) -------------------------------- */
.pb-modal-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.55);
  z-index: 1000;
  display: grid;
  place-items: center;
  padding: var(--s-4);
}
.pb-modal-backdrop[hidden] { display: none; }
.pb-modal {
  background: var(--bg-elev-1);
  border: 1px solid var(--stroke-soft);
  border-radius: var(--radius-lg);
  max-width: 880px;
  width: 100%;
  max-height: 90vh;
  overflow-y: auto;
  padding: var(--s-5);
  box-shadow: var(--shadow-card);
}
.pb-modal-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: var(--s-4);
}
.pb-modal-header h2 { margin: 0; font-size: 1.25rem; }
.pb-modal-close {
  background: transparent;
  border: 1px solid var(--stroke-soft);
  border-radius: var(--radius-md);
  width: 44px;
  height: 44px;
  font-size: 1.25rem;
  cursor: pointer;
  display: grid;
  place-items: center;
}
.pb-modal-close:hover { background: var(--bg-elev-2); }
.pb-modal-close:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.pb-modal .theme-grid {
  grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
}
.pb-modal .theme-card.is-current .theme-preview {
  border-color: var(--accent);
  box-shadow: 0 0 0 2px var(--accent);
}

/* ---- Drafts dashboard ---------------------------------------------- */
.pb-drafts {
  max-width: 1000px;
  margin: 0 auto;
  display: grid;
  gap: var(--s-5);
}
.pb-drafts-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  gap: var(--s-4);
  margin: 0;
  padding: 0;
  list-style: none;
}
.pb-draft-card {
  background: var(--bg-elev-1);
  border: 1px solid var(--stroke-soft);
  border-radius: var(--radius-lg);
  padding: var(--s-4);
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
  transition: transform var(--t-fast) var(--ease), border-color var(--t-fast) var(--ease);
}
.pb-draft-card:hover { transform: translateY(-2px); border-color: var(--stroke-strong); }
.pb-draft-thumb {
  aspect-ratio: 4 / 3;
  background: var(--bg-elev-2);
  border-radius: var(--radius-md);
  display: grid;
  place-items: center;
  overflow: hidden;
}
.pb-draft-thumb img { width: 100%; height: 100%; object-fit: cover; }
.pb-draft-thumb .placeholder { font-size: 2rem; opacity: 0.4; }
.pb-draft-title { font-weight: 600; font-size: 1rem; margin: 0; }
.pb-draft-meta { color: var(--text-muted); font-size: 0.85rem; margin: 0; }
.pb-draft-status {
  display: inline-block;
  padding: 2px 8px;
  border-radius: var(--radius-sm, 4px);
  font-size: 0.75rem;
  background: var(--bg-elev-2);
  color: var(--text-muted);
}
.pb-draft-status.status-draft { background: rgba(251, 191, 36, 0.15); color: #b45309; }
.pb-draft-status.status-awaiting { background: rgba(96, 165, 250, 0.15); color: #1d4ed8; }
.pb-draft-status.status-complete { background: rgba(94, 234, 212, 0.15); color: #047857; }
.pb-draft-actions {
  display: flex;
  gap: var(--s-2);
  margin-top: auto;
  padding-top: var(--s-2);
}
.pb-draft-actions a {
  flex: 1 1 auto;
  text-align: center;
  padding: 0.55rem 0.85rem;
  border-radius: var(--radius-md);
  background: var(--accent);
  color: #fff;
  text-decoration: none;
  font-weight: 600;
  font-size: 0.9rem;
  min-height: 44px;
  display: grid;
  place-items: center;
}
.pb-draft-actions a:hover { filter: brightness(1.05); }
.pb-draft-actions a:focus-visible { outline: 2px solid var(--text); outline-offset: 2px; }

.pb-drafts-empty {
  padding: var(--s-6);
  text-align: center;
  background: var(--bg-elev-1);
  border: 1px dashed var(--stroke-soft);
  border-radius: var(--radius-lg);
}

/* ---- WCAG fix: empty-slot placeholder text contrast ---------------- *
 * The base `.pb-slot` rule (mbit_commerce/storefront.css) uses
 * `--text-faint` (#989899) on the elevated white background — 2.88:1
 * vs WCAG AA 4.5:1. Bump to `--text-muted` for placeholder labels.
 */
.pb-slot { color: #595959 !important; }
.pb-slot strong { color: #2a2a2a !important; }

/* I.5 / Fix 2 (2026-05-25) — empty-state hint rendered as a button so
 * the buyer can click it to open the Photos rail-panel directly.
 * Replaces the static "Foto rechts auswählen, dann hier klicken" copy
 * (the right panel is closed by default; the hint pointed nowhere).
 *
 * WCAG 2.1 AA notes:
 *   - color #2563eb on #f5f7fa → 5.85:1 (AA Large + AA Normal),
 *     close enough to AAA 7:1 to read as "interactive".
 *   - min-height 32px + 12px padding gives a comfortable hit area on
 *     desktop; full-width inside .pb-slot keeps it tappable on touch.
 *     For pure WCAG 2.5.5 we rely on the entire .pb-slot still being
 *     clickable (the parent's click handler delegates to .pb-empty-hint),
 *     so the effective tap target is the whole slot (≥ 200×200px).
 *   - :focus-visible outline matches the rest of the editor accents.
 *   - text-align center so the long hint reads naturally inside the slot.
 */
.pb-slot .pb-empty-hint {
  display: inline-block;
  background: transparent;
  border: 0;
  padding: 6px 10px;
  margin: 4px 0 0;
  color: #2563eb;
  font: inherit;
  font-size: 0.85rem;
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 2px;
  cursor: pointer;
  text-align: center;
  border-radius: 6px;
  line-height: 1.35;
  max-width: 28em;
}
.pb-slot .pb-empty-hint:hover {
  background: rgba(37, 99, 235, 0.08);
  color: #1d4ed8;
}
.pb-slot .pb-empty-hint:focus-visible {
  outline: 2px solid var(--accent, #2563eb);
  outline-offset: 2px;
  background: rgba(37, 99, 235, 0.08);
}

/* The active source-tab pill inherits the (AAA-darkened) --accent:
 * #047857 background; #1a1a1a text-on-dark-green is only 3.17:1.
 * Force white text to clear WCAG AA at all viewports. */
.source-tab.is-active,
.source-tab[aria-selected="true"] { color: #ffffff !important; }

/* The landing `<input type="range">` has its own visible <label> in the
 * fieldset legend ("Seitenzahl"). axe rule "label" needs an explicit
 * aria-label or for="..." link. Add a SR-only accessible name. */
.pb-config input[type="range"] { /* placeholder rule for selector specificity */ }

/* ---- Mobile tweaks ------------------------------------------------- */
@media (max-width: 600px) {
  .theme-grid { grid-template-columns: repeat(2, 1fr); }
  .pb-editor-toolbar { width: 100%; }
  .pb-editor-toolbar button { flex: 1 1 auto; }
}

/* ---- H.4.b — Mobile spread navigator -------------------------------
 * The editor renders spreadCount + 1 (cover stays visible separately,
 * but spreads stack vertically). On a 28-page book that's 14 spreads;
 * at 280px tall each, mobile scrolls ~9000px. This nav collapses the
 * stack to a single active spread + prev/next + thumb strip.
 *
 * Desktop (>= 769px): hide nav, show all spreads as before.
 * Mobile (<= 768px): show nav, only render .is-active-spread.
 */
.pb-spread-nav {
  display: none; /* desktop default */
  flex-direction: column;
  gap: var(--s-3);
  margin-bottom: var(--s-3);
  padding: var(--s-3);
  background: var(--bg-elev-1);
  border: 1px solid var(--stroke-soft);
  border-radius: var(--radius-md);
  position: sticky;
  top: 60px;
  z-index: 2;
}
.pb-spread-nav-controls {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-3);
}
.pb-spread-nav-btn {
  min-width: 44px;
  min-height: 44px;
  font-size: 1.5rem;
  background: var(--bg-elev-2);
  color: var(--text);
  border: 1px solid var(--stroke-strong);
  border-radius: var(--radius-md);
  cursor: pointer;
  line-height: 1;
  transition: all var(--t-fast) var(--ease);
}
.pb-spread-nav-btn:hover:not(:disabled) {
  background: var(--accent);
  color: #ffffff;
  border-color: var(--accent);
}
.pb-spread-nav-btn:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.pb-spread-nav-btn:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}
.pb-spread-nav-label {
  font-weight: 600;
  font-size: 0.95rem;
  flex: 1 1 auto;
  text-align: center;
}
.pb-spread-strip {
  display: flex;
  flex-direction: row;
  gap: var(--s-2);
  overflow-x: auto;
  overflow-y: hidden;
  padding-bottom: var(--s-1);
  scroll-snap-type: x mandatory;
  -webkit-overflow-scrolling: touch;
}
.pb-spread-thumb {
  flex: 0 0 auto;
  min-width: 56px;
  min-height: 44px;
  padding: 0.4rem 0.6rem;
  background: var(--bg-elev-2);
  color: var(--text);
  border: 1px solid var(--stroke-strong);
  border-radius: var(--radius-sm, 4px);
  cursor: pointer;
  font-size: 0.8rem;
  white-space: nowrap;
  scroll-snap-align: start;
  transition: all var(--t-fast) var(--ease);
}
.pb-spread-thumb:hover {
  border-color: var(--accent);
}
.pb-spread-thumb:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.pb-spread-thumb[aria-current="true"] {
  background: var(--accent);
  color: #ffffff;
  border-color: var(--accent);
  font-weight: 600;
}

@media (max-width: 768px) {
  .pb-spread-nav { display: flex; }
  .pb-spreads-list { display: block !important; }
  .pb-spread { display: none !important; }
  .pb-spread.is-active-spread { display: block !important; }
}

/* ============================================================
 * I.1 — Workspace-shell adjustments for the Photo Book editor.
 *
 * The pb-editor article now carries .ws-shell, so the .ws-* grid
 * + topbar + rail + side + fab rules from print-editor.css apply.
 * The legacy .pb-editor grid (header / cover / spreads / tray) is
 * neutralised; the canvas grid-area hosts cover + spreads vertically,
 * and the asset tray now lives inside the Photos rail-tab panel.
 * ============================================================ */
.pb-editor.ws-shell {
  /* Override the legacy .pb-editor grid (storefront.css line 541) so
   * the .ws-shell grid-template-areas can take effect. .ws-shell on
   * its own already does this, but the more-specific .pb-editor rule
   * would otherwise win the cascade.
   *
   * I.27 (2026-05-26) — side panel now docks LEFT (between rail + canvas)
   * so buyers don't have to traverse the canvas every time they switch
   * tools. See the base rule in print-editor.css.
   */
  grid-template-columns: 56px 0 minmax(0, 1fr);
  grid-template-areas:
    "topbar topbar topbar"
    "rail   side   canvas"
    "fab    fab    fab";
  gap: 0;
  max-width: none;
}
.pb-editor.ws-shell[data-side-open="true"] {
  grid-template-columns: 56px 320px minmax(0, 1fr);
}
/* The form must dissolve into the grid so the .ws-* grid-area
 * children become direct grid items. Same trick the POD editor uses
 * for #ts-form. */
.pb-editor.ws-shell > #pb-form {
  display: contents;
}

/* I.9 (2026-05-25) — sticky right panel.
 *
 * The print-editor.css base rule puts `.ws-side` in the grid's `side`
 * area with `width: 0` (collapsed) and grows it to 320px when the panel
 * is open. In flat grid flow the panel sits at the TOP of its row and
 * scrolls AWAY when the buyer scrolls past the cover into the lower
 * spreads (cover + 14 spreads can be 4000+px tall). The canvas has its
 * own `overflow: auto`, but when the viewport itself is shorter than
 * the shell's min-height the page scrolls too, and the panel goes with it.
 *
 * Fix: `position: sticky` with `top: 48px` (the topbar height) so the
 * panel follows the viewport as the page or canvas scrolls. `align-self:
 * start` prevents the grid row from stretching it. `max-height: calc(
 * 100vh - 48px)` caps the panel so its own content can scroll inside
 * the panel if the controls are taller than the viewport. Mobile
 * (<= 768px) keeps print-editor.css's `position: fixed` bottom-sheet —
 * we gate this rule on min-width: 769px so it doesn't fight the
 * bottom-sheet transform.
 *
 * Note: `.ws-shell` has `overflow: hidden` for rounded-corner clipping.
 * `position: sticky` still works because (a) `overflow: hidden` does
 * not create a scroll context for sticky and (b) sticky anchors to the
 * nearest scroll container which is `<html>` (the viewport).
 */
@media (min-width: 769px) {
  .pb-editor.ws-shell .ws-side {
    position: sticky;
    top: 48px;
    align-self: start;
    max-height: calc(100vh - 48px);
    overflow-y: auto;
  }
  /* I.10 (2026-05-26) — sticky left icon rail mirrors I.9's right-panel
   * pattern. When the buyer scrolls down into the spreads (cover + 14
   * spreads can be 4000+px tall), the rail used to scroll off the top
   * along with the page so the buyer couldn't switch tabs from the
   * spread area. Same `top: 48px` anchor under the topbar, same
   * `align-self: start` so the grid row doesn't stretch it, same
   * `max-height` + `overflow-y: auto` so a future rail with many tabs
   * scrolls internally. Mobile (<= 768px) keeps print-editor.css's
   * fixed bottom-nav pattern — this rule is gated to min-width: 769px.
   */
  .pb-editor.ws-shell .ws-rail {
    position: sticky;
    top: 48px;
    align-self: start;
    max-height: calc(100vh - 48px);
    /* Vertical scroll only when the rail outgrows the viewport.
     * `overflow: hidden auto` (x hidden, y auto) prevents the
     * horizontal scrollbar from appearing for sub-pixel layout
     * jitter (the rail icon labels can be 1-2px wider than the
     * 56px column on certain font fallbacks). */
    overflow-x: hidden;
    overflow-y: auto;
  }
}

/* Inside the canvas area, .pb-cover + .pb-spreads stack vertically
 * with breathing room. The legacy grid-area assignments on these
 * sections are overridden because they no longer apply inside the
 * .ws-canvas wrapper. */
.pb-editor.ws-shell .pb-cover,
.pb-editor.ws-shell .pb-spreads {
  grid-area: auto;
  margin-bottom: var(--s-5);
}
.pb-editor.ws-shell .pb-cover {
  background: var(--bg-elev-2);
  border: 1px solid var(--stroke-soft);
  border-radius: var(--radius-lg);
  padding: var(--s-4);
}
.pb-editor.ws-shell .pb-spreads-list {
  grid-template-columns: 1fr;
}

/* Spreads-panel layout catalog — a static reference list of the
 * available page layouts. The actual layout SELECTOR per spread
 * stays on the spread itself (rendered by JS); this list is just a
 * visual reminder of the catalog. */
.pb-layout-catalog {
  list-style: none;
  padding: 0;
  margin: 0;
  display: grid;
  gap: var(--s-2);
}
.pb-layout-card {
  padding: var(--s-3);
  background: var(--bg-elev-1);
  border: 1px solid var(--stroke-soft);
  border-radius: var(--radius-md);
  font-size: 0.85rem;
}
.pb-layout-card strong { display: block; }
.pb-layout-card small { color: var(--text-muted); }

/* Settings-panel meta list. */
.pb-meta-list {
  display: grid;
  grid-template-columns: max-content 1fr;
  column-gap: var(--s-3);
  row-gap: var(--s-1);
  margin: 0 0 var(--s-3);
  font-size: 0.85rem;
}
.pb-meta-list dt { color: var(--text-muted); }
.pb-meta-list dd { margin: 0; }
.pb-meta-list code {
  font-size: 0.78rem;
  background: var(--bg-elev-2);
  padding: 2px 6px;
  border-radius: 4px;
}

/* The asset tray now lives inside the Photos rail-tab panel. Tighten
 * its layout so it fits in the 320px side rail. */
.ws-panel[data-panel="photos"] .pb-asset-tray {
  display: block;
  padding: 0;
  background: transparent;
  border: 0;
}
.ws-panel[data-panel="photos"] .pb-tray-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--s-2);
  margin: var(--s-2) 0;
  max-height: 40vh;
  overflow-y: auto;
}
.ws-panel[data-panel="photos"] .pb-upload-row {
  display: flex;
  gap: var(--s-2);
  flex-wrap: wrap;
  align-items: center;
  margin-bottom: var(--s-2);
}
.ws-panel[data-panel="photos"] .pb-tray-pagination {
  display: flex;
  gap: var(--s-2);
  align-items: center;
  justify-content: space-between;
  margin-top: var(--s-2);
  font-size: 0.85rem;
}

/* I.18 — Text panel: list of every editable text slot in the book.
 * Click a row → scroll target into view + focus → I.11 chain switches
 * to the selection panel with text controls. Designed to surface
 * discoverability the inline-on-canvas editing model otherwise hides. */
.ws-panel[data-panel="text"] .ws-text-list {
  list-style: none;
  margin: var(--s-2) 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--s-1);
}
.ws-panel[data-panel="text"] .ws-text-row {
  display: block;
}
.ws-panel[data-panel="text"] .ws-text-row-btn {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  width: 100%;
  padding: var(--s-2) var(--s-2);
  text-align: left;
  background: var(--bg-elev-1, #f7f7f9);
  border: 1.5px solid transparent;
  border-radius: 6px;
  cursor: pointer;
  color: var(--text-primary, #1a1a1a);
  font: inherit;
  transition: background-color 0.12s, border-color 0.12s;
}
.ws-panel[data-panel="text"] .ws-text-row-btn:hover,
.ws-panel[data-panel="text"] .ws-text-row-btn:focus-visible {
  background: var(--bg-elev-2, #ececef);
  border-color: var(--accent, #1a4d80);
  outline: none;
}
.ws-panel[data-panel="text"] .ws-text-row.is-active .ws-text-row-btn {
  background: var(--bg-elev-2, #ececef);
  border-color: var(--accent, #1a4d80);
}
.ws-panel[data-panel="text"] .ws-text-row-readonly .ws-text-row-btn {
  cursor: default;
  opacity: 0.75;
}
.ws-panel[data-panel="text"] .ws-text-row-icon {
  flex: 0 0 auto;
  width: 28px;
  height: 28px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--bg-elev-2, #ececef);
  border-radius: 4px;
  font-weight: 700;
  font-size: 0.85rem;
  color: var(--text-primary, #1a1a1a);
}
.ws-panel[data-panel="text"] .ws-text-row-body {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
  flex: 1 1 auto;
}
.ws-panel[data-panel="text"] .ws-text-row-label {
  font-size: 0.88rem;
  font-weight: 600;
  color: var(--text-primary, #1a1a1a);
}
.ws-panel[data-panel="text"] .ws-text-row-preview {
  font-size: 0.8rem;
  color: var(--text-secondary, #5a5a60);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.ws-panel[data-panel="text"] .ws-text-empty {
  margin-top: var(--s-2);
  font-size: 0.85rem;
}

/* The Theme panel inherits the existing .theme-grid layout from
 * the modal version (G.5 photographic previews). Compact it for the
 * 320px side rail by dropping to a single column. */
.ws-panel[data-panel="theme"] .theme-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--s-3);
}
.ws-panel[data-panel="theme"] .theme-card .theme-preview {
  aspect-ratio: 400 / 200;
}

/* The legacy .pb-editor-toolbar is gone; no rules needed for it under
 * the shell. */

/* ---- I.2 — F.1 on-canvas handles + per-slot transform --------------
 * Each filled .pb-slot now hosts a .pb-slot-img <img> which carries the
 * live CSS transform (translate / rotate / scale) driven by handle
 * drags. The slot stays at fixed grid dimensions so the layout doesn't
 * shift when the buyer scales the inner image.
 *
 * The .pb-editor scope is what canvas_handles attaches to (handles are
 * appended as direct children, positioned absolutely in slot-relative
 * coordinates). We need position:relative on the editor root so
 * absolute positioning lands in the right context.
 */
.pb-editor.ws-shell { position: relative; }
.pb-slot.has-photo {
  position: relative;
  overflow: hidden;
}
.pb-slot-img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  user-select: none;
  pointer-events: auto;
  /* I.8 (2026-05-25) — grab cursor signals the photo is pannable
   * within its slot. Switches to grabbing while a drag is active so
   * the buyer's mental model ("I'm holding the photo") matches what
   * the screen shows. */
  cursor: grab;
  touch-action: none;
  transition: outline-color var(--t-fast) var(--ease);
}
.pb-slot.is-dragging .pb-slot-img {
  cursor: grabbing;
}
.pb-slot.has-photo .pb-slot-img.is-selected {
  outline: 1.5px dashed var(--accent, #2563eb);
  outline-offset: 2px;
}

/* ---- I.2 — F.3 text-first CTA ---------------------------------------
 * Each caption-bearing layout (text_count > 0) renders a caption block
 * below the slots. Empty state surfaces a "Text hinzufügen" button +
 * hint; filled state renders an input + color picker + remove button +
 * WCAG contrast warning marker.
 */
.pb-caption-block {
  margin-top: var(--s-3);
  padding: var(--s-3);
  border-radius: var(--radius-md);
  background: var(--bg-elev-1);
  border: 1px solid var(--stroke-soft);
}
.pb-caption-block.pb-placement-empty {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--s-2);
  border-style: dashed;
}
.pb-add-text {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  min-height: 44px; /* WCAG 2.5.5 — 44×44 target */
  padding: var(--s-2) var(--s-3);
  font-weight: 600;
}
.pb-caption-hint {
  margin: 0;
  font-size: 0.85rem;
}
.pb-caption-label {
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
  font-weight: 600;
}
.pb-caption-input {
  width: 100%;
  min-height: 44px;
  padding: var(--s-2) var(--s-3);
  font: inherit;
  border: 1px solid var(--stroke-strong);
  border-radius: var(--radius-md);
  background: var(--bg-base);
  color: var(--text-strong, #1a1a1a);
}
.pb-caption-input:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.pb-caption-controls {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-3);
  margin-top: var(--s-2);
}
.pb-caption-color {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  font-size: 0.85rem;
}
.pb-caption-color-input {
  width: 44px;
  height: 44px;
  padding: 0;
  border: 1px solid var(--stroke-strong);
  border-radius: var(--radius-sm);
  background: transparent;
  cursor: pointer;
}
.pb-caption-contrast-warn,
.ts-tx-contrast-warn {
  display: inline-block;
  margin-left: var(--s-2);
  font-size: 1.1rem;
  color: #b45309; /* AAA-cleared amber on white */
}
.pb-caption-remove {
  min-height: 44px;
}

/* ---- I.3 — H.3 chrome opacity ---------------------------------------
 * Empty .pb-slot placeholders default to ~40% opacity so the canvas
 * looks cleaner; bump to full on hover, focus-within, or whenever any
 * descendant is being dragged (mirrors POD's .ts-region treatment).
 *
 * The `.is-target` class is applied during a photo-pick gesture (the
 * buyer has picked an asset and is about to drop it); we keep those
 * fully opaque too so the drop target is obvious.
 */
.pb-slot.is-empty {
  opacity: 0.4;
  transition: opacity 180ms ease;
}
.pb-slot.is-empty:hover,
.pb-slot.is-empty:focus-within,
.pb-slot.is-empty.is-target,
.pb-slot.is-empty.is-dragging,
.pb-spreads-list:has(.is-dragging) .pb-slot.is-empty {
  opacity: 1;
}
@media (prefers-reduced-motion: reduce) {
  .pb-slot.is-empty { transition: none; }
}

/* ---- I.3 — G.4 Vorlagen gallery ------------------------------------
 * Mirror the POD .ws-template-* rules so the gallery looks identical
 * in both editors. Two-column grid on desktop, single-column on
 * narrow mobile.
 */
.ws-template-filters {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2);
  margin-bottom: var(--s-3);
}
.ws-template-filters select {
  flex: 1 1 auto;
  min-height: 44px;
  padding: var(--s-2);
  background: var(--bg-elev-1);
  border: 1px solid var(--stroke-strong);
  border-radius: var(--radius-md);
  color: var(--text-strong, #1a1a1a);
}
.ws-template-gallery {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--s-3);
}
@media (max-width: 480px) {
  .ws-template-gallery { grid-template-columns: 1fr; }
}
.ws-template-card {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: var(--s-2);
  padding: var(--s-2);
  background: var(--bg-elev-1);
  border: 1px solid var(--stroke-soft);
  border-radius: var(--radius-md);
  cursor: pointer;
  text-align: left;
  color: var(--text-strong, #1a1a1a);
  min-height: 44px;
  transition: border-color var(--t-fast) var(--ease), transform var(--t-fast) var(--ease);
}
.ws-template-card:hover {
  border-color: var(--accent);
}
.ws-template-card:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.ws-template-card img,
.ws-template-card-placeholder {
  display: block;
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  border-radius: var(--radius-sm);
  background: var(--bg-elev-2);
}
.ws-template-card-title {
  display: block;
  font-size: 0.85rem;
  font-weight: 600;
  line-height: 1.3;
}

/* Inline confirmation bar — replaces window.confirm() for template apply.
 * aria-live="polite" on the element announces the prompt to AT users.
 *
 * I.10 (2026-05-26) — the JS now appends the bar to `.ws-side .ws-side-
 * content` directly (instead of the templates `.ws-panel`). That keeps
 * the bar bounded to the 320px side column even when the templates
 * panel is closed and prevents the screenshot-review symptom where the
 * bar appeared overlapping the left rail's Vorlagen icon. We keep
 * `position: sticky; bottom: 0` so the bar tracks the visible bottom
 * of the side scroll container, and add `width: 100%; box-sizing:
 * border-box` to clamp it to the column width regardless of source
 * stacking-context quirks. */
.ws-template-confirm-bar {
  position: sticky;
  bottom: 0;
  width: 100%;
  box-sizing: border-box;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
  background: var(--bg-elev-2, #1c2230);
  border: 1px solid var(--accent, #2563eb);
  border-radius: 8px;
  padding: 10px 12px;
  margin-top: 12px;
  font-size: 0.9rem;
  box-shadow: 0 -2px 12px rgba(0, 0, 0, 0.2);
  z-index: 5;
  color: inherit;
}
.ws-template-confirm-bar > .ws-template-confirm-bar-text {
  flex: 1 1 100%;
  min-width: 0;
}
.ws-template-confirm-bar > button {
  flex: 0 0 auto;
}
.ws-template-confirm-bar .ws-template-apply-yes:focus-visible,
.ws-template-confirm-bar .ws-template-apply-no:focus-visible {
  outline: 2px solid var(--accent, #2563eb);
  outline-offset: 2px;
}

/* ---- I.3 — E.2 selection panel ------------------------------------
 * The selection panel shares the .ws-prop-* primitives from print-
 * editor.css. We add a few PB-specific tweaks so the body / header /
 * thumb work for caption + photo slot inputs.
 */

/* I.10 (2026-05-26) — gate .ws-prop-body / .ws-prop-header by [hidden].
 *
 * print-editor.css declares `.ws-prop-body { display: flex }` and
 * `.ws-prop-header { display: flex }` with the same specificity as the
 * UA `[hidden] { display: none }` rule. Source order means our class
 * rules win and `el.hidden = true` from pbShowImagePanel() /
 * pbShowTextPanel() does NOTHING — both photo and text sections stay
 * visible. The screenshot review showed cover-photo selection rendering
 * BOTH the photo controls (Skalierung, Drehung, etc.) AND the text
 * controls (INHALT textarea, font, color) stacked in the same panel.
 *
 * Fix: explicit `[hidden] { display: none }` rule for these two
 * primitives so the JS gating works as intended. Selection-kind →
 * exactly one of #pb-prop-image / #pb-prop-text visible. The cover
 * title stays inline-editable on canvas (I.7) and the side panel
 * NEVER shows the INHALT textarea + font/color controls for a cover
 * photo selection.
 */
#pb-prop-image[hidden],
#pb-prop-text[hidden],
#pb-prop-header[hidden] {
  display: none !important;
}

/* I.25 (2026-05-26) — same display-vs-hidden specificity gap as I.10
 * above, but for the Immich status + connect blocks. `.pb-immich-status`
 * declares `display:flex` and `.pb-immich-connect` declares block-flow
 * styling, both at the same specificity as `[hidden] { display:none }`.
 * Without these explicit !important overrides, the renderImmichPane()
 * JS toggles `.hidden = true` and the visible state doesn't change.
 *
 * This bug applied to the Photo Book editor too (Verbinden + connect
 * form both visible at once) but only surfaced now that the Calendar
 * editor's Fotos panel reuses the same .pb-immich-* primitives.
 */
.pb-immich-status[hidden],
.pb-immich-connect[hidden] {
  display: none !important;
}

#pb-prop-thumb {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
#pb-prop-thumb-text {
  display: inline-block;
  font: 600 1.2rem var(--font-sans, system-ui);
  color: var(--text-strong, #1a1a1a);
  text-align: center;
  width: 100%;
  line-height: 2;
}
.pb-prop-align.is-active {
  background: var(--accent);
  color: #ffffff;
  border-color: var(--accent);
}
.ws-prop-contrast.is-warn {
  color: #b45309;
}

/* ----------------------------------------------------------------------
 * I.6 (2026-05-25) — Immich source pane + used-photo markers
 * ----------------------------------------------------------------------
 * Photos panel gained two affordances:
 *   1. Tab switcher Upload ↔ Immich with a connect form for buyers who
 *      haven't linked an Immich account yet.
 *   2. Already-used photos in the tray are dimmed + show a "1×" / "2×"
 *      badge counting how many slots they fill across cover + spreads.
 */

/* Immich pane ---------------------------------------------------------- */
.pb-asset-tray .pb-source-pane {
  margin-bottom: var(--s-2);
}
.pb-immich-status {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--s-2);
  padding: var(--s-2);
  margin: 0 0 var(--s-2);
  background: var(--bg-elev-1);
  border: 1px solid var(--stroke-soft);
  border-radius: var(--radius-md);
  font-size: 0.85rem;
}
.pb-immich-status p { margin: 0; }
.pb-immich-status .btn-secondary {
  flex-shrink: 0;
  font-size: 0.78rem;
  padding: 0.25rem 0.55rem;
}
.pb-immich-connect {
  padding: var(--s-2);
  background: var(--bg-elev-1);
  border: 1px solid var(--stroke-soft);
  border-radius: var(--radius-md);
}
.pb-immich-connect .ws-field { display: block; margin-bottom: var(--s-2); }
.pb-immich-connect .ws-field input {
  width: 100%;
  padding: 0.4rem 0.6rem;
  border: 1px solid var(--stroke-strong);
  border-radius: var(--radius-sm, 4px);
  font-size: 0.9rem;
  background: var(--bg-elev-2);
  color: var(--text);
}
.pb-immich-connect small {
  display: block;
  margin-top: 0.2rem;
  font-size: 0.75rem;
  color: var(--text-muted);
}
.pb-immich-connect .btn-primary {
  width: 100%;
  margin-top: var(--s-1);
}

/* Used-photo markers --------------------------------------------------- */
.pb-tray-thumb {
  position: relative;        /* anchor for the count badge */
  border: 2px solid transparent;
  transition: opacity var(--t-fast) var(--ease), border-color var(--t-fast) var(--ease);
}
.pb-tray-thumb.is-used {
  opacity: 0.7;
  border-color: var(--accent, #047857);
}
.pb-tray-thumb.is-used:hover,
.pb-tray-thumb.is-used:focus-visible {
  opacity: 1;
}
.pb-tray-use-badge {
  position: absolute;
  top: 4px;
  right: 4px;
  min-width: 22px;
  height: 22px;
  padding: 0 6px;
  border-radius: 11px;
  background: var(--accent, #047857);
  color: #ffffff;
  font-size: 0.72rem;
  font-weight: 700;
  line-height: 22px;
  text-align: center;
  pointer-events: none;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
}
/* Print-editor tray reuses .pb-tray-thumb (mirror of selectors in
 * print-editor.css world). Keep the rules above scoped to .pb-tray-
 * thumb so they apply in both editors. */

/* Roving-tabindex focus ring sits on top of the standard btn focus. */
.pb-tray-thumb:focus-visible {
  outline: 2px solid var(--accent, #2563eb);
  outline-offset: 2px;
}

/* ============================================================
 * I.7 — WYSIWYG canvas (cover + spreads at actual proportions)
 *
 * REPLACES the legacy form-input + Vorschau split layout. The
 * `.pb-wysiwyg` container holds:
 *
 *   - `.pb-cover-view-toggle` — Frontcover / Flachansicht pill row
 *   - `.pb-cover-stage[data-cover-view]` — either single front panel
 *     OR a back+spine+front flat grid. Aspect driven by `--cover-aspect`
 *     custom property set in inline style on `.pb-wysiwyg`.
 *   - `.pb-spreads-list > .pb-spread > .pb-spread-pages` — flat two-
 *     page surface with a centre `.pb-spread-gutter` overlay.
 *
 * Custom properties on `.pb-wysiwyg`:
 *   --cover-aspect  : "1 / 1" | "3 / 4" | "4 / 3" (from book_size)
 *   --spread-aspect : "2 / 1" | "6 / 4" | "8 / 3"
 *   --spine-mm      : numeric mm (e.g. 2.8) — multiplied by 3px in CSS.
 * ============================================================ */

/* Outer wrapper centres everything in the canvas + clamps width. */
.pb-wysiwyg {
  display: flex;
  flex-direction: column;
  gap: 24px;
  align-items: center;
  padding: 16px 0;
  max-width: 1100px;
  margin: 0 auto;
  width: 100%;
}

/* ---- Cover toggle row ---------------------------------------------- */
.pb-cover-view-toggle {
  display: flex;
  gap: 4px;
  justify-content: center;
  margin-bottom: 4px;
}
.pb-cover-view-tab {
  padding: 8px 16px;
  min-height: 36px; /* keyboard tap target */
  border-radius: 9999px;
  font-size: 0.85rem;
  font-weight: 600;
  background: var(--bg-elev-1);
  color: var(--text-strong, #1a1a1a);
  border: 1px solid var(--stroke-strong);
  cursor: pointer;
  transition: background-color var(--t-fast) var(--ease), color var(--t-fast) var(--ease), border-color var(--t-fast) var(--ease);
}
.pb-cover-view-tab:hover {
  border-color: var(--accent);
}
.pb-cover-view-tab:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.pb-cover-view-tab.is-active {
  background: var(--accent, #047857);
  color: #ffffff;
  border-color: var(--accent, #047857);
}

/* ---- Cover stage --------------------------------------------------- */
.pb-cover {
  width: 100%;
  max-width: 1000px;
  /* Keep stacking order (toggle row on top, stage below). */
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
}
.pb-cover-stage {
  position: relative;
  background: #ffffff;
  box-shadow: 0 8px 28px rgba(0, 0, 0, 0.22);
  display: grid;
  aspect-ratio: var(--cover-aspect, 1 / 1);
  overflow: hidden;
  border-radius: 4px;
  /* Single-axis size so aspect-ratio actually applies. Setting both
   * width AND max-height made the browser pick whichever was smaller
   * and DROP the aspect-ratio — at 1080 viewport that gave a 966×800
   * cover (1.21:1) for a book that should be 1:1. Locking width to
   * the lesser of (parent width, viewport-h reserve) keeps the cover
   * square (or whatever --cover-aspect dictates) at every viewport. */
  width: min(100%, calc(100vh - 280px));
  max-width: 100%;
}
/* Front-only: full-width single front panel. Back + spine hidden. */
.pb-cover-stage[data-cover-view="front"] {
  grid-template-columns: 1fr;
}
.pb-cover-stage[data-cover-view="front"] .pb-cover-back,
.pb-cover-stage[data-cover-view="front"] .pb-cover-spine {
  display: none;
}
/* Flat: back | spine | front. The stage aspect is approximately doubled
 * for the flat layout — but we keep --cover-aspect single-front; the
 * data-attribute switches to the SPREAD aspect instead. */
.pb-cover-stage[data-cover-view="flat"] {
  aspect-ratio: var(--spread-aspect, 2 / 1);
  /* Spine width: --spine-mm × 3px (≈ 6-30px at 28-100 pages). The
   * back + front split the remaining width 1:1. */
  grid-template-columns: 1fr calc(var(--spine-mm, 2) * 3px) 1fr;
}

/* Back cover — I.7.b: editable. The wrapper carries a `data-back-layout`
 * attribute that gates which slots are visible. `back_blank` hides
 * everything; `back_simple` shows the centered note input. */
.pb-cover-back {
  background: linear-gradient(180deg, #f5f5f5 0%, #e5e5e5 100%);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 6%;
}
/* I.20 (2026-05-26) — The original rule hid the back-cover note input
 * entirely whenever data-back-layout="back_blank" (the default). Result:
 * buyers on Flachansicht saw a completely empty gray panel where the
 * back cover should be, with no affordance to add a back note. Keep the
 * back-blank semantic (no decorative graphics) but always show the note
 * input — the buyer can leave it empty if they want, but at least they
 * can SEE the back cover is editable. */
.pb-cover-back-note-wrap {
  width: 100%;
  text-align: center;
}
.pb-cover-back-note-input {
  width: 100%;
  /* Visible by default so the buyer knows the back cover is editable
   * (same affordance pattern as the cover title in I.12). Dashed
   * border + faint scrim renders clearly on the gray back-cover
   * gradient. Hover/focus lift the contrast further. */
  background: rgba(0, 0, 0, 0.04);
  border: 1px dashed rgba(0, 0, 0, 0.25);
  text-align: center;
  font-size: 0.95rem;
  color: #1a1a1a;
  padding: 8px 10px;
  font-style: italic;
  pointer-events: auto;
  font-family: inherit;
  border-radius: 4px;
}
.pb-cover-back-note-input::placeholder {
  color: rgba(0, 0, 0, 0.55);
  opacity: 1;
}
.pb-cover-back-note-input:hover,
.pb-cover-back-note-input:focus {
  border-color: rgba(0, 0, 0, 0.2);
  background: rgba(255, 255, 255, 0.6);
  outline: none;
}
.pb-cover-back-note-input:focus {
  border-color: #1a4d80;
  border-style: solid;
}

/* Spine — vertical bar between back + front. Title is reflected from
 * #cover-title via JS. I.7.b: --spine-bg + --spine-fg are inline-style
 * CSS variables set server-side from the active theme's palette
 * (and updated client-side on theme change). */
.pb-cover-spine {
  background: linear-gradient(180deg,
    var(--spine-bg, #1a4d80) 0%,
    color-mix(in srgb, var(--spine-bg, #1a4d80) 75%, #000) 100%);
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}
.pb-spine-title {
  writing-mode: vertical-rl;
  text-orientation: mixed;
  color: var(--spine-fg, #ffffff);
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  padding: 8px 0;
  white-space: nowrap;
  text-overflow: ellipsis;
  overflow: hidden;
  max-height: 100%;
}

/* Front cover — the editable face. The slot fills the panel; the
 * overlay text sits above it. */
.pb-cover-front {
  position: relative;
  background: #ffffff;
  display: grid;
  grid-template-areas: "cover";
  /* Grid items default to `min-height: auto` which prevents them
   * from being smaller than their content. A portrait cover photo
   * (e.g. 1080×2400 phone screenshot) was pushing this container to
   * ~2140px tall, blowing past .pb-cover-stage's aspect-ratio cap
   * and shoving the title overlay 1200+px below the visible cover.
   * `min-height: 0` lets the grid cell honour the stage's height. */
  min-height: 0;
  min-width: 0;
  overflow: hidden;
}
.pb-cover-front > .pb-slot,
.pb-cover-front > .pb-cover-text-overlay {
  grid-area: cover;
  min-height: 0;
  min-width: 0;
}

/* The slot fills the front-cover panel. Override the legacy pb-slot
 * grid-area + dimensions so the WYSIWYG aspect-ratio takes effect. */
.pb-cover-front > .pb-slot {
  width: 100%;
  height: 100%;
  margin: 0;
  background: #f5f5f5;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: var(--s-3);
  border-radius: 0;
  /* Same min-height: 0 reasoning as the parent — keeps a portrait
   * cover photo from forcing the slot taller than the stage. */
  min-height: 0;
}
.pb-cover-front > .pb-slot.has-photo {
  background: transparent;
  padding: 0;
}

/* Text overlay — inline-editable title + subtitle painted over the
 * lower third of the front cover. White text + subtle shadow so it
 * reads on any photo. Transparent inputs let the photo bleed through.
 *
 * `position: relative; z-index: 2` ensures the overlay paints ABOVE
 * the slot's <img>. The image has a CSS transform (pan/scale) which
 * creates a stacking context, so without an explicit z-index here
 * the photo would cover the title even though DOM order has the
 * overlay after the slot.
 *
 * `align-self` switched from `end` to a smaller bottom inset via
 * padding-bottom — keeps the title clearly inside the cover but
 * lifted above the sticky cart-FAB that anchors the viewport's
 * bottom edge. */
.pb-cover-text-overlay {
  position: relative;
  z-index: 2;
  align-self: end;
  width: 100%;
  padding: 8% 8% 14%;
  display: flex;
  flex-direction: column;
  gap: 4px;
  pointer-events: none; /* let clicks fall through to slot except on inputs */
  background: linear-gradient(180deg, rgba(0,0,0,0) 0%, rgba(0,0,0,0.55) 100%);
}
.pb-cover-front > .pb-slot:not(.has-photo) ~ .pb-cover-text-overlay {
  background: none; /* empty slot already has neutral bg; skip gradient */
}
.pb-cover-text-overlay input {
  pointer-events: auto;
  /* Visible-by-default so the buyer knows the title is editable.
   * Subtle dark scrim + dashed accent border calls out the affordance
   * against any cover photo. Hover/focus lifts the contrast further. */
  background: rgba(0, 0, 0, 0.35);
  border: 1px dashed rgba(255, 255, 255, 0.55);
  color: #ffffff;
  font-family: inherit;
  font-weight: 700;
  text-align: center;
  width: 100%;
  padding: 6px 10px;
  border-radius: 4px;
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.6);
  transition: border-color var(--t-fast) var(--ease), background-color var(--t-fast) var(--ease);
}
.pb-cover-text-overlay input.pb-cover-title-input {
  font-size: clamp(1.1rem, 3vw, 2rem);
}
.pb-cover-text-overlay input.pb-cover-subtitle-input {
  font-size: clamp(0.85rem, 1.8vw, 1.15rem);
  font-weight: 500;
}
.pb-cover-text-overlay input::placeholder {
  color: rgba(255, 255, 255, 0.9);
  font-style: italic;
  opacity: 1;
}
/* When the front slot is EMPTY (no photo), the gradient overlay isn't
 * helpful — drop the white-on-dark trick and use dark text on light. */
.pb-cover-front > .pb-slot:not(.has-photo) ~ .pb-cover-text-overlay input {
  color: var(--text-strong, #1a1a1a);
  text-shadow: none;
}
.pb-cover-front > .pb-slot:not(.has-photo) ~ .pb-cover-text-overlay input::placeholder {
  color: #6b6b6b;
}
.pb-cover-text-overlay input:hover,
.pb-cover-text-overlay input:focus {
  border-color: var(--accent, #047857);
  background: rgba(255, 255, 255, 0.12);
}
.pb-cover-text-overlay input:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* Collapsible render-preview behind <details>. Buyers don't need to
 * see the duplicated rendered cover for the WYSIWYG view; expanding
 * surfaces the final composed PDF-ready render. */
.pb-cover-preview-toggle {
  margin-top: var(--s-3);
}
.pb-cover-preview-toggle summary {
  cursor: pointer;
  padding: 4px 0;
  font-size: 0.9rem;
}
.pb-cover-preview-toggle summary:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.pb-cover-preview-toggle .pb-preview-figure {
  margin: var(--s-2) 0 0;
}
.pb-spread-preview-toggle {
  margin-top: var(--s-3);
}
.pb-spread-preview-toggle summary {
  cursor: pointer;
  padding: 4px 0;
  font-size: 0.9rem;
}

/* ---- Inner spreads ------------------------------------------------- */
.pb-spreads {
  width: 100%;
  max-width: 1000px;
}
.pb-spreads-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 24px;
}
.pb-editor.ws-shell .pb-spread {
  background: #ffffff;
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.18);
  border-radius: 4px;
  overflow: visible;
  padding: 0;
}
/* The page-pair wrapper carries the flat aspect ratio. Slots layer
 * inside it; the gutter is an absolutely-positioned overlay running
 * down the centre seam. */
.pb-spread-pages {
  position: relative;
  width: 100%;
  aspect-ratio: var(--spread-aspect, 2 / 1);
  background: #ffffff;
  overflow: hidden;
}
.pb-spread-pages .pb-slots {
  position: absolute;
  inset: 0;
  display: grid;
  gap: 0;
  /* Default: 1 slot fills the spread. Per-layout overrides below
   * via [data-layout] (set by renderSpreads). I.20 found that the
   * "grid auto-flow" assumption was creating a single column for
   * everything, so "2 Fotos (nebeneinander)" rendered stacked. */
  grid-template-columns: 1fr;
  grid-template-rows: 1fr;
}
/* page_single + page_single_caption: 1 slot fills the spread (default rules above). */

/* page_double_h: 2 slots SIDE BY SIDE. */
.pb-spread-pages .pb-slots[data-layout="page_double_h"] {
  grid-template-columns: 1fr 1fr;
  grid-template-rows: 1fr;
}

/* page_double_v: 2 slots stacked top/bottom. */
.pb-spread-pages .pb-slots[data-layout="page_double_v"] {
  grid-template-columns: 1fr;
  grid-template-rows: 1fr 1fr;
}

/* page_quad: 2×2 grid. */
.pb-spread-pages .pb-slots[data-layout="page_quad"] {
  grid-template-columns: 1fr 1fr;
  grid-template-rows: 1fr 1fr;
}

/* spread_full: panorama — 1 slot fills entire spread (same as default). */
.pb-spread-pages .pb-slots[data-layout="spread_full"] {
  grid-template-columns: 1fr;
  grid-template-rows: 1fr;
}
/* Hairline gutter — purely decorative, sits behind the slots with
 * pointer-events: none so clicks pass through. */
.pb-spread-gutter {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 50%;
  width: 24px;
  transform: translateX(-50%);
  background: linear-gradient(90deg,
    rgba(0, 0, 0, 0) 0%,
    rgba(0, 0, 0, 0.18) 35%,
    rgba(0, 0, 0, 0.28) 50%,
    rgba(0, 0, 0, 0.18) 65%,
    rgba(0, 0, 0, 0) 100%);
  pointer-events: none;
  z-index: 1;
}
@media (prefers-contrast: more) {
  .pb-spread-gutter {
    background: linear-gradient(90deg,
      rgba(0, 0, 0, 0) 0%,
      rgba(0, 0, 0, 0.5) 50%,
      rgba(0, 0, 0, 0) 100%);
  }
}

/* Spread header: keep visible but tighter. The per-spread layout
 * select still hangs off the header (so a buyer can change just
 * one spread's layout). */
.pb-spread-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 8px 12px;
  background: var(--bg-elev-1);
  border-bottom: 1px solid var(--stroke-soft);
  border-radius: 4px 4px 0 0;
}
.pb-spread-num {
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--text-muted);
}
.pb-spread-layout {
  min-height: 32px;
  font-size: 0.85rem;
  padding: 4px 8px;
  background: var(--bg-base);
  border: 1px solid var(--stroke-strong);
  border-radius: 4px;
  color: var(--text-strong, #1a1a1a);
}

/* Mobile collapses to single-spread-at-a-time via the existing
 * H.4.b nav (rules already in this file). The WYSIWYG container
 * width still tracks the viewport on small screens. */
@media (max-width: 768px) {
  .pb-wysiwyg {
    gap: 16px;
    padding: 8px 0;
  }
  .pb-cover-text-overlay {
    padding: 8% 6% 6%;
  }
}

/* ============================================================
 * I.66 — Single-CTA landing hero + in-editor Format panel.
 *
 * Landing replaces the old radio + slider + theme-grid form with
 * a hero block: title, lede, single primary CTA, optional "Meine
 * Entwürfe" link, and a feature-grid below. Mirrors the way POD
 * lands buyers straight in the editor.
 *
 * Format panel reuses .ts-pills / .ts-pill from print-editor.css
 * (already loaded by edit.html). The two rules below are local
 * polish — multi-line pill labels (size in mm under the inch
 * label) and the quote-line block in the footer.
 * ============================================================ */

.pb-landing-hero {
  max-width: 720px;
  margin: 0 auto;
  padding: 48px 24px 64px;
  text-align: center;
}
.pb-landing-hero .pb-hero-header { margin-bottom: 32px; }
.pb-landing-hero h1 {
  font-size: clamp(1.8rem, 4vw, 2.6rem);
  margin: 0 0 12px;
  line-height: 1.15;
}
.pb-landing-hero .lede {
  font-size: 1.1rem;
  max-width: 560px;
  margin: 0 auto 16px;
}
.pb-hero-defaults {
  font-size: 0.95rem;
  max-width: 560px;
  margin: 0 auto;
}
.pb-hero-cta-row {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  justify-content: center;
  margin-bottom: 16px;
}
.pb-hero-cta {
  font-size: 1.15rem;
  padding: 16px 32px;
  display: inline-flex;
  align-items: center;
  gap: 12px;
  /* AAA-compliant focus halo for keyboard users — matches storefront
   * tokens (--stroke-strong already 4.5:1+ against any surface). */
}
.pb-hero-cta:focus-visible {
  outline: 3px solid var(--stroke-strong, #1a1a1a);
  outline-offset: 3px;
}
.pb-hero-cta-arrow {
  font-size: 1.2em;
  transition: transform 150ms ease;
}
.pb-hero-cta:hover .pb-hero-cta-arrow,
.pb-hero-cta:focus-visible .pb-hero-cta-arrow {
  transform: translateX(4px);
}
.pb-hero-cta-secondary { align-self: center; }
.pb-hero-status {
  min-height: 1.5em;
  margin: 8px 0 32px;
}
.pb-hero-status.error { color: var(--status-error, #c0392b); }
.pb-hero-status.success { color: var(--status-success, #1d8348); }
.pb-hero-features {
  margin-top: 40px;
  border-top: 1px solid var(--stroke-subtle, rgba(255,255,255,0.08));
  padding-top: 32px;
}
.pb-hero-feature-list {
  list-style: none;
  padding: 0;
  margin: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 16px;
  text-align: left;
}
.pb-hero-feature-list li {
  padding: 12px 16px;
  background: var(--bg-elev-1, rgba(255,255,255,0.04));
  border-radius: 8px;
  font-size: 0.95rem;
}

/* ---- Format panel (in-editor variant picker) ---------------------- */
/* .ts-pill comes from print-editor.css — make the inner two-line label
 * read cleanly when stacked. */
.ts-axis-book-size .ts-pill span small,
.ts-axis-page-count .ts-pill span small {
  display: block;
  font-size: 0.8em;
  margin-top: 2px;
  font-weight: normal;
}
.ws-format-quote-line {
  margin: 8px 0 4px;
  font-size: 1rem;
}
.ws-format-quote-line strong { margin-right: 6px; }
.ws-format-status {
  min-height: 1.2em;
  font-size: 0.9rem;
}
.ws-format-status.error { color: var(--status-error, #c0392b); }
.ws-format-status.success { color: var(--status-success, #1d8348); }

/* ============================================================================
   I.93.c — Photo Book theme carousel (landing page)
   Surfaces 8-12 theme thumbnails above the fold so the "20+ Themen" bullet
   isn't text-only. Horizontal scroll on mobile, grid on desktop.
   Sits inside the .pb-landing-hero article (max-width 720px) — we break
   out via negative margins on desktop so the grid gets the full width.
   ========================================================================== */
.pb-landing-hero .pb-theme-carousel {
  /* Break out of the 720px parent on desktop so 4-6 thumbs fit per row */
  margin-left: calc(50% - 50vw + 24px);
  margin-right: calc(50% - 50vw + 24px);
  max-width: none;
}
.pb-theme-carousel {
  margin: 32px auto 48px;
  max-width: 1200px;
  padding: 0 20px;
}
.pb-theme-carousel-head {
  margin-bottom: 20px;
  text-align: center;
}
.pb-theme-carousel-head h2 {
  margin: 0 0 6px;
  font-size: 1.5rem;
  font-weight: 700;
}
.pb-theme-carousel-head .muted {
  margin: 0;
  font-size: 0.95rem;
}
.pb-theme-thumbs {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
  gap: 16px;
  margin: 0;
  padding: 0;
  list-style: none;
}
.pb-theme-thumb { margin: 0; }
.pb-theme-link {
  display: flex;
  flex-direction: column;
  text-decoration: none;
  color: inherit;
  background: var(--bg-elev-1, #161b22);
  border: 1px solid var(--stroke-soft, rgba(255,255,255,0.08));
  border-radius: 12px;
  overflow: hidden;
  transition: transform 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease;
}
.pb-theme-link:hover,
.pb-theme-link:focus-visible {
  transform: translateY(-2px);
  border-color: var(--accent, #5eead4);
  box-shadow: 0 6px 20px rgba(0,0,0,0.35);
  outline: none;
}
.pb-theme-link img {
  width: 100%;
  aspect-ratio: 240 / 320;
  object-fit: cover;
  display: block;
  background: var(--bg-elev-2, #1c2230);
}
.pb-theme-name {
  display: block;
  padding: 10px 12px 4px;
  font-weight: 600;
  font-size: 0.95rem;
  line-height: 1.3;
}
.pb-theme-cta {
  display: block;
  padding: 0 12px 10px;
  font-size: 0.825rem;
  color: var(--text-muted, rgba(255,255,255,0.7));
  font-weight: 500;
}
/* Mobile: horizontal scroll snap row so all thumbs are reachable
   without the grid taking ages of vertical space. */
@media (max-width: 640px) {
  .pb-theme-thumbs {
    display: flex;
    overflow-x: auto;
    overflow-y: hidden;
    scroll-snap-type: x mandatory;
    gap: 12px;
    padding-bottom: 8px;
    -webkit-overflow-scrolling: touch;
  }
  .pb-theme-thumb {
    flex: 0 0 60vw;
    max-width: 240px;
    scroll-snap-align: start;
  }
}
