/* ── Book Preview Page ─────────────────────────────────────────────────────── */

:root {
  --bp-page-w: 1;
  --bp-page-h: 1;
  --bp-media-w: 1;
  --bp-media-h: 1;
  --bp-cover-page-w: 1;
  --bp-cover-page-h: 1;
  /* Set by JS on load/resize to fit the 2-page spread within the stage.
     Represents the media (page-with-bleed) height in pixels. */
  --bp-page-h-px: calc(100vh - var(--header-height, 60px) - 140px);
}

/* Full-height shell: flex column, no scroll.
   Break out of main's max-width so the preview fills the full viewport.
   Background matches .bp-stage so the toolbar appears to float on the
   same surface as the book itself, with no visual seam between the
   site header and the preview area. */
.bp-shell {
  display: flex;
  flex-direction: column;
  height: calc(100vh - var(--header-height) - 4px);
  overflow: hidden;
  width: 100vw;
  margin-left: calc(-50vw + 50%);
  background: var(--bg-secondary);
}

/* ── Snapshot notice (shown above the action bar on /orders/{id}/view-book) ──
   Soft terracotta-tinted info banner that frames "you're looking at the
   frozen version of the book that was ordered, not the live one." Same
   visual weight as the order_detail.html notice so the same message
   carries through both surfaces. */
.bp-snapshot-notice {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  flex-shrink: 0;
  margin: var(--space-md) auto 0;
  padding: 0.6rem 0.9rem;
  background: color-mix(in srgb, var(--mp-color-accent, #b5573a) 8%, var(--bg-elevated));
  border: 1px solid color-mix(in srgb, var(--mp-color-accent, #b5573a) 35%, var(--border-default));
  border-radius: var(--radius-md);
  color: var(--text-primary);
  font-size: 0.85rem;
  line-height: 1.4;
  /* Match the action bar's width clamping pattern (set inline by JS).
     Until JS runs, fall back to a sensible content-wide constraint so
     the banner doesn't span the full viewport. */
  max-width: min(960px, 92vw);
  box-sizing: border-box;
}

.bp-snapshot-notice svg {
  flex-shrink: 0;
  color: var(--mp-color-accent, #b5573a);
}

/* ── Top action bar ──────────────────────────────────────────────────────────── */

.bp-bar {
  display: flex;
  flex-direction: row;
  align-items: center;
  justify-content: space-between;
  flex-shrink: 0;
  height: 56px;
  padding: 0 var(--space-lg);
  background: var(--bg-elevated);
  border: 1px solid var(--border-default);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  gap: 1rem;
  /* Width is set by JS to match the active spread's rendered width */
  margin: var(--space-md) auto 0;
  box-sizing: border-box;
}

.bp-bar-title {
  font-weight: 600;
  color: var(--text-primary);
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  flex: 1;
  text-align: center;
  min-width: 0;
}

/* Right-side cluster of actions on the preview action bar. */
.bp-bar-actions {
  display: flex;
  gap: 8px;
  align-items: center;
}

/* Invisible left-side placeholder used in read-only mode where the Edit
   button is suppressed. Keeps the centered title balanced by occupying
   the symmetrical width of the right-side actions cluster. */
.bp-bar-spacer {
  display: inline-block;
  width: 0;
}

/* ── Stage: centered spread display area ──────────────────────────────────── */

.bp-stage {
  flex: 1;
  display: flex;
  flex-direction: row;
  /* Top-aligned (not vertical-center) so the spread sits just below
     the toolbar with the padding as breathing room. Vertical-center
     produced a tall empty gap between the toolbar and the book on
     viewports where the spread is shorter than the available stage. */
  align-items: flex-start;
  justify-content: center;
  background: var(--bg-secondary);
  overflow: hidden;
  padding: 1.5rem 3rem;
  position: relative;
}

/* ── Navigation arrows ───────────────────────────────────────────────────── */

.bp-nav-prev,
.bp-nav-next {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  width: 40px;
  height: 40px;
  border-radius: 50%;
  background: var(--bg-elevated);
  border: 1px solid var(--border-default);
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12);
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--text-primary);
  transition: background var(--transition-normal), box-shadow var(--transition-normal), opacity var(--transition-normal);
  z-index: 10;
  padding: 0;
}

.bp-nav-prev {
  left: 1rem;
}

.bp-nav-next {
  right: 1rem;
}

.bp-nav-prev:hover:not(:disabled),
.bp-nav-next:hover:not(:disabled) {
  background: var(--bg-tertiary);
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.18);
}

.bp-nav-prev:disabled,
.bp-nav-next:disabled {
  opacity: 0.35;
  cursor: not-allowed;
}

/* ── Spreads ──────────────────────────────────────────────────────────────── */

.bp-spread {
  display: none;
  flex-direction: column;
  align-items: center;
  gap: 0.75rem;
}

.bp-spread.active {
  display: flex;
}

.bp-spread-pages {
  display: flex;
  flex-direction: row;
  gap: 0;
  position: relative;
}

/* ── Page sizing (driven by CSS custom properties set inline by the template) */

/* Pages are rendered at trim size. The printer's bleed margin lives
   only in the PDF (per products.csv media dimensions) and is never
   shown to the customer — the trim box edge IS the trim line, and
   pushing a photo to that edge produces a full-bleed print because
   pdfgen extends edge-adjacent slots into bleed automatically
   (generate.go:283-305). */
.bp-page {
  height: var(--bp-page-h-px);
  width: calc(var(--bp-page-h-px) * var(--bp-page-w) / var(--bp-page-h));
  position: relative;
  overflow: hidden;
  background: #ffffff;
  flex-shrink: 0;
}

/* Trim box fills the page. Slot coordinates are trim-relative
   (0-100% of trim) so slots position within this element. */
.bp-page-trim {
  position: absolute;
  inset: 0;
}

/* ── Cover canvas ─────────────────────────────────────────────────────────── */
/* One flat white rectangle sized to the full laid-flat cover trim:
   width = 2*coverPageW + spineW, height = coverPageH.
   Images and textboxes are positioned on top as percentages of this canvas. */
.bp-cover-canvas {
  position: relative;
  height: var(--bp-cover-h-px);
  width: calc(
    var(--bp-cover-h-px) * var(--bp-cover-page-w) / var(--bp-cover-page-h) * 2 +
    var(--bp-cover-h-px) * var(--bp-spine-w) / var(--bp-cover-page-h)
  );
  background: #ffffff;
  overflow: hidden;
  /* Outer drop-shadow + trim-indicator inset. The trim inset lives on the
     canvas itself (not a pseudo) so it paints under descendants — full-bleed
     cover art covers it on the edges it touches. */
  box-shadow:
    0 2px 8px rgba(0, 0, 0, 0.15),
    inset 0 0 0 3px rgba(0, 0, 0, 0.03);
}

/* Outer-edge paper vignette for the cover. Mirrors the interior-page
   vignette for consistency — subtle darkening on all four outer edges.
   The two spine hinges are overridden by `::before` above this layer,
   so the spine fold remains the dominant shadow at the hinges. */
.bp-cover-canvas::after {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 5;
  box-shadow:
    inset 0 6px 12px -6px rgba(0, 0, 0, 0.10),
    inset 0 -6px 12px -6px rgba(0, 0, 0, 0.10),
    inset 6px 0 12px -6px rgba(0, 0, 0, 0.10),
    inset -6px 0 12px -6px rgba(0, 0, 0, 0.10);
}

/* Spine depth: subtle shadow gradients at the two spine hinges, fading into
   the adjacent cover pages. Suggests the fold where the cover wraps the spine
   without darkening the spine artwork itself. */
.bp-cover-canvas::before {
  content: '';
  position: absolute;
  top: 0;
  bottom: 0;
  left: calc(var(--bp-cover-page-w) / (2 * var(--bp-cover-page-w) + var(--bp-spine-w)) * 100%);
  width: calc(var(--bp-spine-w) / (2 * var(--bp-cover-page-w) + var(--bp-spine-w)) * 100%);
  pointer-events: none;
  z-index: 4;
  background: linear-gradient(
    to right,
    rgba(0, 0, 0, 0.10) 0%,
    rgba(0, 0, 0, 0.036) 18%,
    rgba(0, 0, 0, 0) 40%,
    rgba(0, 0, 0, 0) 60%,
    rgba(0, 0, 0, 0.036) 82%,
    rgba(0, 0, 0, 0.10) 100%
  );
}

.bp-cover-image {
  position: absolute;
  overflow: hidden;
  /* Above the trim indicator (z-index 1), below the spine-hinge gradient
     (z-index 4) so full-bleed cover art is not framed by the inset border. */
  z-index: 2;
}

.bp-cover-image img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.bp-cover-tb {
  position: absolute;
  pointer-events: none;
  overflow: hidden;
  word-break: break-word;
  white-space: pre-wrap;
  /* Above images and trim indicator, below the spine-hinge shadow gradient. */
  z-index: 3;
}

/* Trim-area indicator: thin inset border shows the trim boundary.
   Uses four separate one-sided inset shadows so each side can be suppressed
   individually — the inner (spine) side is dropped per page below to avoid
   a doubled border running down the gutter. Rendered behind slots (z-index 1)
   so full-bleed photos cover it and do not appear framed. */
.bp-page-trim::after {
  content: '';
  position: absolute;
  inset: 0;
  box-shadow:
    inset 0 3px 0 0 rgba(0, 0, 0, 0.03),   /* top */
    inset 0 -3px 0 0 rgba(0, 0, 0, 0.03),  /* bottom */
    inset 3px 0 0 0 rgba(0, 0, 0, 0.03),   /* left */
    inset -3px 0 0 0 rgba(0, 0, 0, 0.03);  /* right */
  pointer-events: none;
  z-index: 1;
}

/* Suppress the inner (gutter-facing) side of the trim indicator so left and
   right pages meet without a doubled border down the spine. */
.bp-page--left .bp-page-trim::after {
  box-shadow:
    inset 0 3px 0 0 rgba(0, 0, 0, 0.03),
    inset 0 -3px 0 0 rgba(0, 0, 0, 0.03),
    inset 3px 0 0 0 rgba(0, 0, 0, 0.03);
}

.bp-page--right .bp-page-trim::after {
  box-shadow:
    inset 0 3px 0 0 rgba(0, 0, 0, 0.03),
    inset 0 -3px 0 0 rgba(0, 0, 0, 0.03),
    inset -3px 0 0 0 rgba(0, 0, 0, 0.03);
}

/* Page drop-shadow on the outer (non-spine) edge.
   The spine-facing gutter fade is handled by the `::before` eased gradient
   overlay below — this rule only contributes the soft outer drop-shadow. */
.bp-page--left {
  box-shadow:
    4px 0 8px rgba(0, 0, 0, 0.12),      /* spine-side (into gutter) */
    -4px 0 10px rgba(0, 0, 0, 0.14),    /* outer (left) edge */
    0 3px 8px rgba(0, 0, 0, 0.10),      /* bottom */
    0 -3px 8px rgba(0, 0, 0, 0.06);     /* top */
}

.bp-page--right {
  box-shadow:
    -4px 0 8px rgba(0, 0, 0, 0.12),     /* spine-side (into gutter) */
    4px 0 10px rgba(0, 0, 0, 0.14),     /* outer (right) edge */
    0 3px 8px rgba(0, 0, 0, 0.10),      /* bottom */
    0 -3px 8px rgba(0, 0, 0, 0.06);     /* top */
}

/* Outer-edge paper vignette.
   Subtle inset shadows on the three outer edges (top, bottom, and the
   outer side — opposite the spine) give full-bleed photos a faint paper
   curl so they don't read as flat rectangles. The spine-facing side is
   intentionally omitted — the binding fade on `.bp-page--left/right`
   handles that edge. Rendered above slots so full-bleed imagery picks
   up the effect. */
.bp-page::after {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 7;
}

.bp-page--left::after {
  box-shadow:
    inset 0 6px 12px -6px rgba(0, 0, 0, 0.10),
    inset 0 -6px 12px -6px rgba(0, 0, 0, 0.10),
    inset 6px 0 12px -6px rgba(0, 0, 0, 0.10);
}

.bp-page--right::after {
  box-shadow:
    inset 0 6px 12px -6px rgba(0, 0, 0, 0.10),
    inset 0 -6px 12px -6px rgba(0, 0, 0, 0.10),
    inset -6px 0 12px -6px rgba(0, 0, 0, 0.10);
}

/* ── Gutter curl overlay ───────────────────────────────────────────────────── */
/* Soft, eased gradient along the spine-facing edge of each interior page to
   simulate the page curving down into the binding. Rendered above slots
   (z-index 6) and the trim indicator (z-index 1) so it paints over
   full-bleed photos as well. */
.bp-page--left::before,
.bp-page--right::before {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 8;
}

/* The gutter shadow is darker and wider than the cover-spine version
   because interior spreads have no separate spine canvas — the entire
   "this is the binding" cue has to come from the inner edge of each
   page. Two layers stacked:
     1. A bold inset border-right/left at the seam (linear-gradient
        with a hard stop) reads unambiguously as a binding line even
        when a full-bleed photo covers everything else.
     2. A wider eased gradient simulates the paper curving into the
        binding — fades over ~25% of the page width.
   Both together produce the "open book" feel without obscuring photos. */
.bp-page--left::before {
  background:
    linear-gradient(
      to left,
      rgba(0, 0, 0, 0.30) 0%,
      rgba(0, 0, 0, 0.30) 0.4%,
      transparent 0.5%
    ),
    linear-gradient(
      to left,
      rgba(0, 0, 0, 0.22) 0%,
      rgba(0, 0, 0, 0.14) 3%,
      rgba(0, 0, 0, 0.07) 9%,
      rgba(0, 0, 0, 0.025) 16%,
      transparent 25%
    );
}

.bp-page--right::before {
  background:
    linear-gradient(
      to right,
      rgba(0, 0, 0, 0.30) 0%,
      rgba(0, 0, 0, 0.30) 0.4%,
      transparent 0.5%
    ),
    linear-gradient(
      to right,
      rgba(0, 0, 0, 0.22) 0%,
      rgba(0, 0, 0, 0.14) 3%,
      rgba(0, 0, 0, 0.07) 9%,
      rgba(0, 0, 0, 0.025) 16%,
      transparent 25%
    );
}

/* ── Slots ─────────────────────────────────────────────────────────────────── */

.bp-slot {
  position: absolute;
  overflow: hidden;
  /* Flex column so the caption (when show_caption is on) sits in a
     reserved area BELOW the image instead of overlaying it. Mirrors
     the editor's .preview-slot and pdfgen's captionReserveH layout —
     image and caption never overlap. */
  display: flex;
  flex-direction: column;
  /* Above the trim indicator (z-index 1) so full-bleed photos cover the
     inset border on the sides they touch — no awkward frame around them. */
  z-index: 6;
}

.bp-slot img {
  width: 100%;
  /* `flex: 1 1 0; min-height: 0` lets the image take the slot's
     remaining height after the caption claims its natural height.
     Without `min-height: 0` the image's intrinsic content size would
     prevent flex from shrinking it. */
  flex: 1 1 0;
  min-height: 0;
  object-fit: cover;
  display: block;
}

.bp-empty-slot {
  position: absolute;
  background: #ffffff;
}

/* ── Slot captions ─────────────────────────────────────────────────────────── */
/* Caption is a normal flex child of .bp-slot, sitting below the image
   in its own reserved band. Mirrors the editor's .slot-caption-area
   and pdfgen's captionReserveH band — image and caption never overlap.
   font-size is scaled from pt→px via the --bp-page-h-px / --bp-page-h
   ratio (same formula the cover text boxes use). overflow:hidden keeps
   long caption text from escaping the slot the way pdfgen clips its
   CellFormat. flex-shrink: 0 protects the caption's natural height
   when the image flex grows to fill the rest. */
.bp-slot-caption {
  flex-shrink: 0;
  padding: 0.12em 0.25em 0.18em;
  line-height: 1.2;
  overflow: hidden;
  white-space: pre-wrap;
  word-break: break-word;
  pointer-events: none;
}

/* ── Interior text boxes ───────────────────────────────────────────────────── */
/* Mirror of .bp-cover-tb for interior pages. Positioned in .bp-page-trim
   so percentages stay trim-relative; on top of slots (z-index 7) because
   text boxes are foreground overlays. Rotation is applied via inline
   transform from the template. */
.bp-page-textbox {
  position: absolute;
  overflow: hidden;
  word-break: break-word;
  white-space: pre-wrap;
  line-height: 1.2;
  pointer-events: none;
  z-index: 7;
}

/* Page numbers render through the regular .bp-page-textbox styling
   (driven by the shared PageNumberStyle on settings); no separate
   .bp-page-number rule is needed in the read-only preview. */


/* ── Spread label / info ────────────────────────────────────────────────────── */

.bp-spread-label {
  font-size: 0.8rem;
  color: var(--text-muted);
  text-align: center;
  margin-top: 0.5rem;
}

.bp-spread-info {
  text-align: center;
  font-size: 0.85rem;
  color: var(--text-secondary);
}

/* Three-part label beneath the cover spread (back | spine | front) */
.bp-cover-label {
  display: flex;
  flex-direction: row;
  width: calc(
    var(--bp-cover-h-px) * var(--bp-cover-page-w) / var(--bp-cover-page-h) * 2 +
    var(--bp-cover-h-px) * var(--bp-spine-w) / var(--bp-cover-page-h)
  );
  justify-content: space-between;
  padding: 0 0.25rem;
}

/* ── Dark mode ───────────────────────────────────────────────────────────── */

[data-theme="dark"] .bp-page--left {
  box-shadow:
    4px 0 8px rgba(0, 0, 0, 0.25),      /* spine-side */
    -4px 0 10px rgba(0, 0, 0, 0.25),    /* outer (left) edge */
    0 3px 8px rgba(0, 0, 0, 0.18),      /* bottom */
    0 -3px 8px rgba(0, 0, 0, 0.12);     /* top */
}

[data-theme="dark"] .bp-page--right {
  box-shadow:
    -4px 0 8px rgba(0, 0, 0, 0.25),     /* spine-side */
    4px 0 10px rgba(0, 0, 0, 0.25),     /* outer (right) edge */
    0 3px 8px rgba(0, 0, 0, 0.18),      /* bottom */
    0 -3px 8px rgba(0, 0, 0, 0.12);     /* top */
}

[data-theme="dark"] .bp-page--left::after {
  box-shadow:
    inset 0 6px 12px -6px rgba(0, 0, 0, 0.20),
    inset 0 -6px 12px -6px rgba(0, 0, 0, 0.20),
    inset 6px 0 12px -6px rgba(0, 0, 0, 0.20);
}

[data-theme="dark"] .bp-page--right::after {
  box-shadow:
    inset 0 6px 12px -6px rgba(0, 0, 0, 0.20),
    inset 0 -6px 12px -6px rgba(0, 0, 0, 0.20),
    inset -6px 0 12px -6px rgba(0, 0, 0, 0.20);
}

[data-theme="dark"] .bp-cover-canvas::after {
  box-shadow:
    inset 0 6px 12px -6px rgba(0, 0, 0, 0.20),
    inset 0 -6px 12px -6px rgba(0, 0, 0, 0.20),
    inset 6px 0 12px -6px rgba(0, 0, 0, 0.20),
    inset -6px 0 12px -6px rgba(0, 0, 0, 0.20);
}

/* Dark mode: deeper gutter curl gradient (~1.8x alpha) so the crease still
   reads against darker surroundings. */
[data-theme="dark"] .bp-page--left::before {
  background: linear-gradient(
    to left,
    rgba(0, 0, 0, 0.18) 0%,
    rgba(0, 0, 0, 0.117) 2%,
    rgba(0, 0, 0, 0.063) 6%,
    rgba(0, 0, 0, 0.027) 11%,
    rgba(0, 0, 0, 0.007) 16%,
    rgba(255, 255, 255, 0.05) 18%,
    transparent 22%
  );
}

[data-theme="dark"] .bp-page--right::before {
  background: linear-gradient(
    to right,
    rgba(0, 0, 0, 0.18) 0%,
    rgba(0, 0, 0, 0.117) 2%,
    rgba(0, 0, 0, 0.063) 6%,
    rgba(0, 0, 0, 0.027) 11%,
    rgba(0, 0, 0, 0.007) 16%,
    rgba(255, 255, 255, 0.05) 18%,
    transparent 22%
  );
}

/* Keep printed page white in dark mode */
[data-theme="dark"] .bp-page {
  background: #ffffff;
}

[data-theme="dark"] .bp-nav-prev,
[data-theme="dark"] .bp-nav-next {
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.4);
}

[data-theme="dark"] .bp-nav-prev:hover:not(:disabled),
[data-theme="dark"] .bp-nav-next:hover:not(:disabled) {
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.55);
}

/* ── Bar button label variants ─────────────────────────────────────────────
   The Order button has two label spans: the long one shows by default; the
   short one is hidden until the mobile breakpoint swaps them. Other bar
   buttons just have a single .bp-bar-btn-label that gets hidden on mobile
   so the chevron/arrow icon carries the meaning (aria-label preserves
   accessible naming for assistive tech). */
.bp-bar-btn-label-short {
  display: none;
}

/* ── Responsive ──────────────────────────────────────────────────────────── */

@media (max-width: 768px) {
  .bp-stage {
    padding: 1rem 4rem;
  }
}

@media (max-width: 600px) {
  /* Override the JS-set inline `style="width: …px"` so the bar spans the
     viewport (minus a small inset) instead of matching the spread width.
     `!important` is acceptable here because we're countering an inline
     style we don't fully control — the JS `updateBarWidth` skips on
     mobile to avoid stale values, but a stale value left from a desktop
     resize still needs to be defeated by CSS. */
  .bp-bar {
    width: auto !important;
    margin: var(--space-md) 0.75rem 0;
    max-width: calc(100vw - 1.5rem);
    padding: 0 0.5rem;
    gap: 0.5rem;
    height: 48px;
  }

  /* Hide verbose label text on the side buttons so the centered title
     gets the bulk of the horizontal space. Chevron / arrow / icon
     glyphs in the button content carry the meaning, and aria-label on
     the anchor preserves the accessible name. */
  .bp-bar .bp-bar-btn .bp-bar-btn-label {
    display: none;
  }
  /* Order Book button: swap the long label for the short "Order" label
     so the primary CTA still has text + arrow at mobile widths. */
  .bp-bar .bp-bar-btn--primary .bp-bar-btn-label-short {
    display: inline;
  }
  /* Drop bar button padding so icon-only buttons stay compact. */
  .bp-bar .bp-bar-btn {
    padding-left: 0.55rem;
    padding-right: 0.55rem;
  }
  .bp-bar-title {
    font-size: 0.95rem;
  }
}

@media (max-width: 480px) {
  .bp-stage {
    /* Horizontal padding reserves room for the absolutely-positioned nav
       arrows (40px wide, left/right: 1rem) so they sit outside the
       spread rather than overlapping it. */
    padding: 1rem 3.5rem;
  }
}
