/* ══════════════════════════════════════════════
   Book Settings Panel (single card)
   ══════════════════════════════════════════════ */

.settings-panel {
  background: var(--bg-elevated);
  border: 1px solid var(--border-default);
  border-radius: var(--radius-md);
  padding: var(--space-md) var(--space-lg);
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
}

.settings-panel-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-md);
  align-items: flex-end;
}

.settings-panel-row .settings-field {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
  flex: 1 1 140px;
  min-width: 0;
}

.settings-panel-row--secondary {
  align-items: center;
  gap: var(--space-lg);
  padding-top: var(--space-sm);
  border-top: 1px solid var(--border-default);
}

.settings-panel .settings-label {
  font-size: 0.8125rem;
  font-weight: 500;
  color: var(--text-secondary);
  white-space: nowrap;
}

.settings-panel .settings-select {
  width: 100%;
  padding: 6px var(--space-sm);
  border: 1px solid var(--border-default);
  border-radius: var(--radius-sm);
  font-size: 0.875rem;
  background: var(--bg-primary);
  color: var(--text-primary);
  transition: border-color var(--transition-fast);
}

.settings-panel .settings-select:focus {
  outline: none;
  border-color: var(--border-focus);
  box-shadow: var(--shadow-focus);
}

.settings-panel .checkbox-option {
  border: none;
  padding: var(--space-xs) 0;
  border-radius: 0;
  white-space: nowrap;
}

.settings-panel .checkbox-option:hover {
  background: transparent;
}

.settings-panel-album {
  font-size: 0.9rem;
  color: var(--text-primary);
  line-height: 1.5;
  margin-left: auto;
  white-space: nowrap;
}

.settings-panel-album a {
  color: var(--color-primary);
  text-decoration: underline;
  text-underline-offset: 2px;
}

.settings-panel-album a:hover {
  color: var(--color-primary-hover);
}

.settings-panel-photo-count {
  color: var(--text-muted);
  font-size: 0.8125rem;
}

/* Bold/Italic style toggle buttons in settings panel */
.settings-style-toggles {
  display: flex;
  gap: 4px;
  align-items: center;
}

.style-toggle-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  border: 1px solid var(--color-border, #e7e5e4);
  border-radius: var(--radius-sm, 4px);
  background: var(--color-surface, #fff);
  color: var(--text-muted);
  cursor: pointer;
  transition: background var(--transition-fast), color var(--transition-fast), border-color var(--transition-fast);
}

.style-toggle-btn:hover {
  background: var(--bg-tertiary);
  color: var(--text-primary);
  border-color: var(--color-border, #e7e5e4);
}

.style-toggle-btn.active {
  background: var(--color-primary);
  color: var(--text-inverse);
  border-color: var(--color-primary);
}

@media (max-width: 600px) {
  .settings-panel {
    padding: var(--space-sm) var(--space-md);
  }
  .settings-panel-row .settings-field {
    flex: 1 1 100%;
  }
  .settings-panel-row--secondary {
    flex-direction: column;
    align-items: flex-start;
    gap: var(--space-sm);
  }
  .settings-panel-album {
    margin-left: 0;
  }
}

/* ══════════════════════════════════════════════
   Inline Preview (within Book page)
   ══════════════════════════════════════════════ */

/* Bundled handwriting font for captions */
@font-face {
  font-family: 'Caveat';
  src: url('/static/fonts/Caveat-Regular.ttf') format('truetype');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

/* Liberation fonts — same TTFs we embed in the printed PDF
 * (internal/pdfgen/fonts.go) so the editor's caption / textbox rendering
 * is byte-for-byte the same as what RPI prints. The fpdf side rejects
 * the base14 Helvetica/Times/Courier (RPI preflight wants them
 * embedded), and OS-system Helvetica varies per machine, so we ship
 * one identical glyph source for both contexts. */
@font-face {
  font-family: 'LiberationSans';
  src: url('/static/fonts/LiberationSans-Regular.ttf') format('truetype');
  font-weight: 400; font-style: normal; font-display: swap;
}
@font-face {
  font-family: 'LiberationSans';
  src: url('/static/fonts/LiberationSans-Bold.ttf') format('truetype');
  font-weight: 700; font-style: normal; font-display: swap;
}
@font-face {
  font-family: 'LiberationSans';
  src: url('/static/fonts/LiberationSans-Italic.ttf') format('truetype');
  font-weight: 400; font-style: italic; font-display: swap;
}
@font-face {
  font-family: 'LiberationSans';
  src: url('/static/fonts/LiberationSans-BoldItalic.ttf') format('truetype');
  font-weight: 700; font-style: italic; font-display: swap;
}
@font-face {
  font-family: 'LiberationSerif';
  src: url('/static/fonts/LiberationSerif-Regular.ttf') format('truetype');
  font-weight: 400; font-style: normal; font-display: swap;
}
@font-face {
  font-family: 'LiberationSerif';
  src: url('/static/fonts/LiberationSerif-Bold.ttf') format('truetype');
  font-weight: 700; font-style: normal; font-display: swap;
}
@font-face {
  font-family: 'LiberationSerif';
  src: url('/static/fonts/LiberationSerif-Italic.ttf') format('truetype');
  font-weight: 400; font-style: italic; font-display: swap;
}
@font-face {
  font-family: 'LiberationSerif';
  src: url('/static/fonts/LiberationSerif-BoldItalic.ttf') format('truetype');
  font-weight: 700; font-style: italic; font-display: swap;
}
@font-face {
  font-family: 'LiberationMono';
  src: url('/static/fonts/LiberationMono-Regular.ttf') format('truetype');
  font-weight: 400; font-style: normal; font-display: swap;
}
@font-face {
  font-family: 'LiberationMono';
  src: url('/static/fonts/LiberationMono-Bold.ttf') format('truetype');
  font-weight: 700; font-style: normal; font-display: swap;
}
@font-face {
  font-family: 'LiberationMono';
  src: url('/static/fonts/LiberationMono-Italic.ttf') format('truetype');
  font-weight: 400; font-style: italic; font-display: swap;
}
@font-face {
  font-family: 'LiberationMono';
  src: url('/static/fonts/LiberationMono-BoldItalic.ttf') format('truetype');
  font-weight: 700; font-style: italic; font-display: swap;
}

.inline-preview-section.hidden {
  display: none;
}

.section-divider {
  border: none;
  border-top: 1px solid var(--color-border);
  margin: var(--space-xl) 0;
}

.inline-preview-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-md);
  margin-bottom: var(--space-lg);
}

.inline-preview-header h2 {
  font-size: 1.4rem;
  margin: 0;
}

.inline-preview-actions {
  display: flex;
  gap: var(--space-sm);
  align-items: center;
}

/* Override main max-width when inline preview is visible.
   JS sets data-preview-size on <main> so we don't rely on :has(). */
main[data-preview-size] {
  max-width: none;
}
/* :has() fallback for browsers where JS hasn't run yet */
main:has(.inline-preview-section:not(.hidden)) {
  max-width: none;
}

/* Keep non-preview content (header, settings, generate controls) contained
   to the normal page width so only the preview area can expand. */
main[data-preview-size] .album-detail {
  max-width: var(--max-width);
  margin: 0 auto;
}
main:has(.inline-preview-section:not(.hidden)) .album-detail {
  max-width: var(--max-width);
  margin: 0 auto;
}

/* On desktop the preview section always breaks out to 80vw, centered.
   View size only controls column count, not overall width. */
@media (min-width: 769px) {
  .inline-preview-section {
    width: 80vw;
    max-width: 80vw;
    margin-left: calc(-40vw + 50%);
    padding: 0 2vw;
    box-sizing: border-box;
  }
}

/* ══════════════════════════════════════════════
   Preview & Edit Page
   ══════════════════════════════════════════════ */

/* Override main max-width on preview pages */
main:has(.album-preview) {
  max-width: none;
}

.album-preview {
  max-width: 1900px;
  margin: 0 auto;
}

.preview-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-md);
  margin-bottom: var(--space-xl);
}

.preview-header h1 {
  font-size: 1.5rem;
  margin: 0;
}

.preview-actions {
  display: flex;
  gap: var(--space-sm);
  align-items: center;
}

#export-status {
  margin-bottom: var(--space-md);
}

.export-msg {
  padding: var(--space-sm) var(--space-md);
  border-radius: var(--radius-md);
  font-size: 0.9rem;
}

.export-msg.success {
  background: color-mix(in srgb, var(--color-success) 12%, transparent);
  color: var(--color-success);
}

.export-msg.error {
  background: color-mix(in srgb, var(--color-danger) 12%, transparent);
  color: var(--color-danger);
}

/* View size toggle buttons */
.view-size-controls {
  display: flex;
  align-items: center;
  gap: 8px;
}


.view-size-buttons {
  display: inline-flex;
  gap: 2px;
  padding: 3px;
  background: var(--bg-tertiary);
  border: 1px solid var(--border-default);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-sm);
}

.view-size-btn {
  padding: 5px 7px;
  border: 1px solid transparent;
  border-radius: calc(var(--radius-md) - 2px);
  background: transparent;
  color: var(--text-secondary);
  cursor: pointer;
  font-size: 0;
  line-height: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition: background var(--transition-fast), color var(--transition-fast), border-color var(--transition-fast), box-shadow var(--transition-fast);
}

.view-size-btn svg {
  display: block;
  width: 20px;
  height: 16px;
}

.view-size-btn.active {
  background: var(--bg-elevated);
  border-color: var(--border-default);
  color: var(--color-primary);
  box-shadow: var(--shadow-sm);
}

.view-size-btn:hover:not(.active) {
  background: var(--bg-elevated);
  border-color: var(--border-hover);
  color: var(--text-primary);
}

.view-size-btn:focus-visible {
  outline: none;
  box-shadow: var(--shadow-focus);
  z-index: 1;
  position: relative;
}


/* Page cards grid — fixed column counts per view size on desktop.
   Mobile override (single column) is applied in the @media (max-width: 768px) block below. */
.preview-container {
  display: grid;
  grid-template-columns: repeat(2, 1fr); /* default: lg (2 per row) */
  column-gap: 2px;
  row-gap: var(--space-xl);
  /* Contain the editor's internal z-index stack (slot toolbars, print
     guides, textbox handles up to ~10001) so the fixed site header
     (z-index --z-sticky) always paints above the editor while scrolling. */
  isolation: isolate;
}

/* Desktop grid columns by view size — L: 2, XL: 1 */

.preview-container[data-view-size="lg"] {
  grid-template-columns: repeat(2, 1fr);
  column-gap: 2px;
  row-gap: var(--space-xl);
}

.preview-container[data-view-size="xl"] {
  grid-template-columns: repeat(1, 1fr);
  gap: 40px;
}

.preview-page-wrapper {
  max-width: 600px;
  width: 100%;
  margin: 0 auto;
}

.preview-container[data-view-size="lg"] .preview-page-wrapper {
  max-width: 800px;
}

/* Spread layout: left-column pages push right, right-column pages push left.
   The cover wrapper spans both columns (grid-column: 1 / -1) and shifts
   simple nth-child parity.  Using "of S" filtering excludes the cover from
   the count so odd = column 1 and even = column 2 regardless of whether a
   cover is present. */
.preview-container[data-view-size="lg"] .preview-page-wrapper:nth-child(odd of .preview-page-wrapper:not(.preview-cover-wrapper)) {
  margin-left: auto;
  margin-right: 0;
  align-items: flex-end;
}

.preview-container[data-view-size="lg"] .preview-page-wrapper:nth-child(even of .preview-page-wrapper:not(.preview-cover-wrapper)) {
  margin-left: 0;
  margin-right: auto;
  align-items: flex-start;
}

.preview-container[data-view-size="xl"] .preview-page-wrapper {
  max-width: none;
  align-items: center;
}

/* Cap page height for all view sizes so pages don't get absurdly tall (especially XL at 80vw). */
.preview-container[data-view-size] .preview-page:not(.preview-cover-page),
.preview-container[data-view-size] .add-page-card {
  max-height: 80vh;
  width: 100%;
  max-width: calc(80vh * var(--page-aspect-ratio, 0.707));
}

/* Align page header with the page: match the page's max-width so the delete icon
   aligns to the right edge of the page, not the grid cell, even when align-items: center
   causes the page to be narrower than its wrapper. Must mirror the page's max-width
   formula exactly: calc(80vh * var(--page-aspect-ratio, 0.707)). */
.preview-container[data-view-size] .preview-page-wrapper:not(.preview-cover-wrapper) .preview-page-header,
.preview-container[data-view-size] .preview-page-wrapper:not(.preview-cover-wrapper) .template-picker {
  width: 100%;
  max-width: calc(80vh * var(--page-aspect-ratio, 0.707));
}

/* Individual page card */
.preview-page-wrapper {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-start;
  gap: var(--space-sm);
}

.preview-page-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--space-sm);
}

.preview-page-label {
  font-weight: 600;
  font-size: 0.85rem;
  color: var(--text-secondary);
  text-align: left;
}

/* Remove page button */
.remove-page-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  border: 1px solid var(--border-default);
  border-radius: var(--radius-sm);
  background: var(--bg-primary);
  color: var(--text-muted);
  font-size: 0.85rem;
  cursor: pointer;
  opacity: 0;
  transition: opacity var(--transition-fast), color var(--transition-fast), border-color var(--transition-fast), background var(--transition-fast);
}

.preview-page-wrapper:hover .remove-page-btn {
  opacity: 1;
}

.remove-page-btn:hover {
  color: var(--color-danger);
  border-color: var(--color-danger);
  background: color-mix(in srgb, var(--color-danger) 8%, var(--bg-primary));
}

.remove-page-btn:focus-visible {
  opacity: 1;
  outline: none;
  border-color: var(--border-focus);
  box-shadow: var(--shadow-focus);
}

/* ── Per-page Toolbar ── */

.page-toolbar {
  display: flex;
  align-items: center;
  gap: 4px;
  opacity: 0;
  transition: opacity var(--transition-fast);
}

.preview-page-wrapper:hover .page-toolbar {
  opacity: 1;
}

/* Cover mode toggle — lives in the cover page header alongside the
 * toolbar. Visible without hover so the two modes are discoverable. */
.cover-mode-toggle {
  font-size: 12px;
  padding: 4px 10px;
  margin-left: 8px;
}

.page-toolbar-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  border: 1px solid var(--border-default);
  border-radius: var(--radius-sm);
  background: var(--bg-primary);
  color: var(--text-secondary);
  cursor: pointer;
  padding: 0;
  min-width: 0;
  min-height: 0;
  transition: color var(--transition-fast), border-color var(--transition-fast), background var(--transition-fast);
}

.page-toolbar-btn:hover {
  color: var(--color-primary);
  border-color: var(--color-primary);
  background: var(--color-primary-subtle);
}

.page-toolbar-btn:focus-visible {
  outline: none;
  border-color: var(--border-focus);
  box-shadow: var(--shadow-focus);
}

.page-toolbar-btn svg {
  width: 14px;
  height: 14px;
  pointer-events: none;
}

.page-toolbar-btn-danger:hover {
  color: var(--color-danger);
  border-color: var(--color-danger);
  background: color-mix(in srgb, var(--color-danger) 8%, var(--bg-primary));
}

/* ── Visual Template Picker ── */

.template-picker {
  display: flex;
  gap: var(--space-sm);
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  padding: var(--space-xs) 0;
}

.template-icon {
  width: 48px;
  height: 36px;
  cursor: pointer;
  border: 2px solid var(--border-default);
  border-radius: var(--radius-sm);
  padding: 2px;
  background: var(--bg-primary);
  transition: border-color var(--transition-fast), box-shadow var(--transition-fast), background var(--transition-fast);
}

.template-icon--portrait {
  width: 36px;
  height: 48px;
}

.template-icon--square {
  width: 44px;
  height: 44px;
}

.template-icon rect {
  fill: var(--bg-inset);
  stroke: var(--text-muted);
  stroke-width: 0.5;
  transition: fill var(--transition-fast), stroke var(--transition-fast);
}

.template-icon:hover {
  border-color: var(--color-primary);
  box-shadow: var(--shadow-sm);
}

.template-icon:hover rect {
  fill: color-mix(in srgb, var(--color-primary) 15%, var(--bg-inset));
  stroke: var(--text-secondary);
}

.template-icon:focus-visible {
  outline: none;
  border-color: var(--border-focus);
  box-shadow: var(--shadow-focus);
}

.template-icon.active {
  border-color: var(--color-primary);
  background: color-mix(in srgb, var(--color-primary) 8%, var(--bg-primary));
  box-shadow: 0 0 0 1px var(--color-primary);
}

.template-icon.active rect {
  fill: color-mix(in srgb, var(--color-primary) 20%, var(--bg-inset));
  stroke: var(--color-primary);
  stroke-width: 0.8;
}

/* Custom layout indicator icon */
.template-icon-custom {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 48px;
  height: 36px;
  border: 2px solid var(--border-default);
  border-radius: var(--radius-sm);
  padding: 2px;
  background: var(--bg-primary);
  font-size: 0.55rem;
  font-weight: 600;
  color: var(--text-muted);
  cursor: default;
  user-select: none;
  transition: border-color var(--transition-fast), box-shadow var(--transition-fast), background var(--transition-fast), color var(--transition-fast);
  opacity: 0.5;
  overflow: hidden;
}

.template-icon-custom span {
  display: inline-block;
  transform: rotate(-45deg);
}

.template-icon-custom--portrait {
  width: 36px;
  height: 48px;
}

.template-icon-custom--square {
  width: 44px;
  height: 44px;
}

.template-icon-custom.active {
  border-color: var(--color-primary);
  background: color-mix(in srgb, var(--color-primary) 8%, var(--bg-primary));
  box-shadow: 0 0 0 1px var(--color-primary);
  color: var(--color-primary);
  opacity: 1;
}

/* The page itself */
.preview-page {
  position: relative;
  background: var(--bg-elevated);
  border: 1px solid var(--border-default);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-md);
  overflow: hidden;
  /* aspect-ratio set via inline style */
}

/* Page-number boxes used to render through this rule when page numbers
   were a hardcoded bottom-right numeral. They now render through
   .preview-textbox.preview-page-number (see the textbox section below)
   driven by the shared PageNumberStyle on settings. The old rule's
   pointer-events:none + low z-index + hardcoded bottom/right insets
   silently clobbered the new live element — so the rule is gone. */

.preview-page-content {
  position: absolute;
  /* No z-index: avoid creating a stacking context so that child
     toolbars can render above sibling fade overlays (z-index 101). */
  /* insets set via inline style for margin */
}

/* Photo slots */
.preview-slot {
  position: absolute;
  /* z-index set dynamically by preview.js based on row position */
  display: flex;
  flex-direction: column;
  border: 2px dashed var(--border-default);
  border-radius: var(--radius-sm);
  overflow: visible;
  cursor: grab;
  transition: border-color var(--transition-fast), box-shadow var(--transition-fast), opacity var(--transition-fast);
  background: transparent;
  padding-bottom: 0;
  /* Establish a size container so child handles (resize, move, delete)
     can scale with the slot's actual rendered dimensions via cqmin
     units instead of staying pinned to fixed px. Tiny slots no longer
     get giant grips that swallow the slot. */
  container-type: size;
}

/* White-margin warning: slots that are nearly-flush with the trim edge but
 * fall short of it would print with a thin white sliver after the cut. An
 * amber dashed inset outline flags this at a glance; see preview.js
 * (margin-warn-*) and PR #16 for the rationale. Lives behind the slot's
 * normal border so hover/select/drag states still override it. The title
 * attribute on the slot explains the cue and how to fix it. */
.preview-slot.margin-warn-left,
.preview-slot.margin-warn-top,
.preview-slot.margin-warn-right,
.preview-slot.margin-warn-bottom {
  outline: 2px dashed rgba(217, 119, 6, 0.85);
  outline-offset: -4px;
}
[data-theme="dark"] .preview-slot.margin-warn-left,
[data-theme="dark"] .preview-slot.margin-warn-top,
[data-theme="dark"] .preview-slot.margin-warn-right,
[data-theme="dark"] .preview-slot.margin-warn-bottom {
  outline-color: rgba(251, 191, 36, 0.85);
}

/* Safe-zone warning: applied to a slot whose caption strip extends past
 * the asymmetric safe zone (the pink-overlay area). The amber dashed
 * outline matches margin-warn-* — both are "this will print poorly"
 * cues. The title attribute on the slot explains the specific risk. */
.preview-slot.text-warn-outside-safe {
  outline: 2px dashed rgba(217, 119, 6, 0.85);
  outline-offset: -4px;
}
[data-theme="dark"] .preview-slot.text-warn-outside-safe {
  outline-color: rgba(251, 191, 36, 0.85);
}

/* Textbox version of the safe-zone warning: flip the dashed border from
 * the default blue to amber so the boundary itself signals the risk.
 * Use !important to outrank the hover/active/dragging border-color
 * rules below — when a user is dragging a textbox past the safe-zone
 * edge, the amber must keep showing, not get swapped back to blue. */
.preview-textbox.text-warn-outside-safe {
  border-color: rgba(217, 119, 6, 0.95) !important;
}
[data-theme="dark"] .preview-textbox.text-warn-outside-safe {
  border-color: rgba(251, 191, 36, 0.95) !important;
}

/* Boost slot above fade overlays (z-index 101) when its caption toolbar is open */
.preview-slot.slot-toolbar-active {
  z-index: 200;
}

.slot-image-container {
  position: relative;
  flex: 1 1 0;
  min-height: 0;
  overflow: hidden;
  border-radius: inherit;
  background: var(--bg-tertiary);
}

/* Flag any photo placed in more than one slot. Top-center is the only
   slot corner the slot toolbar doesn't already use — move-grip (TL),
   delete (TR), crop (BL), caption-toggle (BC) — so the badge stays
   visible even when hover reveals all the other controls. */
.slot-usage-badge {
  position: absolute;
  top: 4px;
  left: 50%;
  transform: translateX(-50%);
  background: rgba(230, 126, 34, 0.92);
  color: #fff;
  font-size: 10px;
  font-weight: 600;
  padding: 2px 6px;
  border-radius: 3px;
  pointer-events: none;
  line-height: 1.2;
  z-index: 16;
  white-space: nowrap;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.25);
}

.preview-slot:hover {
  border-color: var(--color-primary);
  box-shadow: var(--shadow-sm);
}

.preview-slot img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 50% 50%;
  display: block;
  user-select: none;
  -webkit-user-drag: none;
  border-radius: inherit;
  clip-path: inset(0);
}

.preview-slot img.panning {
  cursor: grabbing;
}

/* Crop button — positioned in slot corner */
.crop-btn {
  position: absolute;
  bottom: 3px;
  left: 3px;
  z-index: 3;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--border-on-dark);
  border-radius: 50%;
  background: var(--overlay-darker);
  color: var(--icon-on-dark);
  font-weight: 400;
  line-height: 1;
  cursor: pointer;
  opacity: 0;
  transition: opacity var(--transition-fast), background var(--transition-fast), border-color var(--transition-fast);
  padding: 0;
  width: 28px;
  height: 28px;
}

.crop-btn svg {
  width: 16px;
  height: 16px;
}

.preview-slot:hover .crop-btn {
  opacity: 1;
}

.crop-btn:hover {
  background: var(--overlay-heavy);
  border-color: var(--border-on-dark-strong);
}

.crop-btn:focus-visible {
  opacity: 1;
  outline: none;
  box-shadow: 0 0 0 2px var(--color-primary);
}

/* Slot delete button — same clamp(...) scaling as .resize-grip,
   tighter floor since the delete is the smallest control by spec. */
.slot-delete-btn {
  position: absolute;
  top: -6px;
  right: -6px;
  z-index: 10;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: clamp(18px, 20cqmin, 32px);
  height: clamp(18px, 20cqmin, 32px);
  min-width: unset;
  min-height: unset;
  border-radius: 50%;
  background: var(--overlay-darker);
  color: var(--icon-on-dark);
  font-size: clamp(10px, 11cqmin, 17px);
  font-weight: bold;
  line-height: 1;
  cursor: pointer;
  border: 1px solid var(--border-on-dark);
  padding: 0;
  transition: opacity var(--transition-fast), transform var(--transition-fast), background var(--transition-fast), border-color var(--transition-fast);
}

/* Crop button positioned at top-left to avoid overlap with delete button at top-right */
.preview-slot .crop-btn {
  left: 3px;
}

.preview-slot:hover .slot-delete-btn {
  opacity: 1;
}

.slot-delete-btn:hover {
  background: var(--color-danger-solid);
  border-color: var(--border-on-dark-strong);
  transform: scale(1.08);
  opacity: 1;
}

.slot-delete-btn:focus-visible {
  opacity: 1;
  outline: none;
  box-shadow: 0 0 0 2px var(--color-danger, #dc2626);
}

.preview-slot.crop-mode .slot-delete-btn {
  display: none;
}

/* Caption toggle button — appears on hover, bottom-center of slot */
.slot-caption-toggle {
  position: absolute;
  bottom: 4px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 10;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: auto;
  height: 24px;
  min-width: unset;
  min-height: unset;
  border-radius: 12px;
  background: var(--overlay-darker);
  color: var(--icon-on-dark);
  font-size: 10px;
  font-weight: bold;
  line-height: 1;
  cursor: pointer;
  border: 1px solid var(--border-on-dark);
  padding: 2px 12px;
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--transition-fast), transform var(--transition-fast), background var(--transition-fast), border-color var(--transition-fast);
}

.preview-slot:hover .slot-caption-toggle {
  opacity: 1;
  pointer-events: auto;
}

.slot-caption-toggle:hover {
  background: var(--color-primary);
  border-color: var(--color-primary);
  transform: translateX(-50%) scale(1.08);
  opacity: 1;
}

.slot-caption-toggle.active {
  background: var(--color-primary);
  border-color: var(--color-primary);
}

.slot-caption-toggle:focus-visible {
  opacity: 1;
  outline: none;
  box-shadow: 0 0 0 2px var(--color-primary);
}

.preview-slot.crop-mode .slot-caption-toggle {
  display: none;
}

/* Active crop mode indicator */
.preview-slot.crop-mode {
  border-color: var(--color-primary);
  border-style: solid;
  box-shadow: 0 0 0 3px var(--color-primary-glow), 0 0 12px var(--color-primary-muted);
  cursor: default;
  z-index: 5;
}

.preview-slot.crop-mode .crop-btn {
  opacity: 1;
  background: var(--color-primary);
  border-color: var(--color-primary);
}

.preview-slot.crop-mode img {
  cursor: grab;
}

.preview-slot.crop-mode img.panning {
  cursor: grabbing;
}

/* Crop label shown in crop mode */
.preview-slot.dragging {
  opacity: 0.4;
  border-color: var(--text-muted);
}

.preview-slot.drag-over {
  border-color: var(--color-primary);
  border-style: solid;
  box-shadow: 0 0 0 3px var(--color-primary-ring);
  background: color-mix(in srgb, var(--color-primary) 8%, var(--bg-tertiary));
}

/* ── Resize handles ── */

/* ── Resize Grip (bottom-right corner) ── */
/* Width/height + inner SVG use clamp(MIN, Ncqmin, MAX) so the grip
   shrinks proportionally on small slots (cqmin = % of the host's
   min dimension via .preview-slot's container-type:size) but never
   below a mouse-clickable floor. The .preview-container[data-view-
   size="lg"|"xl"] rules below bump the MAX cap when the editor is
   zoomed in. */
.resize-grip {
  position: absolute;
  bottom: 4px;
  right: 4px;
  width: clamp(20px, 25cqmin, 44px);
  height: clamp(20px, 25cqmin, 44px);
  z-index: 15;
  cursor: nwse-resize;
  background: var(--overlay-dark);
  border-radius: 4px;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background var(--transition-fast), opacity var(--transition-fast), transform var(--transition-fast);
  opacity: 0.7;
}

.resize-grip svg {
  width: clamp(12px, 14cqmin, 24px);
  height: clamp(12px, 14cqmin, 24px);
  color: var(--icon-on-dark);
  pointer-events: none;
  transform: none;
  transform-origin: center;
}

.resize-grip:hover {
  background: var(--overlay-opaque);
  opacity: 1;
  transform: scale(1.08);
}

.resize-grip:hover svg {
  transform: scale(1.08);
}

.preview-slot.selected .resize-grip {
  opacity: 1;
}

/* Visual feedback during resize */
.preview-slot.resizing {
  border-color: var(--color-primary);
  border-style: dashed;
  outline: 2px dashed var(--color-primary);
  outline-offset: -2px;
  z-index: 20;
  cursor: grabbing;
}

/* Hide resize grip during crop mode */
.preview-slot.crop-mode .resize-grip {
  display: none;
}

/* ── Move grip (slot repositioning) ── */
/* Same clamp(...) scaling as .resize-grip — see comment above. */
.move-grip {
  position: absolute;
  top: 4px;
  left: 4px;
  z-index: 15;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 4px;
  background: var(--overlay-dark);
  color: var(--icon-on-dark);
  font-size: clamp(11px, 13cqmin, 20px);
  font-weight: 600;
  line-height: 1;
  cursor: grab;
  opacity: 0.7;
  transition: background var(--transition-fast), opacity var(--transition-fast), transform var(--transition-fast);
  padding: 0;
  width: clamp(20px, 25cqmin, 44px);
  height: clamp(20px, 25cqmin, 44px);
  user-select: none;
  -webkit-user-select: none;
  touch-action: none;
}
.preview-slot:hover .move-grip { opacity: 1; }
.move-grip:hover {
  background: var(--overlay-opaque);
  opacity: 1;
  transform: scale(1.08);
}
.move-grip:focus-visible {
  opacity: 1;
  outline: none;
  box-shadow: 0 0 0 2px var(--color-primary);
}
.preview-slot.moving {
  cursor: grabbing;
  z-index: 30;
  opacity: 0.85;
  border-color: var(--color-primary);
  border-style: dashed;
  outline: 2px dashed var(--color-primary);
  outline-offset: -2px;
}
.preview-slot.moving .move-grip { cursor: grabbing; opacity: 1; }
.preview-slot.crop-mode .move-grip { display: none; }

/* ── Proportional slot icons by view size ── */
/* Slot move/resize/delete icons no longer need data-view-size bumps
   — the base clamp(MIN, Ncqmin, MAX) formulas already scale with the
   slot's rendered size via container-type:size. crop-btn and
   slot-caption-toggle keep their fixed-size bumps because they
   aren't part of the cqmin scaling rework yet (they have their own
   sizing concerns: crop-btn is icon-only with a fixed ratio, the
   caption-toggle is a pill shape). */
.preview-container[data-view-size="lg"] .crop-btn {
  width: 34px;
  height: 34px;
}
.preview-container[data-view-size="lg"] .crop-btn svg {
  width: 19px;
  height: 19px;
}
.preview-container[data-view-size="lg"] .slot-caption-toggle {
  height: 26px;
  padding: 2px 14px;
  border-radius: 13px;
}

.preview-container[data-view-size="xl"] .crop-btn {
  width: 38px;
  height: 38px;
}
.preview-container[data-view-size="xl"] .crop-btn svg {
  width: 21px;
  height: 21px;
}
.preview-container[data-view-size="xl"] .slot-caption-toggle {
  height: 28px;
  padding: 2px 16px;
  border-radius: 14px;
}


.preview-slot.empty-slot {
  cursor: default;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--bg-tertiary);
}

.empty-slot-label {
  font-size: 0.7rem;
  color: var(--text-muted);
  text-align: center;
  padding: var(--space-xs);
}

/* ── Slot Captions ── */

.slot-caption-area {
  position: relative;
  flex-shrink: 0;
  /* No z-index: avoid creating a stacking context so that the caption
     toolbar can participate in the slot's stacking context and render
     above fade overlays when the slot is boosted. */
  display: flex;
  flex-direction: column;
  align-items: stretch;
  pointer-events: auto;
  padding-top: 2px;
  background: transparent;
}

.slot-caption {
  font-size: 0.65rem;
  line-height: 1.3;
  color: var(--text-secondary);
  padding: 2px 4px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-height: 1.1em;
  border-radius: var(--radius-xs, 2px);
  background: transparent;
  transition: background var(--transition-fast), box-shadow var(--transition-fast);
}

.slot-caption.caption-left { text-align: left; }
.slot-caption.caption-center { text-align: center; }
.slot-caption.caption-right { text-align: right; }

.slot-caption-placeholder {
  font-size: 0.6rem;
  color: transparent;
  font-style: italic;
  text-align: center;
  padding: 2px 4px;
  cursor: text;
  transition: color var(--transition-fast);
  min-height: 1.2em;
}

.preview-slot:hover .slot-caption-placeholder {
  color: var(--text-muted);
}

.slot-caption[contenteditable="true"] {
  outline: none;
  box-shadow: none;
  border: none;
  white-space: normal;
  cursor: text;
}

/* Old caption-align-toggle replaced by .caption-toolbar (shown on click) */
.caption-align-toggle {
  display: none;
}

/* Responsive caption sizing — default sizes per view; per-slot overrides via inline style */
.preview-container[data-view-size="lg"] .slot-caption,
.preview-container[data-view-size="lg"] .slot-caption-placeholder {
  font-size: 0.65rem;
}

.preview-container[data-view-size="xl"] .slot-caption,
.preview-container[data-view-size="xl"] .slot-caption-placeholder {
  font-size: 0.75rem;
}

/* View-size scaling for caption toolbar */
.preview-container[data-view-size="lg"] .caption-toolbar { font-size: 12px; }
.preview-container[data-view-size="xl"] .caption-toolbar { font-size: 12px; }


/* Add page card */
.add-page-card {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-sm);
  width: 100%;
  border: 2px dashed var(--border-default);
  border-radius: var(--radius-md);
  background: var(--bg-tertiary);
  cursor: pointer;
  transition: border-color var(--transition-fast), background var(--transition-fast), box-shadow var(--transition-fast);
  box-sizing: border-box;
  /* aspect-ratio set via inline style */
}

.add-page-card:hover {
  border-color: var(--color-primary);
  background: color-mix(in srgb, var(--color-primary) 6%, var(--bg-tertiary));
  box-shadow: var(--shadow-sm);
}

.add-page-card:focus-visible {
  outline: none;
  border-color: var(--border-focus);
  box-shadow: var(--shadow-focus);
}

.add-page-icon {
  font-size: 2.5rem;
  font-weight: 300;
  line-height: 1;
  color: var(--text-muted);
  transition: color var(--transition-fast);
}

.add-page-card:hover .add-page-icon {
  color: var(--color-primary);
}

.add-page-label {
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--text-muted);
  transition: color var(--transition-fast);
}

.add-page-card:hover .add-page-label {
  color: var(--color-primary);
}

/* Add-page card at the product's hard page-count ceiling.
   Killed-state border + amber tint signals "not actionable" without
   shouting "error" — the user hasn't done anything wrong, the book
   just hit its cap. The hover lift is suppressed so the card doesn't
   pretend to be clickable. */
.add-page-card.add-page-card--maxed {
  cursor: not-allowed;
  border-color: color-mix(in srgb, var(--color-warning, #c2782b) 50%, var(--border-default));
  background: color-mix(in srgb, var(--color-warning, #c2782b) 6%, var(--bg-tertiary));
}

.add-page-card.add-page-card--maxed:hover {
  border-color: color-mix(in srgb, var(--color-warning, #c2782b) 50%, var(--border-default));
  background: color-mix(in srgb, var(--color-warning, #c2782b) 6%, var(--bg-tertiary));
  box-shadow: none;
}

.add-page-card.add-page-card--maxed .add-page-icon {
  color: color-mix(in srgb, var(--color-warning, #c2782b) 70%, var(--text-muted));
  opacity: 0.55;
}

.add-page-card.add-page-card--maxed:hover .add-page-icon,
.add-page-card.add-page-card--maxed:hover .add-page-label {
  color: color-mix(in srgb, var(--color-warning, #c2782b) 70%, var(--text-muted));
}

.add-page-card.add-page-card--maxed .add-page-label {
  color: color-mix(in srgb, var(--color-warning, #c2782b) 70%, var(--text-muted));
  text-align: center;
  line-height: 1.35;
  padding: 0 var(--space-md);
  max-width: 22ch;
}

/* ── Export FAB ── */

.export-fab {
  position: fixed;
  bottom: 24px;
  right: 24px;
  background: var(--color-primary);
  color: white;
  border: none;
  border-radius: 28px;
  padding: 12px 20px;
  font-size: 0.9rem;
  font-weight: 600;
  cursor: pointer;
  box-shadow: 0 4px 12px var(--overlay-light);
  z-index: 100;
  display: flex;
  align-items: center;
  gap: 8px;
  transition: transform 0.2s, box-shadow 0.2s;
}

.export-fab:hover {
  transform: translateY(-2px);
  box-shadow: 0 6px 16px var(--overlay-strong);
  background: var(--color-primary-hover);
}

.export-fab:disabled {
  opacity: 0.7;
  cursor: not-allowed;
  transform: none;
}

.export-fab.hidden {
  display: none;
}

/* ── Alignment Guides ── */

.align-guide-v,
.align-guide-h {
  position: absolute;
  pointer-events: none;
  z-index: 100;
  opacity: 0;
  transition: opacity 0.1s;
}

.align-guide--visible {
  opacity: 1;
}

.align-guide-v {
  left: 50%;
  top: 0;
  bottom: 0;
  width: 1px;
  background: var(--color-primary-medium);
}

.align-guide-h {
  top: 50%;
  left: 0;
  right: 0;
  height: 1px;
  background: var(--color-primary-medium);
}

/* Dark mode: ensure amber guide color remains visible */
[data-theme="dark"] .align-guide-v,
[data-theme="dark"] .align-guide-h {
  background: var(--color-primary-bright);
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .align-guide-v,
  :root:not([data-theme="light"]) .align-guide-h {
    background: var(--color-primary-bright);
  }
}

/* ── Snap Indicator Lines ── */
/* Shown when an element edge snaps to a bleed, trim, or safe zone boundary */

.snap-indicator {
  position: absolute;
  pointer-events: none;
  z-index: 150;
}

.snap-indicator-v {
  top: -20%;
  bottom: -20%;
  width: 0;
  border-left: 1.5px dashed rgba(50, 140, 220, 0.8);
}

.snap-indicator-h {
  left: -20%;
  right: -20%;
  height: 0;
  border-top: 1.5px dashed rgba(50, 140, 220, 0.8);
}

/* ── Slot Icon Visibility (Figma-style) ─────────────────────────────────
   Slot icons hide at rest and show on:
     - :hover (desktop mouse)
     - .selected (added by slot-select.js on click/tap; touch path)
     - .resizing / .moving / .crop-mode (during the gesture itself)
     - :focus-visible (keyboard nav)

   Previously this was wrapped in @media (hover: hover) so touch users
   saw all icons all the time — that meant tiny slots had giant icons
   covering them. The slot-select.js module restores touch usability:
   a tap adds .selected, a tap-outside clears it, so touch users get
   the same hide-at-rest / show-on-engagement model as desktop.
   pointer-events at rest stays auto so the existing drag/resize
   handlers remain reachable; we only fade visibility. */
.preview-slot .crop-btn,
.preview-slot .slot-delete-btn,
.preview-slot .slot-caption-toggle,
.preview-slot .move-grip,
.preview-slot .resize-grip {
  opacity: 0;
  transition: opacity 0.15s;
}

.preview-slot:hover .crop-btn,
.preview-slot:hover .slot-delete-btn,
.preview-slot:hover .slot-caption-toggle,
.preview-slot:hover .move-grip,
.preview-slot:hover .resize-grip,
.preview-slot.selected .crop-btn,
.preview-slot.selected .slot-delete-btn,
.preview-slot.selected .slot-caption-toggle,
.preview-slot.selected .move-grip,
.preview-slot.selected .resize-grip {
  opacity: 1;
}

.preview-slot.resizing .resize-grip,
.preview-slot.moving .move-grip,
.preview-slot.crop-mode .crop-btn {
  opacity: 1;
}

.crop-btn:focus-visible,
.slot-delete-btn:focus-visible,
.slot-caption-toggle:focus-visible,
.move-grip:focus-visible,
.resize-grip:focus-visible {
  opacity: 1;
}

/* ── Mini-Toolbar Threshold ───────────────────────────────────────
   Single source of truth for when slots / textboxes switch from
   corner-anchored handles to the horizontal mini-toolbar layout.
   Exposed as a CSS custom property AND hardcoded in the two
   @container queries below — CSS doesn't (yet) allow `var()` in
   container query lengths, so the literal must match. JS reads
   the var via `slot-mini-toolbar.js` so re-parenting and smart-
   positioning use the same value automatically. To change the
   threshold, update --mini-toolbar-threshold AND the two literal
   values in the @container queries. */
:root {
  --mini-toolbar-threshold: 100px;
}

/* ── Slot Mini-Toolbar Mode on Small Hosts ────────────────────────
   When the slot's short side drops below the threshold, the
   touch-floor handles (20px each, delete 18px) cram into the
   corners and either overlap or escape past the page card's
   overflow:hidden. No corner positioning works at this size — we
   tried inside, outside, and per-handle nudges (PRs #137/#139/
   #140/#141), and each had a different failure mode.

   At the threshold, all five handles re-lay themselves out as a
   horizontal strip BELOW the slot — a contextual mini-toolbar.
   The strip is centered on the slot, 20×20 buttons with 4px gaps
   (total ~116px wide). Buttons sit at `bottom: -28px`, so the
   strip is fully outside the slot.

   For slots near the page bottom edge the strip would clip past
   the page card; slot-mini-toolbar.js detects that case and adds
   `.slot-mini-toolbar-flipped` to the slot, which moves the strip
   above (`top: -28px`) instead. Horizontal page-edge clipping is
   handled via `--mini-toolbar-shift-x` set by the same module. */
@container (max-width: 100px) or (max-height: 100px) {
  /* `.slot-image-container` clips its children with overflow:hidden
     to keep cropped images inside the slot bounds. Most slot
     handles (move/resize/crop/caption) are children of THIS
     container — so when their toolbar position extends outside
     the slot bottom they get clipped, leaving only the delete-btn
     (which is a direct child of .preview-slot) visible. Disable
     the clip on small slots; the trade-off is that a cropped
     image's overflow may spill slightly past the slot edge, but
     at < 80px the spill is small and the slot is too small for
     visible cropping anyway. */
  .preview-container .preview-slot .slot-image-container {
    overflow: visible;
  }
  /* Hover bridge: extend the slot's hover hit-test region 8px in
     every direction so the cursor can travel from slot to a
     toolbar button (which sits 8px below or above the slot edge)
     without leaving any descendant of the slot. Without this, the
     gap between slot.bottom and the toolbar's first button stops
     `.preview-slot:hover`, and the handles fade out before the
     user can reach them — most visibly on the caption-toggle
     where cursor lingers while users read the icon. The bridge
     is `z-index: -1` so it sits behind the slot's painted content
     and behind toolbar buttons; clicks on those still hit them. */
  .preview-container .preview-slot::after {
    content: '';
    position: absolute;
    inset: -8px;
    z-index: -1;
    pointer-events: auto;
  }
  /* `.preview-container` prefix bumps specificity to 0,3,0 to
     match the `.preview-container[data-view-size="lg"] .crop-btn`
     scaling rule (which would otherwise win at 0,3,0 vs a plain
     `.preview-slot .crop-btn` at 0,2,0). Same source-order wins
     when specificity ties — and these rules come later. */
  .preview-container .preview-slot .move-grip,
  .preview-container .preview-slot .resize-grip,
  .preview-container .preview-slot .crop-btn,
  .preview-container .preview-slot .slot-caption-toggle,
  .preview-container .preview-slot .slot-delete-btn {
    /* 20px matches the clamp(20px, 25cqmin, 44px) floor that
       normal-mode handles bottom out at, so the visual size
       transitions smoothly across the 80px @container threshold
       (no growth-on-shrink discontinuity). It's also the touch-
       target floor we already commit to for normal-mode handles. */
    width: 20px;
    height: 20px;
    /* The global `button` rule in components.css sets
       min-width/min-height: 42px on every <button>, which would
       otherwise force move-grip / crop-btn / caption-toggle /
       delete-btn back up to 42 in toolbar mode. Unset here so the
       width above wins. */
    min-width: unset;
    min-height: unset;
    top: auto;
    right: auto;
    bottom: -28px;
    transform: none;
    padding: 0;
    line-height: 0;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
  }
  /* Toolbar layout: 5 buttons × 20px + 4 gaps × 4px = 116px wide.
     Strip is centered on the slot — first button at -58 from
     center, stride 24px (20 + 4 gap) per button. */
  .preview-container .preview-slot .move-grip {
    left: calc(50% - 58px + var(--mini-toolbar-shift-x, 0px));
  }
  .preview-container .preview-slot .resize-grip {
    left: calc(50% - 34px + var(--mini-toolbar-shift-x, 0px));
  }
  .preview-container .preview-slot .crop-btn {
    left: calc(50% - 10px + var(--mini-toolbar-shift-x, 0px));
  }
  .preview-container .preview-slot .slot-caption-toggle {
    left: calc(50% + 14px + var(--mini-toolbar-shift-x, 0px));
  }
  .preview-container .preview-slot .slot-delete-btn {
    left: calc(50% + 38px + var(--mini-toolbar-shift-x, 0px));
  }
  /* Override the per-handle SVG sizing — clamp formulas + lg/xl
     view-size bumps would otherwise leave the SVG at 16-21px,
     too big inside a 20px button. 12px leaves 4px of bezel.
     Specificity 0,3,0 to beat the view-size scaling rule. */
  .preview-container .preview-slot .move-grip svg,
  .preview-container .preview-slot .resize-grip svg,
  .preview-container .preview-slot .crop-btn svg,
  .preview-container .preview-slot .slot-caption-toggle svg,
  .preview-container .preview-slot .slot-delete-btn svg {
    width: 12px;
    height: 12px;
  }
  /* Flip: strip moves ABOVE the slot when the slot is near the
     page bottom and the default below-position would clip. JS
     toggles the .slot-mini-toolbar-flipped class. */
  .preview-container .preview-slot.slot-mini-toolbar-flipped .move-grip,
  .preview-container .preview-slot.slot-mini-toolbar-flipped .resize-grip,
  .preview-container .preview-slot.slot-mini-toolbar-flipped .crop-btn,
  .preview-container .preview-slot.slot-mini-toolbar-flipped .slot-caption-toggle,
  .preview-container .preview-slot.slot-mini-toolbar-flipped .slot-delete-btn {
    bottom: auto;
    top: -28px;
  }
}

/* ── Text Boxes ── */

.preview-textbox {
  position: absolute;
  /* Sage-green dashed by default. The entire design palette is warm
     (primary = terracotta, warning = amber, danger = terra-red), so a
     warm token reads as "already in the warning family" against the
     dashed-red trim guide and amber safe-zone alert. --color-success
     is the only clearly cool token in the system, which makes "inside
     the safe zone" visually obvious. Hover / active / dragging /
     resizing all pin border-color to the strong primary (terracotta)
     for engagement feedback — that warm pull only happens *during*
     interaction, so it doesn't get confused with the warning state. */
  border: 1px dashed var(--color-success, #6B8F5C);
  border-radius: 3px;
  padding: 4px 6px;
  cursor: default;
  min-width: 40px;
  min-height: 20px;
  overflow: visible;
  word-wrap: break-word;
  white-space: pre-wrap;
  font-family: inherit;
  z-index: 200;
  background: transparent;
  box-sizing: border-box;
  /* Same containment as .preview-slot: lets child handles scale with
     the textbox's rendered size via cqmin units. Page-number boxes
     share .preview-textbox so they inherit the same scaling. */
  container-type: size;
  transition: border-color var(--transition-fast, 0.15s), box-shadow var(--transition-fast, 0.15s);
}

/* The page-number toolbar lives in document.body (so the page card's
   overflow:hidden doesn't clip it) and is positioned via
   positionToolbarFixed which writes viewport-relative pixel top/left.
   That math only works against position:fixed — the inherited
   .textbox-toolbar position:absolute would map the same coordinates to
   body coords, putting the toolbar at the top of the document instead
   of in the viewport whenever the page is scrolled.

   The four positioning shorthands are reset to auto because
   .textbox-toolbar (used by ordinary in-place text-box toolbars) sets
   `bottom: 100%; left: 0;` to lift the toolbar above its parent box.
   Those rules don't help us — positionToolbarFixed sets `top` and
   `left` directly — and worse, with position:fixed the inherited
   `bottom: 100%` resolves against the viewport, fighting our `top`
   value and squashing the toolbar's height to zero (icons clip and
   box looks wrong). The two-class selector raises specificity above
   .textbox-toolbar so these resets win regardless of source order. */
.textbox-toolbar.page-number-toolbar,
.textbox-toolbar.textbox-toolbar-floating {
  position: fixed;
  top: auto;
  bottom: auto;
  left: auto;
  right: auto;
  margin: 0;
}

/* Page-number boxes are linked instances — every interior page renders
   one box from the same shared style. The dashed accent (vs the regular
   text-box dashed-grey) is a visual nudge that "this is special — edit
   one and they all change." Otherwise the geometry / hover / active /
   resize affordances all inherit from .preview-textbox.
   Z-index sits well above the slot range: regular slots paint up to
   z-index ~100, but cover-slot drag bumps to 300 and resize bumps to
   200 (see preview.js startCoverSlotMove / startCoverSlotResize). The
   page-number wrapper has to outrank ALL of those — even the
   mid-drag bumps — so users can always see and grab it. 500 fits
   between --z-overlay (1000, modals) and the slot bumps. .active is
   pinned slightly higher with its own selector so the parent
   .preview-textbox.active rule (z 210) doesn't silently win on
   source-order. */
.preview-textbox.preview-page-number {
  border-color: var(--color-primary-medium, var(--color-primary));
  border-style: dashed;
  z-index: 500;
}
.preview-textbox.preview-page-number.active {
  z-index: 510;
}
.preview-textbox.preview-page-number .page-number-content {
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: inherit;
  pointer-events: none; /* numeral itself isn't editable */
  user-select: none;
}

/* Every textbox interaction state stays in the cool sage family — the
 * whole design palette is warm (terracotta, amber, terra-red), so using
 * --color-primary here would make hover/active/drag/resize visually
 * indistinguishable from the amber safe-zone warning. --color-success
 * (light sage) is the rest tone; --color-success-hover (darker sage) is
 * the engagement tone. When the safe-zone warning fires, the more
 * specific .text-warn-outside-safe rule below flips to amber and wins
 * via !important. */
.preview-textbox:hover {
  border-color: var(--color-success-hover, #557148);
}

.preview-textbox.active {
  border-color: var(--color-success-hover, #557148);
  border-style: solid;
  box-shadow: 0 0 0 3px rgba(107, 143, 92, 0.32);
  z-index: 210;
}

.preview-textbox.dragging {
  opacity: 0.7;
  border-color: var(--color-success-hover, #557148);
}

.preview-textbox.resizing {
  border-color: var(--color-success-hover, #557148);
  border-style: solid;
}

/* When the safe-zone warning is also active, the amber border wins via
 * the rule below — but the active-state glow would still paint sage on
 * top of an amber-bordered textbox. Override the glow too so the visual
 * is unambiguously "warning" when both classes are present. */
.preview-textbox.text-warn-outside-safe.active {
  box-shadow: 0 0 0 3px rgba(217, 119, 6, 0.32);
}

.preview-textbox .textbox-content {
  width: 100%;
  height: 100%;
  outline: none;
  cursor: text;
  overflow: hidden;
  word-wrap: break-word;
  white-space: pre-wrap;
}

.preview-textbox .textbox-content:focus {
  outline: none;
}

.preview-textbox .textbox-content[contenteditable="true"] {
  cursor: text;
}

/* ── Textbox Delete Button (matches .slot-delete-btn) ── */
/* Same clamp(...) scaling as .slot-delete-btn — see comment there. */
.preview-textbox .textbox-delete {
  position: absolute;
  top: -6px;
  right: -6px;
  width: clamp(18px, 20cqmin, 32px);
  height: clamp(18px, 20cqmin, 32px);
  min-width: unset;
  min-height: unset;
  border-radius: 50%;
  background: var(--overlay-darker);
  color: var(--icon-on-dark);
  border: 1px solid var(--border-on-dark);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: clamp(10px, 11cqmin, 17px);
  font-weight: bold;
  line-height: 1;
  z-index: 11;
  padding: 0;
  transition: opacity var(--transition-fast), transform var(--transition-fast), background var(--transition-fast), border-color var(--transition-fast);
}

.textbox-delete:hover {
  background: var(--color-danger-solid);
  border-color: var(--border-on-dark-strong);
  transform: scale(1.08);
  opacity: 1;
}

/* ── Textbox Resize Grip (matches .resize-grip) ── */
/* Same clamp(...) scaling as .resize-grip — see comment there. */
.preview-textbox .textbox-resize {
  position: absolute;
  bottom: 4px;
  right: 4px;
  width: clamp(20px, 25cqmin, 44px);
  height: clamp(20px, 25cqmin, 44px);
  z-index: 10;
  cursor: nwse-resize;
  background: var(--overlay-dark);
  border-radius: 4px;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background var(--transition-fast), opacity var(--transition-fast), transform var(--transition-fast);
}

.textbox-resize svg {
  width: clamp(12px, 14cqmin, 24px);
  height: clamp(12px, 14cqmin, 24px);
  color: var(--icon-on-dark);
  pointer-events: none;
}

.textbox-resize:hover {
  background: var(--overlay-opaque);
  opacity: 1;
  transform: scale(1.08);
}

/* ── Textbox Move Grip (matches .move-grip) ── */
/* Same clamp(...) scaling as .move-grip — see comment there. */
.preview-textbox .textbox-move {
  position: absolute;
  top: 4px;
  left: 4px;
  z-index: 15;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 4px;
  background: var(--overlay-dark);
  color: var(--icon-on-dark);
  font-size: clamp(11px, 13cqmin, 20px);
  font-weight: 600;
  line-height: 1;
  cursor: grab;
  transition: background var(--transition-fast), opacity var(--transition-fast), transform var(--transition-fast);
  padding: 0;
  width: clamp(20px, 25cqmin, 44px);
  height: clamp(20px, 25cqmin, 44px);
  user-select: none;
  -webkit-user-select: none;
  touch-action: none;
}

.textbox-move:hover {
  background: var(--overlay-opaque);
  opacity: 1;
  transform: scale(1.08);
}

.preview-textbox.dragging .textbox-move {
  cursor: grabbing;
  opacity: 1;
}

/* ── Textbox Icon Visibility (Figma-style) ─────────────────────────
   Same hide-at-rest model as .preview-slot above. Was previously
   wrapped in @media (hover: hover) — touch users saw all handles
   always, which meant tiny page-number boxes (which share
   .preview-textbox) had handles overflowing on every interior page.
   The .preview-textbox.active class is set on click/tap by the
   textbox.js + page-number.js activate() helpers, so touch users
   get the same hide-at-rest / show-on-engagement model. */
.preview-textbox .textbox-delete,
.preview-textbox .textbox-resize,
.preview-textbox .textbox-move {
  opacity: 0;
  transition: opacity 0.15s;
}

.preview-textbox:hover .textbox-delete,
.preview-textbox:hover .textbox-resize,
.preview-textbox:hover .textbox-move,
.preview-textbox.active .textbox-delete,
.preview-textbox.active .textbox-resize,
.preview-textbox.active .textbox-move,
.preview-textbox.resizing .textbox-resize,
.preview-textbox.dragging .textbox-move {
  opacity: 1;
}

/* ── Proportional textbox icons by view size ── */
/* The data-view-size lg/xl bumps that previously hardcoded different
   per-zoom widths are gone — the base clamp(MIN, Ncqmin, MAX)
   formulas above already scale with the textbox's rendered size via
   container-type:size, so a zoomed-in editor naturally renders larger
   handles without a separate rule. */

/* ── Textbox Mini-Toolbar Mode on Small Hosts ─────────────────────
   Mirrors the slot mini-toolbar above. Three handles (move/resize/
   delete), so the strip is narrower (3 × 20 + 2 × 4 = 68px wide).
   Page-number boxes inherit this since they share the
   .preview-textbox class — and they're often small, so this is the
   common path for them.
   Threshold value matches --mini-toolbar-threshold above. */
@container (max-width: 100px) or (max-height: 100px) {
  /* Hover bridge — see the slot block above for rationale. */
  .preview-container .preview-textbox::after {
    content: '';
    position: absolute;
    inset: -8px;
    z-index: -1;
    pointer-events: auto;
  }
  /* `.preview-container` prefix bumps specificity to 0,3,0 so the
     view-size scaling rules don't override our toolbar-mode
     sizing. Same approach as the slot block above. */
  .preview-container .preview-textbox .textbox-move,
  .preview-container .preview-textbox .textbox-resize,
  .preview-container .preview-textbox .textbox-delete {
    width: 20px;
    height: 20px;
    /* See slot block above — global button min-width:42px would
       otherwise win over our width:20 here. */
    min-width: unset;
    min-height: unset;
    top: auto;
    right: auto;
    bottom: -28px;
    transform: none;
    padding: 0;
    line-height: 0;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
  }
  /* Toolbar layout: 3 buttons × 20px + 2 gaps × 4px = 68px wide.
     Strip is centered on the textbox — first button at -34 from
     center, stride 24px per button. */
  .preview-container .preview-textbox .textbox-move {
    left: calc(50% - 34px + var(--mini-toolbar-shift-x, 0px));
  }
  .preview-container .preview-textbox .textbox-resize {
    left: calc(50% - 10px + var(--mini-toolbar-shift-x, 0px));
  }
  .preview-container .preview-textbox .textbox-delete {
    left: calc(50% + 14px + var(--mini-toolbar-shift-x, 0px));
  }
  .preview-container .preview-textbox .textbox-move svg,
  .preview-container .preview-textbox .textbox-resize svg,
  .preview-container .preview-textbox .textbox-delete svg {
    width: 12px;
    height: 12px;
  }
  .preview-container .preview-textbox.slot-mini-toolbar-flipped .textbox-move,
  .preview-container .preview-textbox.slot-mini-toolbar-flipped .textbox-resize,
  .preview-container .preview-textbox.slot-mini-toolbar-flipped .textbox-delete {
    bottom: auto;
    top: -28px;
  }

  /* Mini-mode shoves move/resize/delete into the strip directly below
     the textbox where the rotate handle normally sits — push the
     rotate handle further down so the buttons don't cover it. The
     connector line goes invisible in this mode (buttons sit on it),
     so hide it; the icon is now clearly recognisable on its own. */
  .preview-container .preview-textbox .textbox-rotate-handle {
    bottom: -54px;
  }
  .preview-container .preview-textbox .textbox-rotate-line {
    display: none;
  }
  /* When the toolbar strip flips to the top of the textbox, the
     handle's normal position is clear; restore the connector. */
  .preview-container .preview-textbox.slot-mini-toolbar-flipped .textbox-rotate-handle {
    bottom: -44px;
  }
  .preview-container .preview-textbox.slot-mini-toolbar-flipped .textbox-rotate-line {
    display: block;
  }
}

/* ── Textbox Rotation Handle ── */

.textbox-rotate-line {
  position: absolute;
  left: 50%;
  bottom: -25px;
  width: 1px;
  height: 20px;
  background: var(--color-primary, #4a90d9);
  transform: translateX(-50%);
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--transition-fast, 0.15s);
}

.textbox-rotate-handle {
  position: absolute;
  left: 50%;
  bottom: -44px;
  width: 22px;
  height: 22px;
  margin-left: -11px;
  border-radius: 50%;
  background: var(--color-primary, #4a90d9);
  color: #fff;
  border: 1.5px solid #fff;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25);
  cursor: grab;
  opacity: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: opacity var(--transition-fast, 0.15s), transform var(--transition-fast, 0.15s);
  z-index: 210;
}
.textbox-rotate-handle:hover {
  transform: scale(1.08);
}
.textbox-rotate-handle svg {
  width: 14px;
  height: 14px;
  pointer-events: none;
}

.textbox-rotate-handle:active {
  cursor: grabbing;
}

.preview-textbox:hover .textbox-rotate-handle,
.preview-textbox:hover .textbox-rotate-line,
.preview-textbox.active .textbox-rotate-handle,
.preview-textbox.active .textbox-rotate-line,
.preview-textbox.rotating .textbox-rotate-handle,
.preview-textbox.rotating .textbox-rotate-line {
  opacity: 1;
}

/* Rotation angle readout shown while dragging the rotate handle */
.rotate-readout {
  position: fixed;
  background: rgba(0, 0, 0, 0.72);
  color: #fff;
  font-size: 11px;
  font-family: var(--font-mono, monospace);
  font-weight: 600;
  padding: 2px 6px;
  border-radius: 4px;
  pointer-events: none;
  z-index: 9999;
  white-space: nowrap;
  letter-spacing: 0.03em;
}

/* Add Text Box floating action button on page card */
.add-textbox-btn {
  position: absolute;
  bottom: 8px;
  left: 8px;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: var(--overlay-dark);
  color: var(--icon-on-dark);
  border: none;
  cursor: pointer;
  opacity: 0;
  transition: opacity var(--transition-fast, 0.15s);
  z-index: 150;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  min-width: 0;
  min-height: 0;
}

.preview-page:hover .add-textbox-btn {
  opacity: 1;
}

.add-textbox-btn:hover {
  background: var(--color-primary);
  color: white;
}

.add-textbox-btn svg {
  width: 16px;
  height: 16px;
}

/* Responsive sizing via data-view-size */
.preview-container[data-view-size="lg"] .add-textbox-btn {
  width: 34px;
  height: 34px;
}
.preview-container[data-view-size="lg"] .add-textbox-btn svg {
  width: 19px;
  height: 19px;
}

.preview-container[data-view-size="xl"] .add-textbox-btn {
  width: 38px;
  height: 38px;
}
.preview-container[data-view-size="xl"] .add-textbox-btn svg {
  width: 21px;
  height: 21px;
}

/* ── Textbox Floating Toolbar ── */
/* Shared editing toolbar — used by both captions and text boxes */
.editing-toolbar,
.textbox-toolbar,
.caption-toolbar {
  position: absolute;
  bottom: 100%;
  left: 0;
  margin-bottom: 6px;
  display: flex;
  align-items: center;
  gap: 4px;
  background: var(--color-surface, #fff);
  border: 1px solid var(--color-border, #e7e5e4);
  border-radius: var(--radius-sm, 4px);
  padding: 3px 6px;
  box-shadow: 0 2px 8px var(--overlay-lightest);
  z-index: 300;
  white-space: nowrap;
  font-size: 12px;
}

.editing-toolbar select,
.editing-toolbar input,
.textbox-toolbar select,
.textbox-toolbar input,
.caption-toolbar select,
.caption-toolbar input {
  font-size: 11px;
  padding: 2px 4px;
  border: 1px solid var(--color-border, #e7e5e4);
  border-radius: var(--radius-sm, 4px);
  background: var(--color-surface, #fff);
  color: var(--color-text, #1c1917);
  outline: none;
  cursor: pointer;
}

.editing-toolbar select:focus,
.editing-toolbar input:focus,
.textbox-toolbar select:focus,
.textbox-toolbar input:focus,
.caption-toolbar select:focus,
.caption-toolbar input:focus {
  border-color: var(--color-primary);
}

.textbox-toolbar .textbox-font-select,
.caption-toolbar .caption-font-select {
  max-width: 90px;
}

.textbox-toolbar .textbox-size-select,
.caption-toolbar .caption-size-select {
  max-width: 52px;
}

.textbox-toolbar .textbox-color-picker,
.caption-toolbar .caption-color-picker {
  width: 28px;
  height: 24px;
  padding: 1px;
  border: 1px solid var(--color-border, #e7e5e4);
  border-radius: var(--radius-sm, 4px);
  background: var(--color-surface, #fff);
  cursor: pointer;
}

/* Coloris-wired color input in the caption toolbar — render as a compact
   24x24 swatch button matching the surrounding B/I/align icons. The
   input text itself is hidden; Coloris's overlay button shows the
   current color as its background. We have to override Coloris's
   default 30px button width or its click target overflows the toolbar
   row and steals clicks from adjacent buttons. */
.caption-toolbar .clr-field,
.textbox-toolbar .clr-field {
  display: inline-block;
  width: 24px;
  height: 24px;
  margin: 0;
  vertical-align: middle;
  overflow: hidden;
  border-radius: var(--radius-sm, 4px);
  position: relative;
}
.caption-toolbar .caption-toolbar-color,
.textbox-toolbar .textbox-toolbar-color {
  width: 24px;
  height: 24px;
  padding: 0;
  font-size: 0;
  color: transparent;
  background: var(--color-surface, #fff);
  border: 1px solid var(--color-border, #e7e5e4);
  border-radius: var(--radius-sm, 4px);
  box-sizing: border-box;
  cursor: pointer;
}
.caption-toolbar .caption-toolbar-color:focus,
.textbox-toolbar .textbox-toolbar-color:focus {
  border-color: var(--color-primary);
}
.caption-toolbar .clr-field button,
.textbox-toolbar .clr-field button {
  width: 24px;
  height: 24px;
  min-width: 0;
  min-height: 0;
  padding: 0;
  top: 0;
  right: 0;
  transform: none;
  border: 0;
  border-radius: var(--radius-sm, 4px);
  background: currentColor;
}

.settings-color-picker {
  width: 40px;
  height: 32px;
  padding: 2px;
  border: 1px solid var(--color-border, #e7e5e4);
  border-radius: var(--radius-sm, 4px);
  background: var(--color-surface, #fff);
  cursor: pointer;
}

/* Color dropdown picker */
.color-picker-dropdown {
  position: relative;
  display: inline-block;
}

.color-picker-trigger {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  background: var(--color-surface, #fff);
  border: 1px solid var(--color-border, #d4d4d4);
  border-radius: var(--radius-sm, 4px);
  padding: 3px 5px;
  cursor: pointer;
  line-height: 1;
}

.color-picker-trigger:hover {
  border-color: var(--color-primary, #4a90d9);
}

.color-picker-indicator {
  display: block;
  width: 16px;
  height: 16px;
  border-radius: 3px;
  border: 1px solid transparent;
  box-sizing: border-box;
}

.color-picker-indicator.is-white {
  border-color: var(--color-border, #ccc);
}

.color-picker-arrow {
  font-size: 10px;
  color: var(--color-text-secondary, #666);
  line-height: 1;
}

.color-picker-panel {
  display: none;
  position: absolute;
  top: 100%;
  left: 0;
  margin-top: 4px;
  padding: 6px;
  background: var(--color-surface, #fff);
  border: 1px solid var(--color-border, #d4d4d4);
  border-radius: var(--radius-sm, 6px);
  box-shadow: 0 4px 12px rgba(0,0,0,0.15);
  z-index: 10001;
}

.color-picker-panel.flip-up {
  top: auto;
  bottom: 100%;
  margin-top: 0;
  margin-bottom: 4px;
}

/* Legacy rule for non-portaled panels */
.color-picker-dropdown.open .color-picker-panel {
  display: grid;
  grid-template-columns: repeat(6, 24px);
  gap: 4px;
}

/* Portaled panel: fixed position, always visible when attached to body */
.color-picker-panel.color-picker-portal {
  display: grid;
  grid-template-columns: repeat(6, 24px);
  gap: 4px;
  position: fixed;
  top: auto;
  left: auto;
  margin: 0;
}

/* Settings-context portaled panel uses wider swatches */
.color-picker-panel.color-picker-portal.settings-context {
  grid-template-columns: repeat(6, 28px);
  gap: 6px;
}

.color-picker-panel.color-picker-portal.settings-context .color-swatch {
  width: 28px;
  height: 28px;
  min-width: 28px;
  min-height: 28px;
}

.color-swatch {
  width: 24px;
  height: 24px;
  min-width: 24px;
  min-height: 24px;
  border-radius: 50%;
  border: 2px solid var(--color-border, #d4d4d4);
  cursor: pointer;
  padding: 0;
  outline: none;
  transition: transform 0.15s ease, box-shadow 0.15s ease;
  box-sizing: border-box;
}

.color-swatch:hover {
  transform: scale(1.15);
  box-shadow: 0 0 0 2px var(--color-primary, #4a90d9);
}

.color-swatch.active {
  border-color: var(--color-primary, #4a90d9);
  box-shadow: 0 0 0 2px var(--color-primary, #4a90d9);
}

/* White swatch needs a visible border */
.color-swatch[data-color="#FFFFFF"] {
  border-color: var(--color-border, #ccc);
}

/* Settings panel dropdown — slightly wider grid */
.settings-field .color-picker-dropdown.open .color-picker-panel {
  grid-template-columns: repeat(6, 28px);
  gap: 6px;
}

.settings-field .color-swatch {
  width: 28px;
  height: 28px;
  min-width: 28px;
  min-height: 28px;
}

/* Coloris inputs in the book-settings panel.
   The library wraps each input in an inline-block .clr-field and gives the
   input its native browser width (~268px), which overflows the ~150px
   settings-field column. Force the wrapper + input to fill the field, with
   right padding to make room for the absolutely-positioned 30px swatch
   button that Coloris injects. */
.settings-panel .clr-field {
  display: block;
  width: 100%;
}
.settings-panel .coloris-input {
  width: 100%;
  box-sizing: border-box;
  padding: 6px 36px 6px var(--space-sm);
  border: 1px solid var(--border-default);
  border-radius: var(--radius-sm);
  font-size: 0.875rem;
  font-family: inherit;
  background: var(--bg-primary);
  color: var(--text-primary);
  transition: border-color var(--transition-fast);
}
.settings-panel .coloris-input:focus {
  outline: none;
  border-color: var(--border-focus);
  box-shadow: var(--shadow-focus);
}

/* Alignment buttons inside editing toolbars */
.editing-toolbar .toolbar-align-btn,
.caption-toolbar .toolbar-align-btn {
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--radius-xs, 2px);
  width: 24px;
  height: 24px;
  min-width: 0;
  min-height: 0;
  padding: 0;
  cursor: pointer;
  color: var(--text-muted);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition: background var(--transition-fast), color var(--transition-fast), border-color var(--transition-fast);
}

.editing-toolbar .toolbar-align-btn:hover,
.caption-toolbar .toolbar-align-btn:hover {
  background: var(--bg-tertiary);
  color: var(--text-primary);
  border-color: var(--color-border, #e7e5e4);
}

.editing-toolbar .toolbar-align-btn.active,
.caption-toolbar .toolbar-align-btn.active {
  background: var(--color-primary);
  color: var(--text-inverse);
  border-color: var(--color-primary);
}

/* Bold/Italic toggle buttons in toolbars */
.toolbar-style-btn {
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--radius-xs, 2px);
  width: 24px;
  height: 24px;
  min-width: 0;
  min-height: 0;
  padding: 0;
  cursor: pointer;
  color: var(--text-muted);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition: background var(--transition-fast), color var(--transition-fast), border-color var(--transition-fast);
}

.toolbar-style-btn:hover {
  background: var(--bg-tertiary);
  color: var(--text-primary);
  border-color: var(--color-border, #e7e5e4);
}

.toolbar-style-btn.active {
  background: var(--color-primary);
  color: var(--text-inverse);
  border-color: var(--color-primary);
}

/* ══════════════════════════════════════════════
   Cover Page in Preview
   ══════════════════════════════════════════════ */

/* Cover wrapper spans full width of the grid row but is constrained
   to the same max-width as a regular page wrapper so it doesn't
   appear disproportionately wide. */
.preview-cover-wrapper {
  grid-column: 1 / -1;
}

.preview-container[data-view-size="lg"] .preview-cover-wrapper {
  max-width: 100%;
}
.preview-container[data-view-size="xl"] .preview-cover-wrapper {
  max-width: none;
}

/* The cover page card uses its own wider aspect ratio but is
   constrained by the wrapper.  Height-based cap still applies. */
.preview-container[data-view-size] .preview-cover-page {
  max-height: 80vh;
  width: 100%;
}

/* Also constrain the header to match the cover page width */
.preview-cover-wrapper .preview-page-header {
  max-width: 100%;
}

.preview-cover-page {
  position: relative;
  background: white;
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-md, 0 4px 12px rgba(0,0,0,0.1));
  overflow: visible;
  max-width: 100%;
  /* Establish a size container so child textbox font-size can use cqh units
     to scale with the rendered canvas instead of reflowing at fixed pt. */
  container-type: size;
}

/* Slot images clip themselves; zones are bounded to the canvas.
   No additional overflow clip needed here — letting textbox controls show outside. */

.preview-cover-content {
  position: absolute;
  /* No z-index: same stacking-context fix as .preview-page-content. */
}

.preview-cover-label {
  font-weight: 700;
  color: var(--color-primary);
}

/* Print guides (editor only, toggled by "Print Guides" in book settings)
   - .print-guide-pink   : faded-pink band covering the area between the
                           safe-zone edge and the media edge. Drawn OVER
                           slot images so the at-risk zone is always
                           visible even on photo-heavy spreads.
                           Interior pages use a single-cutout polygon;
                           covers use two cutouts (back + front safe
                           zones) with zero-width bridges.
   - .print-guide-trim-line : dashed red rectangle sitting on the trim
                              boundary (approximately where the printer
                              cuts). Drawn over the pink and over slots. */
.print-guide-pink {
  position: absolute;
  inset: 0;
  background: rgba(255, 72, 120, 0.22);
  pointer-events: none;
  z-index: 101;
}

.print-guide-trim-line {
  position: absolute;
  border: 1.5px dashed rgba(210, 36, 36, 0.9);
  box-sizing: border-box;
  pointer-events: none;
  z-index: 102;
}

[data-theme="dark"] .print-guide-pink {
  background: rgba(255, 96, 140, 0.32);
}

[data-theme="dark"] .print-guide-trim-line {
  border-color: rgba(255, 90, 90, 0.95);
}

/* Zone overlays */
.cover-zone-overlay {
  position: absolute;
  pointer-events: none;
  box-sizing: border-box;
  z-index: 50;
}

.cover-zone-back-overlay {
  background: transparent;
}

.cover-zone-spine-overlay {
  border-left: 2px solid rgba(200, 100, 0, 0.5);
  border-right: 2px solid rgba(200, 100, 0, 0.5);
  background: rgba(200, 100, 0, 0.06);
}

.cover-zone-front-overlay {
  background: transparent;
}

.cover-zone-label {
  position: absolute;
  top: 90%;
  left: 50%;
  transform: translate(-50%, -50%);
  font-size: 0.65rem;
  font-weight: 600;
  color: var(--text-tertiary, #999);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  opacity: 0.7;
  white-space: nowrap;
  pointer-events: none;
}

.cover-zone-label-spine {
  writing-mode: vertical-rl;
  text-orientation: mixed;
  font-size: 0.55rem;
}

/* Cover slots and textboxes */
.cover-preview-slot {
  cursor: default;
}

.cover-preview-textbox {
  position: absolute;
  pointer-events: auto;
  padding: 2px;
  font-size: 0.7rem;
}

/* Drag-over indicator for cover */
.preview-cover-page.drag-over {
  outline: 3px dashed var(--color-primary, #4a90d9);
  outline-offset: -3px;
}

/* ---------- Locked pages (inside cover, flyleaf) ---------- */
.preview-page-locked {
  background: #ffffff !important;
  position: relative;
}

.page-locked-content {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  pointer-events: none;
  user-select: none;
}

.page-locked-title {
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--text-secondary, #666);
  text-transform: uppercase;
  letter-spacing: 0.08em;
}

.page-locked-subtitle {
  font-size: 0.7rem;
  color: var(--text-muted, #999);
  text-align: center;
  max-width: 80%;
  line-height: 1.4;
}

.page-toolbar-btn-disabled,
.page-toolbar-btn:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}
