/* Inter, self-hosted rather than pulled from the Google Fonts CDN: that would
   reintroduce a third-party request, break offline-after-load, and hand
   visitor IPs to Google (a GDPR problem for an EU-hosted public site).
   SIL Open Font License — see OFL-Inter.txt. One variable file, weights 400-800. */
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 400 800;
  font-display: swap;
  src: url('inter.woff2') format('woff2');
}

/* Dark is the default, and these are the interface's colours: every one of
   them is redefined by the light theme below and nothing else is. Two things
   deliberately stay literal because they are not interface at all:

   - the PAPER — the white sheet, the canvas placeholders drawn on it, the
     folio numbers and the reader page — which is the thing being printed and
     must look the same whichever theme you work in;
   - anything painted ON TOP of artwork — the remove button, the page arrows,
     the crease lines — which sits on someone's image, not on the app.

   Both read identically in either theme, so a token would only invite a
   later change that makes the light theme print differently from the dark. */
:root {
  color-scheme: dark;
  /* Arrow button plus its gap. app.js reads this to reserve the same space,
     so the two cannot drift apart when the media query changes it. */
  --nav-gutter: 62px;
  --nav-size: 46px;
  --bg: #14151a;
  --panel: #1c1e25;
  --panel-2: #23262f;
  --topbar: #0e0f13;
  /* Behind a thumbnail, for the moment before its image has decoded. */
  --well: #0e0f13;
  --line: #31353f;
  --line-hover: #4a505e;
  /* The slot buttons are small and sit on a darker fill, so their hover edge
     is brighter than the topbar controls'. */
  --line-hover-2: #59616f;
  --ink: #e8e9ec;
  --ink-mid: #bdc2cc;
  --ink-dim: #9298a5;
  --ink-faint: #6b7280;
  /* Text on an --ink fill: the selected view, an armed slot button. */
  --ink-inv: #0e0f13;
  --btn-ink: #b9bfca;
  --accent: #ff4d2e;
  /* The accent where it is text rather than a fill. On the dark panels the
     brand orange reads at 5:1, so here they are the same colour. */
  --accent-ink: #ff4d2e;
  /* The tint a slot takes while an image is held over it. */
  --accent-wash: #2a1f1c;
  --track: #646b7b;
  --paper-shadow: rgba(0, 0, 0, 0.45);
  --panel-shadow: rgba(0, 0, 0, 0.55);
  --scrim: rgba(8, 9, 12, 0.74);
  --radius: 6px;
  --gap: 12px;
  /* The pages panel's width, and so also how far it slides out of the row. */
  --pages-w: 340px;
}

/* Light. Not an inversion: the point of the dark theme is that white paper
   glows against a near-black stage, and the light theme has to keep the sheet
   reading as paper, so the stage stays the darkest surface in the window and
   the panels float on it — grey well, near-white panels, white bar. The
   accent does not move: it is the brand colour and white on it measures the
   same either way. */
:root[data-theme="light"] {
  color-scheme: light;
  --bg: #d7dbe2;
  --panel: #fafbfc;
  --panel-2: #eef0f4;
  --topbar: #ffffff;
  --well: #d5d9e0;
  --line: #d0d5dd;
  --line-hover: #a6adba;
  --line-hover-2: #8f97a5;
  --ink: #16181d;
  --ink-mid: #3f4552;
  --ink-dim: #5f6673;
  --ink-faint: #7d8492;
  --ink-inv: #fbfcfd;
  --btn-ink: #454b58;
  --accent-ink: #d23c1a;
  --accent-wash: #ffe8e1;
  --track: #a8aeba;
  --paper-shadow: rgba(20, 24, 33, 0.24);
  --panel-shadow: rgba(20, 24, 33, 0.22);
  /* The guide dims the window to point at things, so the scrim stays dark —
     but nowhere near as dark, or a light page would black out. */
  --scrim: rgba(26, 29, 36, 0.52);
}

* { box-sizing: border-box; }

/* Flex/grid display rules below would otherwise beat the hidden attribute. */
[hidden] { display: none !important; }

html, body {
  height: 100%;
  margin: 0;
}

body {
  background: var(--bg);
  color: var(--ink);
  font: 14px/1.45 'Inter', ui-sans-serif, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  display: flex;
  flex-direction: column;
}

/* ---------- top bar ---------- */

.topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
  padding: 10px 16px;
  background: var(--topbar);
}

.topbar-left { display: flex; align-items: center; gap: 10px; }

.help-btn {
  appearance: none;
  width: 20px;
  height: 20px;
  padding: 0;
  border-radius: 50%;
  border: 1px solid var(--line);
  background: transparent;
  color: var(--ink-dim);
  font: inherit;
  font-size: 11px;
  line-height: 1;
  cursor: pointer;
  flex: none;
}

.help-btn:hover { color: var(--ink); border-color: var(--ink-dim); }

/* The same 20px circle as the help button it sits beside. The icon is the
   theme the button turns on rather than the one you are in, which is what its
   label says as well — a sun in the dark theme, a moon in the light one. */
.theme-btn {
  display: flex;
  align-items: center;
  justify-content: center;
}

.theme-btn svg {
  display: block;
  width: 12px;
  height: 12px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.5;
  stroke-linecap: round;
}

.theme-btn .moon { display: none; }
:root[data-theme="light"] .theme-btn .sun { display: none; }
:root[data-theme="light"] .theme-btn .moon { display: block; }

.setup-group { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.exports { display: flex; align-items: center; gap: 10px; }

.topbar h1 {
  font-size: 15px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  margin: 0;
  display: flex;
  align-items: baseline;
  gap: 12px;
}

.topbar h1 .sub {
  font-size: 11px;
  letter-spacing: 0.06em;
  text-transform: none;
  color: var(--ink-dim);
  font-weight: 400;
}

/* Two groups that have nothing to do with each other: what the sheet IS, and
   what to do with it. Only the two of them sit in this row, so the column gap
   separates the groups while the 10px inside each one is untouched — at the
   same 10px they ran together and the Margin select read as a fourth button
   in the export row. The row gap stays 10 for when the bar wraps. */
.topbar-right {
  display: flex;
  align-items: center;
  gap: 10px 26px;
  flex-wrap: wrap;
}

/* Uppercase and tracked to match the Sheet and Margin labels in the top bar,
   so the readout reads as a label and a value rather than as a sentence. Kept
   at the readout's own 11px rather than the top bar's 10: it belongs to this
   line, and a smaller word inside it only wobbles the baseline. */
.pagesize-label {
  text-transform: uppercase;
  letter-spacing: 0.12em;
}

/* Tabular figures so the numbers do not jitter sideways as the sheet size
   changes, which they would against a right edge. */
.pagesize {
  flex: none;
  font-size: 11px;
  color: var(--ink-dim);
  font-variant-numeric: tabular-nums;
}

.ctl { display: flex; align-items: center; gap: 6px; }

.ctl-label {
  font-size: 10px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-dim);
}

/* ---------- controls ---------- */

.seg {
  display: inline-flex;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  overflow: hidden;
}

.seg button {
  appearance: none;
  border: 0;
  background: transparent;
  color: var(--ink-dim);
  font: inherit;
  font-size: 12px;
  padding: 6px 12px;
  cursor: pointer;
}

.seg button + button { border-left: 1px solid var(--line); }
.seg button:hover { color: var(--ink); background: var(--panel-2); }
.seg button.on { background: var(--ink); color: var(--ink-inv); font-weight: 600; }

/* Sheet and margin are value pickers rather than modes, so they are native
   selects — one control each instead of four buttons, with room to say what
   a sheet folds down to. */
.picker {
  appearance: none;
  -webkit-appearance: none;
  border: 1px solid var(--line);
  background-color: var(--panel-2);
  color: var(--ink);
  font: inherit;
  font-size: 12px;
  padding: 6px 28px 6px 10px;
  border-radius: var(--radius);
  cursor: pointer;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5 6 6.5 11 1.5' fill='none' stroke='%239298a5' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 9px center;
  background-size: 10px 7px;
}

/* A data URI cannot read a custom property, so the chevron's colour has to
   be restated rather than tokenised. --ink-dim's value, by hand. */
:root[data-theme="light"] .picker {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5 6 6.5 11 1.5' fill='none' stroke='%235f6673' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
}

.picker:hover { border-color: var(--line-hover); }
.picker:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

.btn {
  appearance: none;
  border: 1px solid var(--line);
  background: var(--panel-2);
  color: var(--ink);
  font: inherit;
  font-size: 12px;
  padding: 6px 14px;
  border-radius: var(--radius);
  cursor: pointer;
}

.btn:hover { border-color: var(--line-hover); }
.btn.small { padding: 4px 10px; }
.btn.primary { background: var(--accent); border-color: var(--accent); color: #fff; font-weight: 600; }
.btn.primary:hover { filter: brightness(1.1); }
.btn[disabled] { opacity: 0.4; cursor: default; }

.link {
  appearance: none;
  border: 0;
  background: none;
  color: var(--ink-dim);
  font: inherit;
  font-size: 12px;
  text-decoration: underline;
  cursor: pointer;
  padding: 0;
}

.link:hover { color: var(--accent-ink); }

/* ---------- layout ---------- */

/* The two panes read as separate panels: each is a bordered surface floating
   on the app background, with the same gap between them as around them. */
main {
  flex: 1;
  display: flex;
  gap: var(--gap);
  padding: var(--gap);
  min-height: 0;
}

/* min-width:0 stops a wide preview from blowing out its column. */
.pane {
  min-width: 0;
  min-height: 0;
  display: flex;
  flex-direction: column;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
}

/* Fixed width, and it leaves the row sideways rather than narrowing to
   nothing: shrinking it would reflow its two-column slot grid the whole way
   out. The negative margin is the width plus the gap, so the row closes up
   exactly and the preview takes every pixel.

   visibility is what takes the panel out of the tab order and the
   accessibility tree — parked off-screen it is still focusable, so Tab would
   walk through eight invisible pages. It flips only once the slide is over,
   which is what the delay on the transition is for. */
.pages-pane {
  flex: 0 0 var(--pages-w);
  padding: 12px;
  overflow-y: auto;
  transition: margin-left 0.22s ease, visibility 0s linear 0s;
}

body.panel-hidden .pages-pane {
  margin-left: calc((var(--pages-w) + var(--gap)) * -1);
  visibility: hidden;
  transition: margin-left 0.22s ease, visibility 0s linear 0.22s;
}

@media (prefers-reduced-motion: reduce) {
  .pages-pane,
  body.panel-hidden .pages-pane { transition: none; }
}

/* Focus mode, the other half of the same idea: the panel button gives the
   preview the width, this gives it the height. Two bars go — the top bar, and
   the folding instructions — because neither says anything while you are
   reading the zine. Measured at 1400 x 900 that is 110px handed back to a page
   that is always the taller of its two constraints.

   The view toggle stays. It is the way back to the sheet and the way out of
   focus mode, and a mode with no visible exit is a trap rather than a mode. */
body.focus-mode .topbar,
body.focus-mode .fold { display: none; }

/* The right-hand side is two panels, not one: the view toggle sits in its own
   bar above the stage, separated by the same gap as the columns. */
.preview-col {
  flex: 1;
  min-width: 0;
  min-height: 0;
  display: flex;
  flex-direction: column;
  gap: var(--gap);
}

/* A row, so the panel button sits beside the view toggle. .pane is a column
   flex container, which would stretch both of them across the full width of
   the bar and ring a lot of empty space; as a row the stretch is vertical
   instead, which is what keeps the button exactly as tall as the toggle. */
.toolbar-pane {
  flex: none;
  flex-direction: row;
  align-items: stretch;
  gap: 10px;
  padding: 12px;
}

/* Slides the pages panel away when you want the width for the preview. It
   lives in the toolbar rather than in the panel it hides, because a button
   inside the panel goes with it and leaves no way back. A chevron would have
   been the obvious glyph, but ‹ and › already mean "page through the zine"
   in the panel immediately below. */
.panel-btn {
  appearance: none;
  flex: none;
  padding: 0 7px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: transparent;
  color: var(--ink-dim);
  display: flex;
  align-items: center;
  cursor: pointer;
}

.panel-btn:hover { color: var(--ink); border-color: var(--ink-dim); }

.panel-btn svg {
  display: block;
  width: 16px;
  height: 16px;
}

.panel-btn .frame {
  fill: none;
  stroke: currentColor;
  stroke-width: 1.3;
}

/* The panel itself, drawn in place when it is showing and emptied when it is
   not, so the button says which state you are in and not only what it does. */
.panel-btn .bar { fill: currentColor; }
body.panel-hidden .panel-btn .bar { opacity: 0.22; }

/* Borrows the panel button's box the way .theme-btn borrows the help button's,
   which also means it goes with it below 900px. At the far end of the bar
   rather than beside it: this one comes and goes with the reading views, and
   next to the panel button it would shove the view toggle sideways each time. */
.focus-btn { margin-left: auto; }

.focus-btn path {
  fill: none;
  stroke: currentColor;
  stroke-width: 1.5;
  stroke-linecap: round;
}

/* Corners opening outwards to go in, closing inwards to come back — the glyph
   is what the button will do, as with the theme button's sun and moon. */
.focus-btn .collapse { display: none; }
body.focus-mode .focus-btn .expand { display: none; }
body.focus-mode .focus-btn .collapse { display: block; }

.stage-pane {
  flex: 1;
  min-height: 0;
  padding: 12px;
}

/* Both panes' headers share a height so their contents sit on one line: the
   view toggle is taller than the "Pages" label, and without this the two
   start level but read as misaligned. */
.pane-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  min-height: 32px;
  margin-bottom: var(--gap);
}

/* Optical, not geometric: the line boxes are already centred and the baselines
   sit within 0.3px, but "Pages" and "Clear all" have no descenders while the
   view toggle's labels do, which leaves the left pair reading high. Measured
   against Inter's metrics, so this is exact on every platform rather than
   tuned to whatever the OS happened to supply. */
.pages-pane .pane-head {
  position: relative;
  top: 1px;
  /* the 1px nudge is visual only, so give the margin that pixel back */
  margin-bottom: calc(var(--gap) + 1px);
}

.pane-head h2 {
  margin: 0;
  font-size: 11px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-dim);
}

.hint {
  margin: 0 0 12px;
  font-size: 11px;
  color: var(--ink-dim);
}

/* ---------- page slots ---------- */

.slots {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
}

.slot {
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--panel-2);
  overflow: hidden;
  display: flex;
  flex-direction: column;
}

.slot.drag { border-color: var(--accent); background: var(--accent-wash); }
.slot.dragging { opacity: 0.35; }

/* The image is a pan surface: drag it to reposition the crop. touch-action
   keeps the browser from turning the drag into a scroll. */
.slot-drop.filled { cursor: grab; touch-action: none; }
.slot-drop.panning { cursor: grabbing; }

.reader-page img,
.reader-page .half { touch-action: none; }
.reader-page:has(img) { cursor: grab; }
.reader-page.panning,
.half.panning { cursor: grabbing; }

.slot-name.draggable { cursor: grab; }
.slot-name.draggable:active { cursor: grabbing; }

.slot-drop {
  position: relative;
  aspect-ratio: 74.25 / 105;
  /* As a flex item its min-height defaults to the content's, which lets a
     tall image override the aspect ratio and stretch the slot. The page shape
     must win — object-fit crops the overflow. */
  min-height: 0;
  overflow: hidden;
  background: var(--well);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}

/* Position and size come from applyPreview as percentages of the box, so the
   thumbnail shows exactly the crop the sheet will print. */
.slot-drop img { display: block; }

.slot-drop.empty:hover { filter: brightness(1.12); }

/* A spread slot is two pages wide and occupies both grid columns. */
.slot-drop.span { aspect-ratio: 148.5 / 105; }

/* Removal sits on the image, clear of the fit and position controls. */
.slot-remove {
  position: absolute;
  top: 6px;
  right: 6px;
  z-index: 2;
  width: 24px;
  height: 24px;
  padding: 0;
  border-radius: 50%;
  border: 1px solid rgba(255, 255, 255, 0.25);
  background: rgba(12, 13, 17, 0.6);
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
  color: #fff;
  font: inherit;
  font-size: 15px;
  line-height: 1;
  cursor: pointer;
}

.slot-remove:hover { background: var(--accent); border-color: var(--accent); }

.slot-crease,
.reader-crease {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 50%;
  width: 1px;
  background: rgba(0, 0, 0, 0.35);
  pointer-events: none;
}

.slot-actions .span.on,
.slot-actions .span.on:hover { background: var(--accent); border-color: var(--accent); color: #fff; }

.slot-empty {
  color: rgba(0, 0, 0, 0.42);
  font-size: 32px;
  font-weight: 800;
}

/* Name on its own line above the buttons — sharing one row truncated it. */
.slot-meta {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 7px 8px;
  font-size: 10px;
  color: var(--ink-dim);
  border-top: 1px solid var(--line);
}

.slot-name {
  letter-spacing: 0.06em;
  text-transform: uppercase;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  color: var(--ink);
}

.slot-actions { display: flex; align-items: center; gap: 5px; flex-wrap: wrap; }

input.zoom {
  appearance: none;
  -webkit-appearance: none;
  width: 100%;
  height: 5px;
  margin: 1px 0 0;
  border-radius: 3px;
  /* Not --line: against --panel-2 that is 1.2:1, so the track vanished and the
     thumb read as a stray dot rather than the handle of a slider. */
  background: var(--track);
  cursor: pointer;
}

input.zoom::-webkit-slider-thumb {
  appearance: none;
  -webkit-appearance: none;
  width: 11px;
  height: 11px;
  border-radius: 50%;
  background: var(--ink-dim);
  border: 0;
  cursor: grab;
}

input.zoom::-moz-range-thumb {
  width: 11px;
  height: 11px;
  border-radius: 50%;
  background: var(--ink-dim);
  border: 0;
  cursor: grab;
}

input.zoom:hover::-webkit-slider-thumb { background: var(--ink); }
input.zoom:hover::-moz-range-thumb { background: var(--ink); }

.slot-actions button {
  appearance: none;
  border: 1px solid var(--line);
  /* A filled rest state, so these read as buttons rather than as faint
     outlines against the panel. */
  background: var(--panel);
  color: var(--btn-ink);
  font: inherit;
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  line-height: 1;
  min-height: 24px;
  padding: 5px 9px;
  border-radius: 4px;
  cursor: pointer;
}

/* ↔, ⇄ and ⊕ are symbols, which read smaller than caps at the same size. */
.slot-actions .span,
.slot-actions .swap,
.slot-actions .rotate,
.slot-actions .recentre {
  font-size: 14px;
  letter-spacing: 0;
  padding: 3px 9px;
}

/* Inter's ⊕ is a thin ring, which at 14px reads lighter and smaller than the
   arrows beside it; at 16px it matches their weight, and the button stays
   24px tall. */
.slot-actions .recentre { font-size: 16px; }

/* Drawn to the size of the ↔ and ⇄ beside it, in the button's own colour so
   hover and both themes follow. */
.slot-actions .rotate svg {
  display: block;
  width: 14px;
  height: 14px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.4;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.slot-actions button:hover {
  color: var(--ink);
  border-color: var(--line-hover-2);
  background: var(--panel-2);
}

.slot-actions button.on {
  background: var(--ink);
  color: var(--ink-inv);
  border-color: var(--ink);
  font-weight: 600;
}

.slot-actions button[disabled] {
  opacity: 0.35;
  cursor: default;
  background: transparent;
}

.slot-actions button[disabled]:hover { color: var(--btn-ink); border-color: var(--line); background: transparent; }

/* ---------- stage ---------- */

/* Recessed well inside the preview panel rather than a panel of its own —
   darker than its surroundings, no border of its own. */
.stage {
  flex: 1;
  min-height: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--bg);
  border-radius: var(--radius);
  padding: 16px;
  overflow: auto;
}

.sheet-wrap {
  max-width: 100%;
  max-height: 100%;
  display: flex;
}

/* No object-fit here: renderSheetPreview sizes the element to the bitmap's
   own aspect, so any letterboxing would be the canvas background showing
   through and would read as a white border the printed sheet doesn't have. */
#sheet-canvas {
  display: block;
  background: #fff;
  box-shadow: 0 8px 30px var(--paper-shadow);
}

/* Pages, the two edge arrows over them, and the caption beneath. The arrows
   are positioned against .reader-frame so they centre on the pages, not on
   the frame-plus-caption stack. */
.reader-wrap {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  max-width: 100%;
  max-height: 100%;
  min-height: 0;
}

.reader-frame {
  position: relative;
  display: flex;
  min-height: 0;
}

.page-nav {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  width: var(--nav-size);
  height: var(--nav-size);
  padding: 0;
  border-radius: 50%;
  border: 1px solid rgba(255, 255, 255, 0.2);
  background: rgba(12, 13, 17, 0.55);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transition: background 0.12s ease, opacity 0.12s ease;
}

/* Outside the page, on the stage background. --nav-gutter is set from app.js
   so the space reserved by fitReaderPages and the offset here cannot drift. */
.page-nav.prev { left: calc(var(--nav-gutter) * -1); }
.page-nav.next { right: calc(var(--nav-gutter) * -1); }
.page-nav:hover { background: rgba(12, 13, 17, 0.85); }

.page-nav svg {
  width: 22px;
  height: 22px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2.4;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* At the first or last page there is nowhere to go — drop the arrow rather
   than leave a dead control sitting over the artwork. */
.page-nav[disabled] {
  opacity: 0;
  pointer-events: none;
}

.reader {
  display: flex;
  gap: 2px;
  max-height: 100%;
  align-items: stretch;
  justify-content: center;
}

/* --page-h is measured against the stage in renderReader so a two-page
   spread never overflows a narrow window. */
.reader-page {
  aspect-ratio: 74.25 / 105;
  /* Same reason as .slot-drop: the image must not set the page's size. */
  min-width: 0;
  min-height: 0;
  flex: none;
  height: var(--page-h, 60vh);
  background: #fff;
  box-shadow: 0 8px 30px var(--paper-shadow);
  display: flex;
  align-items: center;
  justify-content: center;
  position: relative;
}

.reader-page img { display: block; }

/* A merged spread read as a spread: one page box of two-page width. */
.reader-page.wide { aspect-ratio: 148.5 / 105; }

/* One half of a merged spread, read as a single page: the image is laid out
   at full spread width inside the page window and slid across. */
.reader-page { overflow: hidden; }
.reader-page .half {
  position: absolute;
  top: var(--page-pad, 0);
  bottom: var(--page-pad, 0);
  left: var(--page-pad, 0);
  width: calc(200% - var(--page-pad, 0px) * 2);
  overflow: hidden;
}
.reader-page .half.right { left: auto; right: var(--page-pad, 0); }

/* The white margin, the same one the printed sheet leaves. The image inside is
   positioned absolutely, so it resolves against this box rather than against
   the page — a padding on the page itself would simply be painted over. A
   spread keeps its margin on the outer edges only, which is what .half above
   is doing, so the artwork stays continuous across the fold. */
.reader-page .art {
  position: absolute;
  top: var(--page-pad, 0);
  right: var(--page-pad, 0);
  bottom: var(--page-pad, 0);
  left: var(--page-pad, 0);
  overflow: hidden;
}

.reader-page .placeholder {
  color: #c9ccd2;
  font-size: 42px;
  font-weight: 700;
}

.reader-page .folio {
  position: absolute;
  bottom: 8px;
  left: 0;
  right: 0;
  text-align: center;
  font-size: 10px;
  letter-spacing: 0.1em;
  color: #b9bcc4;
}

.reader-label {
  font-size: 11px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-dim);
  text-align: center;
  flex: none;
}

/* The printing note and the page measurements share the line under the sheet.
   The readout is the fixed part and holds the right edge; the note takes what
   is left and wraps under itself rather than pushing the readout onto a line
   of its own — measured, the note alone wants 655px and the readout 283 (426
   with a margin set), which fits on one line at 1400 and not at 1280. Wrapping
   would cost a whole extra line at both. */
.stage-foot {
  display: flex;
  align-items: baseline;
  gap: 12px;
  margin-top: var(--gap);
}

.note {
  flex: 1;
  min-width: 0;
  margin: 0;
  font-size: 11px;
  color: var(--ink-dim);
}

/* ---------- fold instructions ---------- */

/* A third panel on the same left and right edges as the two above it, rather
   than a full-bleed footer that ignored their alignment. */
.fold {
  position: relative;
  margin: 0 var(--gap) var(--gap);
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  /* Horizontal padding stays at 12 so the summary keeps the same left edge as
     the other panels' content; only the vertical padding grows. */
  padding: 14px 12px;
  font-size: 14px;
  color: var(--ink-dim);
}

.fold summary {
  cursor: pointer;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  font-size: 12px;
  color: var(--ink);
}

/* The steps and the animation open upward over the stage instead of taking
   layout space: the bar keeps its place in the flow and this floats above it,
   so the working panels never resize. It spans the bar's own edges, and is as
   tall as the animation needs without reaching the top bar — measured, 62% of
   the window leaves at least 170px above it at 1024 x 700. */
.fold-body {
  position: absolute;
  /* -1px because absolute offsets resolve against the padding box: this puts
     the overlay's border on the same line as every other panel's. */
  bottom: calc(100% + 7px);
  left: -1px;
  right: -1px;
  height: min(500px, 62vh);
  display: grid;
  /* The steps get a readable measure and the animation the rest. */
  grid-template-columns: clamp(280px, 38%, 36rem) minmax(0, 1fr);
  grid-template-rows: minmax(0, 1fr);
  gap: 16px;
  padding: 16px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: 0 -10px 34px var(--panel-shadow);
  z-index: 50;
}

/* Instructions you follow with paper in your hands, so they get the app's
   body size and looser leading rather than caption treatment. The list scrolls
   on its own if it has to; the animation beside it never does. */
.fold-steps {
  margin: 0;
  padding: 0 4px 0 24px;
  overflow-y: auto;
  line-height: 1.65;
}

.fold li { margin-bottom: 4px; }
.fold li:last-child { margin-bottom: 0; }
.fold li.now::marker { color: var(--accent-ink); }

/* Each step is a button — picking one shows that step — that still reads as
   the list it is. */
.fold-step {
  appearance: none;
  display: block;
  width: 100%;
  margin: 0;
  padding: 4px 10px;
  border: 0;
  border-radius: 4px;
  background: none;
  color: inherit;
  font: inherit;
  line-height: inherit;
  text-align: left;
  cursor: pointer;
}

.fold-step:hover { color: var(--ink); }

/* The step the animation is showing. */
.fold-step[aria-current="step"] {
  color: var(--ink);
  background: var(--accent-wash);
  box-shadow: inset 3px 0 0 var(--accent);
}

.fold-film {
  margin: 0;
  display: flex;
  flex-direction: column;
  min-width: 0;
}

/* The canvas is sized from this box, not the other way round, so the grid
   decides how big the animation is. */
.fold-screen {
  position: relative;
  flex: 1;
  min-height: 0;
  border-radius: 4px;
  overflow: hidden;
  background: var(--well);
}

.fold-screen canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}

.fold-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
  margin-top: 10px;
  font-size: 12px;
}

/* For folding along while watching. At the far end, so it stays put while
   "All steps" comes and goes. */
.fold-speed { margin-left: auto; }
.fold-speed button { padding: 4px 9px; }

.fold-play {
  display: flex;
  align-items: center;
  padding: 5px 8px;
}

.fold-play svg { width: 12px; height: 12px; fill: currentColor; }
.fold-play .play,
.fold-play.paused .pause { display: none; }
.fold-play.paused .play { display: block; }

.fold .build {
  margin: 14px 0 0;
  font-size: 11px;
  color: var(--ink-faint);
  letter-spacing: 0.04em;
}

/* ---------- swap by tapping (works without drag-and-drop) ---------- */

/* Touchscreens have no HTML5 drag events, so a page can also be armed with
   the ⇄ button and placed by tapping another page. */
.slot-actions .swap.on,
.slot-actions .swap.on:hover { background: var(--accent); border-color: var(--accent); color: #fff; }
.slot.armed { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent); }
.slot.armed .slot-drop { opacity: 0.55; }

.slots.arming .slot:not(.armed) .slot-drop { cursor: copy; }
.slots.arming .slot:not(.armed) .slot-drop::after {
  content: 'Place here';
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(12, 13, 17, 0.62);
  color: #fff;
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

/* ---------- narrow screens ---------- */

@media (max-width: 900px) {
  :root { --nav-gutter: 42px; --nav-size: 36px; }

  html, body { height: auto; }
  body { min-height: 100%; }

  /* Stack the two panes and let the document scroll rather than each pane. */
  main {
    display: block;
    flex: none;
  }

  /* Stacked, but still two distinct panels with the same gap between them. */
  .pages-pane {
    overflow: visible;
    margin-bottom: var(--gap);
  }

  /* Nothing to slide sideways out of, so the button goes and the panel is
     pinned open. Without the reset, collapsing on a wide window and then
     narrowing it would strand the panel off-screen with no way to call it
     back. Same selector as the rule it undoes: a media query adds no
     specificity of its own. */
  .panel-btn { display: none; }

  body.panel-hidden .pages-pane {
    margin-left: 0;
    visibility: visible;
  }

  .stage {
    height: 62vh;
    min-height: 320px;
  }

  .topbar { gap: 10px 14px; }
  .topbar-right { gap: 10px 14px; }
  /* Too narrow to share a line: the readout goes under the note, still against
     the right edge so it reads as a caption on the sheet rather than as more
     of the note. */
  .stage-foot { display: block; }
  .pagesize { display: block; margin-top: 4px; text-align: right; }
  .reader-page { box-shadow: 0 4px 16px var(--paper-shadow); }

  /* No room for two columns: the animation goes above the steps, at a height
     that still shows the folds, and the steps scroll under it. */
  .fold-body {
    height: min(600px, 80vh);
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: 240px minmax(0, 1fr);
  }
  .fold-film { grid-row: 1; }
}

@media (max-width: 560px) {
  .slots { grid-template-columns: 1fr 1fr; }
  .topbar h1 { font-size: 13px; }
  .topbar h1 .sub { display: none; }
}

/* ---------- first-run guide ---------- */

.tour-blocker {
  position: fixed;
  inset: 0;
  z-index: 999;
}

/* The dimming is this element's own huge box-shadow, so the rectangle itself
   stays a clear window onto whatever is being pointed at. */
.tour-hole {
  position: fixed;
  z-index: 1000;
  border-radius: 8px;
  border: 2px solid var(--accent);
  box-shadow: 0 0 0 9999px var(--scrim);
  pointer-events: none;
  transition: left 0.22s ease, top 0.22s ease, width 0.22s ease, height 0.22s ease;
}

/* No spotlight on the welcome step — collapse it so the window dims evenly. */
.tour-hole.no-spot { left: 50%; top: 50%; width: 0; height: 0; border: 0; }

.tour-card {
  position: fixed;
  z-index: 1001;
  width: 340px;
  max-width: calc(100vw - 28px);
  background: var(--panel-2);
  border: 1px solid var(--line);
  border-radius: 8px;
  box-shadow: 0 18px 50px rgba(0, 0, 0, 0.6);
  padding: 14px 16px 12px;
  transition: left 0.22s ease, top 0.22s ease;
}

/* Stacked layout: the card spans the screen and the script pins it above or
   below the spotlight instead of beside it. Nothing here touches left, top or
   transform — a stylesheet rule fighting the script over those is what put the
   card half off-screen on phones. */
.tour-card.sheet {
  width: auto;
  max-width: none;
}

.tour-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 6px;
}

.tour-count {
  font-size: 10px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-dim);
}

.tour-x {
  appearance: none;
  border: 0;
  background: none;
  color: var(--ink-dim);
  font-size: 18px;
  line-height: 1;
  padding: 0 2px;
  cursor: pointer;
}

.tour-x:hover { color: var(--ink); }

.tour-title {
  margin: 0 0 6px;
  font-size: 15px;
  font-weight: 600;
  color: var(--ink);
}

.tour-body {
  margin: 0 0 14px;
  font-size: 12.5px;
  line-height: 1.55;
  color: var(--ink-mid);
}

.tour-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  border-top: 1px solid var(--line);
  padding-top: 10px;
}

.tour-dismiss {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 11px;
  color: var(--ink-dim);
  cursor: pointer;
}

.tour-dismiss input { margin: 0; cursor: pointer; accent-color: var(--accent); }
.tour-dismiss:hover { color: var(--ink); }
.tour-buttons { display: flex; gap: 6px; flex: none; }

/* ---------- printing ---------- */

#print-area { display: none; }

@media print {
  body > * { display: none !important; }

  #print-area {
    display: block !important;
    position: fixed;
    inset: 0;
    margin: 0;
    padding: 0;
  }

  #print-area img {
    display: block;
    width: 100%;
    height: 100%;
  }
}
