/* =============================================================================
   Snapbook editor.

   MOBILE FIRST, AND MEANT LITERALLY
   The base rules below describe a phone. The desktop layout is one media query
   at the bottom, not the other way round — because a phone is where a customer
   actually has their photos, and because a layout designed wide and then
   squeezed puts four stacked panels on a 390px screen with the book itself
   getting whatever is left over. Here the book gets the screen and everything
   else is called for when it is wanted: the photo tray is a sheet that pulls
   up over the book, not a rail permanently taking a third of it.

   IT HAS TO FEEL LIKE A BOUND BOOK
   Three things do that, and none of them cost layout space:
     · the gutter — a shadow down the binding edge, because a full-bleed page
       curves into the spine and a flat rectangle does not read as paper
     · the stack — the rest of the book peeking at the fore-edge
     · the turn — the leaf swings about the spine, showing its blank white back
       on the way round, which is exactly what the customer will be holding

   Scoped under #psb-root and prefixed psb-, because on the shop this mounts
   inside a live product page with its own Bootstrap. Print geometry is not in
   this file: the page is drawn from --psb-page-ar, which editor.js sets from
   the real trim size, so a size change is one variable and never a CSS edit.
   ========================================================================== */

#psb-root {
	/* A WARM ROOM. Light, but warm light.

	   The stock version of this screen is a cool blue-grey stage under white
	   panels, which is what every SaaS editor looks like. Everything here is
	   pulled a few degrees warm instead, for two reasons that are not taste:
	   a warm neutral flatters skin tones, and skin is most of what is on this
	   screen; and a warm surround gives a WHITE page a visible edge, which a
	   cool grey one does not - and the page is full bleed, so its edge is the
	   one thing the customer has to be able to see.

	   Paper is the brightest thing here and nothing else competes with it. */
	--psb-ink:        #1c1e24;
	--psb-ink-soft:   #6e7683;
	--psb-ink-on:     #ffffff;   /* text ON an ink-filled button */
	--psb-line:       #e6e1d9;   /* a warm hairline, not a cold grey */
	--psb-panel:      #fffdfa;   /* bars, drawer, filmstrip */
	--psb-panel-2:    #f4f0e9;   /* a control raised off a panel */
	--psb-accent:     #f15a24;   /* the shop's orange — pps-runtime.css */
	--psb-accent-ink: #ffffff;

	/* The stage is deliberately DARKER than paper. A white photo on a
	   near-white stage has no edge, and a full-bleed print is exactly the case
	   where the customer needs to see where their page stops. */
	--psb-bg:         #e9e4db;

	--psb-bar-h:      54px;
	--psb-film-h:     118px;
	--psb-tray-w:     332px;

	--psb-page-ar:    1.4375;    /* 5.75 / 4.00 — editor.js overrides */
	--psb-cover-ar:   1.5;      /* one cover panel, 6 x 4in — NOT the flat wrap */
	--psb-radius:     12px;
	--psb-safe-b:     env(safe-area-inset-bottom, 0px);

	position: fixed;
	inset: 0;
	z-index: 2000;
	display: none;
	flex-direction: column;
	background: radial-gradient(120% 90% at 50% -8%, #f7f3ec 0%, var(--psb-bg) 58%, #dcd5c9 100%);
	color: var(--psb-ink);
	font: 400 14px/1.45 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
	-webkit-font-smoothing: antialiased;
	overscroll-behavior: contain;
}
#psb-root.is-open { display: flex; }

/* The product page keeps scrolling behind a fixed overlay on iOS unless the
   document itself is frozen. editor.js adds this to <html>. */
html.psb-locked, html.psb-locked body { overflow: hidden; height: 100%; }

:where(#psb-root) *, :where(#psb-root) *::before, :where(#psb-root) *::after { box-sizing: border-box; }

/* :where() on the scope, deliberately.
   These are RESETS — they undo what the shop's Bootstrap does to a bare
   element, and they must lose to every component rule below. Written as
   `#psb-root button` they carry an id's specificity and beat a class, which
   is how every filled button in this file (the cart CTA, Add photos, Done)
   silently lost its background. :where() contributes nothing, so the reset
   ends up weaker than `.psb-cta` and the component wins, which is the whole
   point of a reset. */
:where(#psb-root) button {
	font: inherit; color: inherit; border: 0; background: none;
	padding: 0; margin: 0; cursor: pointer; border-radius: 8px;
	-webkit-tap-highlight-color: transparent;
}
/* disabled is a STATE, not a reset — it is meant to win */
#psb-root button:disabled { cursor: not-allowed; opacity: .4; }
:where(#psb-root) img { display: block; max-width: none; }

/* EVERY icon, once. Each component used to restate fill/stroke for its own
   svg, so the ones whose rule set only a size - the zoom buttons, the chip
   close - inherited the browser's default fill and rendered as black blobs.
   At :where() specificity this is a reset: any component that wants a
   different weight still wins. */
:where(#psb-root) svg {
	fill: none; stroke: currentColor;
	stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round;
	flex: 0 0 auto;
}
#psb-root :focus-visible { outline: 2px solid var(--psb-accent); outline-offset: 2px; }

/* ================================================================= top bar */

.psb-bar {
	flex: 0 0 var(--psb-bar-h);
	display: flex; align-items: center; gap: 6px;
	padding: 0 10px 0 4px;
	background: var(--psb-panel);
	border-bottom: 1px solid var(--psb-line);
	position: relative; z-index: 6;
}
/* The bar holds the way back, the preview and the cart, and the spacer that
   pushes the last two to the right. Nothing else survived: the title, the page
   count, the trim size and undo/redo were all either fixed for the whole
   session or reachable another way. */
.psb-bar-spacer { display: block; flex: 1 1 auto; }

/* 44px is the smallest thing a thumb hits reliably, so everything tappable is
   at least that and the icon inside is smaller than its target. */
.psb-iconbtn {
	width: 44px; height: 44px; flex: 0 0 auto;
	display: inline-flex; align-items: center; justify-content: center;
	color: var(--psb-ink-soft);
}
.psb-iconbtn:hover:not(:disabled) { color: var(--psb-ink); }
.psb-iconbtn svg { width: 20px; height: 20px; fill: none; stroke: currentColor; stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round; }

.psb-ghost {
	height: 40px; padding: 0 10px; white-space: nowrap;
	display: inline-flex; align-items: center; gap: 7px;
	font-size: 13px; font-weight: 500; color: var(--psb-ink-soft);
}
.psb-ghost span { display: none; }
.psb-ghost svg { width: 19px; height: 19px; fill: none; stroke: currentColor; stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round; }

.psb-cta {
	height: 38px; padding: 0 14px; white-space: nowrap; flex: 0 0 auto;
	display: inline-flex; align-items: center; gap: 6px;
	/* INK, NOT ORANGE. Orange is the accent that marks the page you are on and
	   the photo you are about to use; a second orange the same size as the
	   first means neither of them is pointing at anything. Ink is the only
	   thing in the bar this dark, so it is unmissable without shouting. */
	background: var(--psb-ink); color: #fff;
	font-size: 13px; font-weight: 600;
	box-shadow: 0 1px 2px rgba(28,30,36,.28);
}
.psb-cta:hover:not(:disabled) { background: #000; }
.psb-cta b { font-weight: 700; }

/* A book that is not a size we sell cannot be bought, so the button says what
   it can do instead of offering a price it would refuse. Quieter, but never
   disabled — a dead button tells nobody why. */
.psb-cta.is-wait { background: var(--psb-panel-2); color: var(--psb-ink); box-shadow: inset 0 0 0 1px var(--psb-line); }
.psb-cta.is-wait:hover { background: #ece6db; filter: none; }
.psb-cta.is-wait b { color: var(--psb-accent); }
/* empty on a ready button — no price, so no gap before it either */
.psb-cta b:empty { display: none; }


/* ==================================================================== body */

/* On a phone the tray is a sheet, not a column, so these two are lifted out to
   be siblings of the filmstrip and ordered by hand. */
.psb-body { display: contents; }

/* =================================================================== stage */

.psb-stage {
	order: 2;
	flex: 1 1 auto; min-height: 0; min-width: 0;
	display: flex; flex-direction: column; align-items: center; justify-content: center;
	padding: 12px 10px 6px; gap: 10px; position: relative;
}

.psb-sheet-wrap {
	position: relative;
	display: flex; align-items: center; justify-content: center;
	flex: 1 1 auto; min-height: 0; width: 100%;
	/* No side gutters any more: the arrows used to need them and the arrows
	   are under the book now, so the width is the page's. */
	padding: 0 8px;
	perspective: 1800px;          /* the room a turning leaf needs */

	/* The leaf swings about its LEFT edge, so at the end of the turn it is
	   somewhere off to the left of where the page was — and with nothing to
	   stop it that means over the photo tray and off the window. A book turns
	   its pages inside itself; so does this. */
	overflow: hidden;
}

/* ----------------------------------------------------------- the spread -- */

/* Two leaves meeting at the spine. The pages TOUCH — a gap between them would
   be a gap that does not exist in the object. */
.psb-spread {
	display: flex; align-items: stretch; flex: 0 0 auto;
	/* The book slides between its three places on the table - right when it is
	   closed on the front cover, centre while it is open, left when it is
	   closed on the back. Same curve as the page turn, so the cover opening
	   and the book settling read as one movement. See stageShift(). */
	transition: transform .43s cubic-bezier(.35,.02,.22,1);
}

/* The back of the previous leaf. Blank, because every sheet in a snapbook
   prints on one side — which is the fact this page exists to convey.
   Printbox stamps THIS PAGE CAN NOT BE EDITED across it; that is an error
   message where a piece of paper should be, and it tells the customer what
   they cannot do instead of what they are getting. */
.psb-verso-page {
	position: relative; flex: 0 0 auto;
	display: none; align-items: center; justify-content: center;
	background: #fcfcfa;                       /* paper, very slightly warm */
	border-radius: 2px 0 0 2px;
	box-shadow:
		0 0 0 1px rgba(16,24,40,.10),
		0 2px 4px rgba(16,24,40,.10),
		0 18px 44px rgba(16,24,40,.18);
}
.psb-verso-label {
	display: none;
	font-size: 11px; letter-spacing: .06em; text-transform: uppercase;
	color: #b8bec7; font-weight: 600; user-select: none;
}
/* the spine shadow, mirrored — this half of the gutter falls the other way */
.psb-verso-page::after {
	content: ""; position: absolute; right: 0; top: 0; bottom: 0; width: 8%;
	background: linear-gradient(to left,
		rgba(0,0,0,.24) 0%, rgba(0,0,0,.10) 36%, rgba(0,0,0,.02) 74%, rgba(0,0,0,0) 100%);
}
/* and the rest of the book under it, peeking at ITS fore-edge (the left) */
.psb-verso-page::before {
	content: ""; position: absolute; z-index: -1;
	left: -4px; right: 6px; top: 3px; bottom: -3px;
	background: #f7f8f9; border-radius: 2px;
	box-shadow: 0 0 0 1px rgba(16,24,40,.07), 0 4px 10px rgba(16,24,40,.10);
}

/* ------------------------------------------------------------- the page -- */

.psb-sheet {
	position: relative;
	/* width/height are set by fitSheet() in editor.js — see the note there.
	   flex:0 0 auto so a mismeasurement overflows visibly rather than being
	   quietly squashed out of proportion. */
	flex: 0 0 auto;
	background: #fff;
	border-radius: 2px;
	/* Layered, not soft: a hairline so a white photo still has an edge, a close
	   shadow for the sheet, a wide one to lift it off the stage. One blurry
	   shadow reads as fog; these three read as paper. */
	box-shadow:
		0 0 0 1px rgba(16,24,40,.10),
		0 2px 4px rgba(16,24,40,.10),
		0 18px 44px rgba(16,24,40,.20);
	overflow: hidden;
	touch-action: none;
	user-select: none;
	/* Same iOS gesture as the filmstrip, and this is the picture the customer
	   actually puts their finger on: without these, a slow press while
	   framing a photo opens Safari's Save Image sheet over the book. */
	-webkit-touch-callout: none;
	-webkit-user-select: none;
	-webkit-tap-highlight-color: transparent;
	transform-origin: left center;
}
/* Turn a book over and the spine is on the other side, so the back cover
   pivots on its right edge. See the note in turn(). */
.psb-sheet.is-backedge { transform-origin: right center; }
.psb-sheet.is-turning-drag { transition: none; }

/* The rest of the book, peeking at the fore-edge — the edge away from the
   binding, which is where a stack of paper actually shows. */
.psb-sheet::before,
.psb-sheet::after {
	content: ""; position: absolute; z-index: -1;
	left: 6px; right: -4px; top: 3px; bottom: -3px;
	background: #f7f8f9; border-radius: 2px;
	box-shadow: 0 0 0 1px rgba(16,24,40,.07), 0 4px 10px rgba(16,24,40,.10);
}
.psb-sheet::after { right: -8px; top: 6px; bottom: -6px; opacity: .75; }
.psb-sheet.is-cover::before, .psb-sheet.is-cover::after { display: none; }

/* The binding. A full-bleed page curves into the spine, so the ink nearest it
   falls into shadow — a fact about the object, not decoration, and the single
   strongest cue that this is a bound book rather than a loose print. */
.psb-gutter {
	position: absolute; left: 0; top: 0; bottom: 0; width: 7%;
	pointer-events: none; z-index: 2;
	background: linear-gradient(to right,
		rgba(0,0,0,.26) 0%, rgba(0,0,0,.12) 34%, rgba(0,0,0,.03) 72%, rgba(0,0,0,0) 100%);
}
.psb-gutter::after {
	content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 1px;
	background: rgba(0,0,0,.18);
}
.psb-sheet.is-cover .psb-gutter,
.psb-sheet.is-empty .psb-gutter { display: none; }

.psb-sheet.is-empty { background: repeating-linear-gradient(45deg, #fff, #fff 10px, #fafafa 10px, #fafafa 20px); }
.psb-sheet.is-blankcover { background: repeating-linear-gradient(45deg, #fbfbfc, #fbfbfc 12px, #f2f4f6 12px, #f2f4f6 24px); }

.psb-sheet-img { position: absolute; left: 0; top: 0; transform-origin: 50% 50%; will-change: transform;
	-webkit-user-drag: none; user-drag: none; pointer-events: none; }
.psb-coverart  { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.psb-sheet.is-grab { cursor: grab; }
.psb-sheet.is-grabbing { cursor: grabbing; }

/* ------------------------------------------------------------- the turn -- */

/* THE TURNING LEAF IS A CONTAINER WITH TWO FACES.

   Not a page with a verso stuck to it. `overflow: hidden` - which every page
   needs to clip its photograph - forces transform-style: FLAT, cancelling the
   3D context and with it the only thing that makes backface-visibility mean
   anything: the leaf would go on showing its photograph, mirrored, past the
   halfway point instead of the blank back a real page shows. So the clipping
   stays on the face and the 3D stays out here. */
.psb-ghostpage {
	position: absolute; z-index: 5;
	background: transparent;
	transform-origin: left center;
	transform-style: preserve-3d;
	overflow: visible;
	will-change: transform;
	pointer-events: none;
	/* NO filter HERE. A filter flattens the 3D rendering context exactly the
	   way overflow:hidden does - the computed transform-style still reads
	   preserve-3d while the used value is flat, which is a very quiet way to
	   lose the back of the leaf. The shadow goes on the faces instead. */
}
.psb-ghostpage .psb-ghostface {
	position: absolute; inset: 0;
	backface-visibility: hidden; -webkit-backface-visibility: hidden;
	box-shadow: 0 2px 6px rgba(16,24,40,.16), 0 18px 40px rgba(16,24,40,.26);
}
.psb-ghostpage::before, .psb-ghostpage::after { display: none; }
.psb-ghostface::before, .psb-ghostface::after { display: none; }
.psb-ghostpage.is-turning {
	transition: transform .43s cubic-bezier(.35,.02,.22,1), opacity .16s ease .26s;
}
/* A COVER IS HEAVIER THAN A LEAF. When one closes over the book it takes a
   little longer and arrives with weight rather than easing to a stop — and
   the shadow deepens under it as it comes down, which is the whole of what
   "this is shut now" looks like. */
.psb-ghostpage.is-closing.is-turning {
	transition: transform .56s cubic-bezier(.28,.6,.2,1), box-shadow .56s ease;
	box-shadow: 0 10px 22px rgba(16,24,40,.26), 0 34px 70px rgba(16,24,40,.34);
}
/* Gone before it reaches the clip, so the leaf is never seen being cut off. */
.psb-ghostpage.is-turning.is-going { opacity: 0; }
.psb-ghostpage .psb-sheet-img,
.psb-ghostpage .psb-coverart { backface-visibility: hidden; }

/* The blank back of the leaf. Every sheet in a snapbook prints on one side
   only, so this really is what the customer sees mid-turn. */
/* The other side of the leaf: the same rectangle, turned to face away, so it
   is what you see once the turn passes halfway. Blank, because every sheet in
   a snapbook prints on one side. */
.psb-verso {
	position: absolute; inset: 0;
	background: #fdfdfd;
	transform: rotateY(180deg);
	backface-visibility: hidden; -webkit-backface-visibility: hidden;
	box-shadow: inset 6px 0 18px rgba(0,0,0,.07);
	border-radius: 2px;
}

/* the spine, on the cover only — the one thing about a bound book a customer
   cannot picture unless it is shown */
.psb-spine { position: absolute; top: 0; bottom: 0; pointer-events: none; z-index: 3; }
.psb-spine::before, .psb-spine::after {
	content: ""; position: absolute; top: 0; bottom: 0; width: 1px; background: rgba(0,0,0,.3);
}
.psb-spine::before { left: 0; } .psb-spine::after { right: 0; }

/* BACK / FRONT sit on the SHEET, not on the spine: the sheet clips its
   overflow, so a label offset out of the spine strip would be cut off. */
.psb-faces { position: absolute; inset: 0; pointer-events: none; z-index: 3; }
.psb-face {
	position: absolute; bottom: 8px; font-size: 10px; letter-spacing: .14em; font-weight: 700;
	color: rgba(0,0,0,.45); text-shadow: 0 1px 2px rgba(255,255,255,.7);
}
.psb-face-back  { left: 12px; }
.psb-face-front { right: 12px; }

.psb-sheet-drop { position: absolute; inset: 0; border: 3px dashed transparent; border-radius: 2px; pointer-events: none; transition: border-color .12s, background .12s; z-index: 4; }
.psb-sheet.is-dropping .psb-sheet-drop { border-color: var(--psb-accent); background: rgba(241,90,36,.12); }

.psb-sheet-empty-hint {
	position: absolute; inset: 0; display: flex; flex-direction: column;
	align-items: center; justify-content: center; gap: 8px;
	color: #9aa1ab; font-size: 13px; text-align: center; padding: 16px;
}
.psb-sheet-empty-hint svg { width: 28px; height: 28px; fill: none; stroke: currentColor; stroke-width: 1.5; }

/* --------------------------------------------------------------- paging -- */

/* Under the book, in a row with the page tag — not on top of the photo. */
.psb-pager {
	flex: 0 0 auto;
	display: flex; align-items: center; justify-content: center; gap: 10px;
	padding: 10px 12px 0;
}
.psb-nav {
	flex: 0 0 auto;
	width: 40px; height: 40px; border-radius: 50%;
	display: flex; align-items: center; justify-content: center;
	background: var(--psb-panel); color: var(--psb-ink);
	box-shadow: inset 0 0 0 1px var(--psb-line), 0 2px 8px rgba(72,58,38,.10);
}
.psb-nav:hover:not(:disabled) { background: #fff; }
/* Kept in the layout when it cannot be used: a pager whose buttons appear
   and disappear moves the page tag under the pointer between clicks. */
.psb-nav:disabled { opacity: .3; pointer-events: none; }
.psb-nav svg { width: 20px; height: 20px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.psb-pagetag {
	display: inline-flex; align-items: center; gap: 7px;
	height: 30px; padding: 0 12px; border-radius: 999px;
	font-size: 11px; font-weight: 600; letter-spacing: .04em; text-transform: uppercase;
	color: var(--psb-ink-soft); white-space: nowrap; background: rgba(255,253,250,.78);
}
.psb-pagetag:hover { background: #fff; color: var(--psb-ink); }
.psb-pagetag svg { width: 13px; height: 13px; fill: none; stroke: currentColor; stroke-width: 1.9; opacity: .75; }

.psb-covernote { flex: 0 0 auto; margin: 0; font-size: 12px; color: var(--psb-ink-soft); display: none; text-align: center; padding: 0 12px; }
/* The blank-back note is a fact about the product, not an instruction, so it
   sits further back than the cover note it shares a slot with. */
.psb-covernote.is-quiet { font-size: 11px; opacity: .72; }

/* -------------------------------------------------------------- toolbar --
   On a phone the tools are not furniture. While the customer is reading the
   book there is nothing to adjust, so the bar is not there and the page gets
   the room; tapping the page brings it. Done puts it away again. */

#psb-root:not(.is-editing) .psb-tools { display: none; }
#psb-root:not(.is-editing) .psb-sheet { border-radius: 0 2px 2px 0; }

/* the page being worked on says so */
.psb-sheet.is-selected { box-shadow: 0 0 0 2px var(--psb-accent), 0 2px 6px rgba(16,24,40,.12), 0 18px 44px rgba(16,24,40,.22); }
/* Editing is about one page, so the pager steps out of the way. */
#psb-root.is-editing .psb-pager { display: none; }

#psb-root:not(.is-editing) .psb-done { display: none; }

.psb-done {
	flex: 0 0 auto; height: 38px; padding: 0 13px; border-radius: 999px;
	display: inline-flex; align-items: center; gap: 6px;
	background: var(--psb-accent); color: #fff; font-size: 13px; font-weight: 700;
}
.psb-done svg { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 2.4; stroke-linecap: round; stroke-linejoin: round; }

.psb-tools {
	flex: 0 0 auto; max-width: 100%;
	display: flex; align-items: center; gap: 1px;
	padding: 4px; border-radius: 999px;
	background: var(--psb-panel);
	box-shadow: inset 0 0 0 1px rgba(60,48,30,.07), 0 2px 5px rgba(72,58,38,.08), 0 12px 32px rgba(72,58,38,.16);
	overflow-x: auto; scrollbar-width: none;
}
.psb-tools::-webkit-scrollbar { display: none; }

/* THE ADJUST ROW - admin re-edit only, see CFG.showAdjust.

   Hidden by default rather than absent, so the storefront pays nothing for it
   and there is one markup tree to reason about. It sits UNDER the tools row
   because it is a second thought about the same photograph, not a peer of the
   crop controls. */
.psb-adjust { display: none; }
.psb-adjust.is-on {
	display: flex;
	align-items: center;
	gap: 14px;
	flex-wrap: wrap;
	justify-content: center;
	margin: 8px auto 0;
	padding: 8px 14px;
	background: var(--psb-panel);
	border: 1px solid var(--psb-line);
	border-radius: 999px;
	box-shadow: 0 1px 2px rgba(16,24,40,.05), 0 8px 24px rgba(16,24,40,.06);
}
.psb-adjust label {
	display: flex; align-items: center; gap: 7px;
	font-size: 12px; color: var(--psb-ink-soft);
	white-space: nowrap;
}
.psb-adjust label > span { min-width: 62px; }
/* Shown here, unlike the zoom slider, which is hidden below 861px - this row
   only ever appears on the admin page and that is a desktop. */
.psb-adjust input[type=range] {
	display: inline-block; width: 92px;
	accent-color: var(--psb-accent);
}
.psb-adjust output {
	min-width: 38px; text-align: right;
	font-variant-numeric: tabular-nums;
	color: var(--psb-ink);
}
.psb-adjust-reset {
	border: 1px solid var(--psb-line); background: var(--psb-panel-2);
	color: var(--psb-ink); font: inherit; font-size: 12px;
	padding: 5px 12px; border-radius: 999px; cursor: pointer;
}
.psb-adjust-reset:hover { background: #ece7de; }
.psb-tools .psb-iconbtn { width: 42px; height: 42px; border-radius: 999px; }
.psb-tools .psb-iconbtn:hover:not(:disabled) { background: #f1ece3; }
.psb-tools .psb-sep { width: 1px; height: 22px; background: var(--psb-line); margin: 0 3px; flex: 0 0 auto; }

/* Change is the only thing a page can be asked to do, so it is the one
   control in the toolbar that says what it is rather than drawing it. */
.psb-change span { display: none; }
.psb-tools .psb-zoom { display: flex; align-items: center; gap: 2px; padding: 0 2px; flex: 0 0 auto; }
/* A 74px slider is a mouse control wearing a phone's clothes: too small to
	   aim at, and it costs the toolbar the width that the buttons need. On a
	   phone zoom is the pinch and the two buttons; the slider returns with a
	   pointer that can actually hit it. */
.psb-tools input[type=range] { display: none; accent-color: var(--psb-accent); }
.psb-zoom-btn { width: 34px !important; height: 34px !important; }
.psb-zoom-btn svg { width: 16px; height: 16px; }
.psb-zoom-val { display: none; }

/* NOTHING IS SAID ABOUT RESOLUTION - no chip, no number, no mark on a
   thumbnail. The customer is using the photos they have, and there is no
   higher-resolution copy of them waiting somewhere; a notice here can only
   worry somebody out of a book it would not help them improve. The DPI is
   still computed and still travels in design_state for production. */

/* =============================================================== filmstrip */

.psb-film {
	order: 3;
	flex: 0 0 var(--psb-film-h);
	display: flex; align-items: center; gap: 8px;
	padding: 0 10px;
	background: var(--psb-panel);
	border-top: 1px solid var(--psb-line);
	box-shadow: 0 -8px 22px rgba(72,58,38,.07);
}
.psb-film-scroll {
	flex: 1 1 auto; min-width: 0; height: 100%;
	display: flex; align-items: center; gap: 8px;
	overflow-x: auto; overflow-y: hidden;
	/* A slim bar, not none. Hidden entirely, a strip that holds twenty-two
	   pages in the width of six gives no sign that there are more — on a
	   desktop, where there is no flick to discover them with, that reads as
	   broken. */
	scrollbar-width: thin;
	scrollbar-color: #d6cfc2 transparent;
	padding: 8px 2px;
	/* No snapping. Proximity snapping pulls a strip the customer is flicking
	   through back onto a thumbnail every time they let go, which is the
	   opposite of a smooth swipe. */
	-webkit-overflow-scrolling: touch;
	overscroll-behavior-x: contain;
}
.psb-film-scroll::-webkit-scrollbar { height: 6px; }
.psb-film-scroll::-webkit-scrollbar-track { background: transparent; }
.psb-film-scroll::-webkit-scrollbar-thumb { background: #d6cfc2; border-radius: 3px; }
.psb-film-scroll::-webkit-scrollbar-thumb:hover { background: #c4bbaa; }

/* Drag it. The cursor is the only affordance a desktop gets for a gesture
   that has no scrollbar under the pointer. */
.psb-film-scroll { cursor: grab; }
.psb-film-scroll.is-grabbing { cursor: grabbing; }
.psb-film-scroll.is-grabbing .psb-leaf { pointer-events: none; }
.psb-film-add { display: none; }

.psb-leaf {
	flex: 0 0 auto; position: relative; cursor: pointer;
	display: flex; flex-direction: column; align-items: center; gap: 3px;

	/* pan-x, NOT none. `none` told the browser we were handling every touch
	   ourselves — and since a finger on this strip always lands on a
	   thumbnail, it meant the strip could never be swiped at all. */
	touch-action: pan-x;

	/* iOS OWNS A LONG PRESS ON AN IMAGE unless the element says otherwise:
	   Save Image / Copy / Share, a magnifier, a selection, and a native drag
	   of the picture itself. Every thumbnail here is an <img> inside this, so
	   without these four lines a slow finger on the strip gets Safari's
	   gesture instead of ours and the swipe appears to break. */
	-webkit-touch-callout: none;
	-webkit-user-select: none;
	user-select: none;
	-webkit-tap-highlight-color: transparent;
}
/* The picture is never the target: the leaf is. This also takes the image out
   of iOS's native drag entirely. */
.psb-leaf img { -webkit-user-drag: none; user-drag: none; pointer-events: none; }
.psb-leaf-face {
	/* WIDTH IS STATED, NOT DERIVED.

	   This used to be `height: 64px; aspect-ratio: <n>` and let the browser
	   work the width out. Safari would not, reliably: a box with a definite
	   height, an aspect ratio, and a percentage-sized <img> inside it is a
	   cyclic sizing problem, and iOS resolved it from the image's INTRINSIC
	   width instead — 1800px for a cover panel. That is why the two covers
	   sat a long way from their neighbours in the strip while the pages,
	   whose faces are empty until a later frame, were fine.

	   The ratios are bare numbers, so calc() can simply do the arithmetic and
	   the answer is the same in every browser. */
	--psb-thumb-h: 64px;
	height: var(--psb-thumb-h);
	width: calc(var(--psb-thumb-h) * var(--psb-page-ar));
	flex: 0 0 auto;
	background: #fff; border-radius: 2px; overflow: hidden;
	box-shadow: 0 0 0 1px var(--psb-line), 0 1px 3px rgba(16,24,40,.1);
	position: relative;
}
.psb-leaf.is-cover .psb-leaf-face { width: calc(var(--psb-thumb-h) * var(--psb-cover-ar)); }
.psb-leaf-face img { width: 100%; height: 100%; object-fit: cover; }
.psb-leaf.is-current .psb-leaf-face { box-shadow: 0 0 0 2px var(--psb-accent), 0 2px 8px rgba(241,90,36,.3); }
.psb-leaf-n { font-size: 10px; color: var(--psb-ink-soft); font-weight: 600; }
.psb-leaf.is-current .psb-leaf-n { color: var(--psb-accent); }
.psb-leaf-lock {
	position: absolute; right: 3px; bottom: 3px; width: 16px; height: 16px;
	border-radius: 4px; background: rgba(0,0,0,.55); color: #fff;
	display: flex; align-items: center; justify-content: center;
}
.psb-leaf-lock svg { width: 10px; height: 10px; fill: none; stroke: currentColor; stroke-width: 2; }
.psb-leaf.is-locked { cursor: default; }

/* A page not filled yet. Drawn rather than described, because four empty
   sheets in the strip ARE "4 more to go". */
.psb-leaf.is-slot .psb-leaf-face {
	border: 1px dashed #d5cec1; box-shadow: none; background: #fbf9f5;
	display: flex; align-items: center; justify-content: center; color: #b0a896;
}
.psb-leaf.is-slot:hover .psb-leaf-face { border-color: var(--psb-accent); color: var(--psb-accent); background: #fff5f0; }
.psb-leaf.is-slot svg { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; }
.psb-leaf.is-slot .psb-leaf-n { color: #b0a896; }

/* the cover of a size whose wrap has not been made yet */
.psb-leaf.is-blank .psb-leaf-face {
	background: repeating-linear-gradient(45deg, #fbfbfc, #fbfbfc 6px, #f1f3f5 6px, #f1f3f5 12px);
	display: flex; align-items: center; justify-content: center;
}
.psb-leaf-note { font-size: 9px; font-weight: 700; letter-spacing: .08em; color: #9aa1ab; text-transform: uppercase; }

/* =============================================================== all pages */

/* Same rows as the filmstrip, laid out so fifty can be seen at once instead of
   scrolled past one at a time. */
.psb-pages {
	position: fixed; left: 0; right: 0; bottom: 0; z-index: 9;
	max-height: 78%;
	display: flex; flex-direction: column; min-height: 0;
	background: var(--psb-panel);
	border-radius: 16px 16px 0 0;
	box-shadow: 0 -10px 44px rgba(50,40,24,.22);
	transform: translateY(102%);
	transition: transform .26s cubic-bezier(.32,.72,0,1);
	padding-bottom: var(--psb-safe-b);
}
.psb-pages.is-open { transform: none; }
.psb-pages-head {
	display: flex; align-items: center; gap: 8px;
	padding: 0 8px 10px 16px; border-bottom: 1px solid var(--psb-line); flex: 0 0 auto;
}
.psb-pages-head b { font-size: 15px; font-weight: 700; }
.psb-pages-sub { font-size: 12px; color: var(--psb-ink-soft); }
/* A panel that covers the book needs a way off it that is visible without
   being guessed at. The grip does that on a phone and is hidden on a desktop,
   which left Esc and a lucky click on the dimmer as the only exits. */
.psb-pages-shuffle {
	margin-left: auto; padding: 8px 6px;
	font-size: 12px; font-weight: 700; color: var(--psb-accent);
}
.psb-pages-x { width: 38px; height: 38px; }
.psb-pages-grid {
	flex: 1 1 auto; min-height: 0; overflow: auto;
	display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px 10px; padding: 14px;
	align-content: start;
}
/* In the grid a page is a cell, so the fixed thumbnail height the filmstrip
   uses has to give way to the column width. */
.psb-pages-grid .psb-leaf { width: 100%; }
/* In the grid the CELL states the width, so the height is the derived one —
   the direction of the dependency is reversed and there is nothing cyclic
   about it. */
.psb-pages-grid .psb-leaf-face { height: auto; width: 100%; aspect-ratio: var(--psb-page-ar); }
/* width: 100% again, because the stated width above is one class more
   specific than the grid's and would otherwise keep the cover at its
   filmstrip size inside a grid cell. */
.psb-pages-grid .psb-leaf.is-cover .psb-leaf-face { width: 100%; aspect-ratio: var(--psb-cover-ar); }
/* The cover used to span every column, which made a panel called "all pages"
   open on one enormous cover and nothing else. It is a page like the rest;
   it is just a different shape. */
.psb-pages-grid .psb-leaf.is-cover { grid-column: auto; }

/* ==================================================================== dock */

.psb-dock {
	order: 4;
	flex: 0 0 auto;
	display: flex; align-items: stretch; gap: 8px;
	padding: 8px 10px calc(8px + var(--psb-safe-b));
	background: var(--psb-panel); border-top: 1px solid var(--psb-line);
	z-index: 6;
}
.psb-dock-btn {
	flex: 1 1 0; height: 44px;
	display: inline-flex; align-items: center; justify-content: center; gap: 8px;
	border: 1px solid var(--psb-line); border-radius: 10px;
	font-size: 13px; font-weight: 600; color: var(--psb-ink-soft); background: var(--psb-panel);
}
.psb-dock-btn.is-primary { background: var(--psb-ink); color: var(--psb-ink-on); border-color: var(--psb-ink); }
.psb-dock-btn svg { width: 18px; height: 18px; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
.psb-dock-n {
	min-width: 22px; height: 20px; padding: 0 6px; border-radius: 999px;
	display: inline-flex; align-items: center; justify-content: center;
	background: var(--psb-accent); color: #fff; font-size: 11px; font-weight: 700;
}

/* =================================================================== picker

   "Change photo" used to arm a hidden state and say so in a toast that was
   gone three seconds later, leaving a tray that looked exactly as it had a
   moment before. Now the tray says what it is waiting for until it gets it. */

/* The panel only ever opens to ask this, so it is the head, not a banner
   inside one. */
.psb-pick {
	display: flex; align-items: center; gap: 10px;
	padding: 2px 2px 0; font-size: 14px; font-weight: 700; color: var(--psb-ink);
}
.psb-pick-text { flex: 1 1 auto; }
.psb-pick button { flex: 0 0 auto; font-size: 13px; font-weight: 700; color: var(--psb-accent); padding: 8px 4px; }

/* every photo is a candidate — picking one that is already in the book
   swaps the two pages, which is the normal way to reorder */
#psb-root.is-picking .psb-chip { cursor: pointer; }
#psb-root.is-picking .psb-chip::before {
	content: ""; position: absolute; inset: 0; z-index: 2; border-radius: 8px;
	box-shadow: inset 0 0 0 2px transparent; transition: box-shadow .12s;
}
#psb-root.is-picking .psb-chip:hover::before { box-shadow: inset 0 0 0 3px var(--psb-accent); }


/* the page waiting for a photo */
#psb-root.is-picking .psb-sheet { box-shadow: 0 0 0 2px var(--psb-accent), 0 18px 44px rgba(16,24,40,.22); }

/* ================================================================ the tray */

/* NOT a photo library — the answer to one question.

   It opens only when a page has asked for a photo, and it holds exactly two
   kinds of answer: one of the photos already in the book, which SWAPS the two
   pages, or a new one, which lands on that page and takes the old one with
   it. A standing panel listing all twenty photos beside a filmstrip listing
   the same twenty pages was the same book told twice, with every photo ticked
   "used" and nothing to do about it. */
.psb-tray {
	position: fixed; left: 0; right: 0; bottom: 0; z-index: 8;
	max-height: 72%;
	display: flex; flex-direction: column; min-height: 0;
	background: var(--psb-panel);
	border-radius: 16px 16px 0 0;
	box-shadow: 0 -10px 44px rgba(50,40,24,.22);
	transform: translateY(102%);
	transition: transform .26s cubic-bezier(.32,.72,0,1);
	padding-bottom: var(--psb-safe-b);
}
.psb-tray.is-open { transform: none; }

.psb-tray-grip { height: 26px; display: flex; align-items: center; justify-content: center; cursor: pointer; flex: 0 0 auto; }
.psb-tray-grip i { width: 40px; height: 4px; border-radius: 2px; background: #d8d2c7; display: block; }

.psb-tray-head { position: relative; padding: 0 14px 12px; border-bottom: 1px solid var(--psb-line); flex: 0 0 auto; }

/* A FULL BOOK HAS NOTHING TO ADD TO IT, so the dock has nothing in it. The
   customer chose 20 in step 1 and the cart is quoted for 20; an Add photos
   button at 20 of 20 can only ever produce an error message. Both come back
   the moment the book is short of its count. */
#psb-root.is-full .psb-dock { display: none; }
.psb-tray-grid {
	flex: 1 1 auto; min-height: 0; overflow: auto;
	display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px; padding: 12px;
	align-content: start;
	-webkit-overflow-scrolling: touch;
}

/* First, because it is the answer that does not depend on what is already
   there. Same square as a photo so the grid stays a grid. */
.psb-tray-new {
	position: relative; height: 0; padding-bottom: 100%;
	border-radius: 8px; cursor: pointer;
	border: 1px dashed #d5cec1; background: #fbf9f5;
	color: var(--psb-ink-soft);
}
.psb-tray-new svg,
.psb-tray-new span {
	position: absolute; left: 0; right: 0; text-align: center;
}
.psb-tray-new svg { top: 26%; margin: 0 auto; width: 22px; height: 22px; stroke-width: 1.8; }
.psb-tray-new span { top: 52%; font-size: 11px; font-weight: 600; line-height: 1.25; }
.psb-tray-new:hover { border-color: var(--psb-accent); color: var(--psb-accent); background: #fff5f0; }

/* The one that is on this page already. Shown so the customer can see what
   they are changing; ringed rather than offered, because choosing it is not a
   change. */
.psb-chip.is-here { box-shadow: 0 0 0 2px var(--psb-accent); }
/* the ON THIS PAGE bar sits where the page number does, so the number steps up
   over it — on this chip it is the most interesting number in the panel */
.psb-chip.is-here .psb-chip-n { bottom: 26px; }
.psb-chip.is-here::after { content: "on this page"; }
.psb-chip.is-here::after {
	position: absolute; left: 0; right: 0; bottom: 0; z-index: 3;
	padding: 3px 4px; text-align: center;
	background: var(--psb-accent); color: #fff;
	font-size: 9px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase;
}

.psb-chip {
	position: relative; height: 0; padding-bottom: 100%;   /* square, reliably */
	border-radius: 8px; overflow: hidden;
	background: #ece7de; cursor: grab; touch-action: none;
	-webkit-touch-callout: none; -webkit-user-select: none; user-select: none;
	-webkit-tap-highlight-color: transparent;
}
.psb-chip img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover;
	-webkit-user-drag: none; user-drag: none; pointer-events: none; }
/* The pages this photo is on. Always shown, because after a Change the
   interesting one is the photo with NO page — it is the one that was just
   displaced, and it is still there to be used. */
.psb-chip .psb-chip-n {
	position: absolute; left: 5px; bottom: 5px;
	min-width: 20px; height: 20px; padding: 0 7px;
	display: flex; align-items: center; justify-content: center;
	border-radius: 10px; background: rgba(28,30,36,.72); color: #fff;
	font-size: 11px; font-weight: 700; white-space: nowrap;
}
.psb-chip.is-unused .psb-chip-n {
	background: var(--psb-accent);
	font-size: 9px; letter-spacing: .05em; text-transform: uppercase;
}
/* progress drawn as a lifting veil, so the photo is its own indicator */
.psb-chip .psb-chip-up { position: absolute; inset: 0; background: rgba(255,255,255,.72); display: flex; align-items: flex-end; }
.psb-chip .psb-chip-up b { display: block; height: 3px; background: var(--psb-accent); width: 0; transition: width .2s; }
.psb-chip.is-failed .psb-chip-up { background: rgba(185,28,28,.18); }

.psb-tray-empty { grid-column: 1 / -1; padding: 28px 8px; text-align: center; color: var(--psb-ink-soft); font-size: 13px; }

/* =================================================================== scrim */

/* The room behind an open drawer. Dimming it says the drawer is modal, and
   clicking it is the way out that the pointer is already near. */
.psb-scrim {
	position: fixed; inset: 0; z-index: 7;
	background: rgba(46,38,24,.34);
	opacity: 0; pointer-events: none;
	transition: opacity .2s;
}
.psb-scrim.is-on { opacity: 1; pointer-events: auto; }

/* =================================================================== modal */

.psb-modal {
	position: absolute; inset: 0; z-index: 10;
	display: none; align-items: center; justify-content: center;
	background: rgba(10,12,16,.66); padding: 16px;
}
.psb-modal.is-open { display: flex; }
.psb-modal-card {
	background: var(--psb-panel); border-radius: var(--psb-radius); box-shadow: 0 24px 64px rgba(50,40,24,.3);
	border: 1px solid var(--psb-line);
	max-width: 440px; width: 100%; padding: 22px; text-align: center;
}
.psb-modal-card h3 { margin: 0 0 6px; font-size: 17px; font-weight: 700; }
.psb-modal-card p { margin: 0 0 18px; color: var(--psb-ink-soft); font-size: 13px; }
.psb-modal-row { display: flex; gap: 10px; }
.psb-modal-row > * { flex: 1 1 0; height: 46px; display: inline-flex; align-items: center; justify-content: center; font-weight: 600; font-size: 13px; }
.psb-modal-row .psb-ghost { border: 1px solid var(--psb-line); }
.psb-modal-row .psb-ghost span { display: inline; }

/* ================================================================= preview

   A PREVIEW IS NOT THE EDITOR WITH THE TOOLS OFF.

   It used to be one page on a dark rectangle with three buttons under it.
   This is the one moment the customer asks what they are actually buying, so
   it shows the book OPEN - blank left leaf, gutter, photograph on the right -
   hung on a near-white wall with nothing else on it. Light, not dark: the
   editor is the workshop and this is the gallery, and the difference between
   them should be obvious at a glance.
   ========================================================================= */

.psb-modal[data-modal=flip] {
	background: rgba(247,244,238,.985);
	backdrop-filter: blur(10px);
	padding: 0;
	align-items: stretch;
}
.psb-flip {
	flex: 1 1 auto; min-width: 0;
	display: flex; flex-direction: column;
	padding: 0 0 calc(10px + var(--psb-safe-b));
}

/* One control, and it is the way out. */
.psb-flip-top {
	flex: 0 0 auto;
	display: flex; align-items: center;
	padding: 12px 10px;
}
.psb-flip-x {
	margin-left: auto; flex: 0 0 auto;
	width: 42px; height: 42px; border-radius: 50%;
	display: inline-flex; align-items: center; justify-content: center;
	color: var(--psb-ink); background: var(--psb-panel);
	box-shadow: inset 0 0 0 1px var(--psb-line);
}
.psb-flip-x:hover { background: #fff; box-shadow: inset 0 0 0 1px #d8d1c4; }
.psb-flip-x svg { width: 18px; height: 18px; stroke-width: 2; }

.psb-flip-stage {
	position: relative;
	flex: 1 1 auto; min-height: 0;
	display: flex; align-items: center; justify-content: center;
	flex-wrap: wrap; align-content: center; gap: 0 14px;
	padding: 0 6px;
	/* The turning leaf lives HERE, not in .psb-flip-book: the book carries a
	   drop-shadow filter, and a filter on an ancestor flattens the 3D
	   rendering context - the leaf would slide instead of turn. */
	perspective: 1800px;
}

/* The leaf in flight. Same movement as the stage, same curve, and the same
   heavier one for a cover closing over the book. */
/* Same two faces as the stage's, and for the same reason. */
.psb-flip-ghost {
	position: absolute; z-index: 3;
	transform-style: preserve-3d;
	overflow: visible;
	will-change: transform;
	pointer-events: none;
	/* No filter: it would flatten the 3D. See the note on .psb-ghostpage. */
}
/* The face keeps .psb-flip-page, whose rule sits LATER in this file and wins
   at equal specificity, so its position and flex basis are put back here. */
.psb-flip-page.psb-flip-face {
	position: absolute; inset: 0;
	flex: none; margin: 0;
	backface-visibility: hidden; -webkit-backface-visibility: hidden;
	box-shadow: 0 2px 6px rgba(16,24,40,.16), 0 18px 44px rgba(16,24,40,.24);
}
.psb-flip-ghost.is-turning {
	transition: transform .43s cubic-bezier(.35,.02,.22,1), opacity .16s ease .26s;
}
/* gone before it reaches the edge of the stage, so it is never seen clipped */
.psb-flip-ghost.is-turning.is-going { opacity: 0; }
.psb-flip-ghost.is-closing.is-turning { transition: transform .56s cubic-bezier(.28,.6,.2,1); }
.psb-flip-ghost.is-closing .psb-flip-face {
	transition: box-shadow .56s ease;
	box-shadow: 0 10px 22px rgba(16,24,40,.26), 0 34px 70px rgba(16,24,40,.32);
}

.psb-flip-arrow {
	flex: 0 0 auto; width: 44px; height: 44px; border-radius: 50%;
	display: inline-flex; align-items: center; justify-content: center;
	color: var(--psb-ink); background: var(--psb-panel);
	box-shadow: inset 0 0 0 1px var(--psb-line), 0 4px 14px rgba(72,58,38,.12);
	margin-top: 20px;
}
.psb-flip-arrow:hover:not(:disabled) { background: #fff; }
.psb-flip-arrow:disabled { opacity: .3; }
.psb-flip-arrow svg { width: 20px; height: 20px; stroke-width: 2; }

/* THE SPREAD, ON EVERY SCREEN, AT ONE SCALE.

   The editor drops the blank facing leaf on a phone, because there it is a
   white slab next to the photograph with no room for the label that explains
   it. The preview is the opposite case: it is where somebody decides whether
   this is the book they want, and every other side of a snapbook is blank.

   SIZED IN JS, by fitFlip(), for the reason the stage is: a page has to be
   the same size whether the book is open or shut. Letting CSS fit each state
   to the width it had drew the closed cover at twice the size of an open
   page, which is the one thing a real book never does - opening it does not
   shrink the paper. So the HEIGHT is chosen once, from whichever of the two
   states is tighter, and every panel is drawn at it. */
.psb-flip-book {
	position: relative;
	display: flex; align-items: stretch;
	/* A WHOLE LINE TO ITSELF on a phone. Inline arrows were eating 96 of the
	   375 pixels the book had to live in, which made a two-page spread 267px
	   wide - and this is the screen whose entire job is showing the book. */
	flex: 0 0 100%;
	/* FIRST, or the prev arrow takes line one and the book is sandwiched
	   between two lines of arrows - DOM order is prev, book, next. */
	order: -1;
	justify-content: center;
	filter: drop-shadow(0 26px 44px rgba(60,48,30,.26));
}
.psb-flip-verso {
	display: flex; flex: 0 0 auto;
	align-items: center; justify-content: center;
	background: #fdfdfc; border-radius: 2px 0 0 2px;
}
/* The blank leaf says what it is. Not chrome - it is printed on the page, the
   way the editor's facing page names itself, and it answers the question the
   customer is most likely to be surprised by when the parcel arrives. */
.psb-flip-verso span {
	text-align: center; padding: 0 8px; line-height: 1.5;
	font-size: 10px; font-weight: 700; letter-spacing: .1em;
	text-transform: uppercase; color: #c2c7d0;
}
.psb-flip-verso em {
	display: block; font-style: normal; font-weight: 600;
	letter-spacing: .06em; opacity: .75;
}
/* the binding, drawn once across the join */
.psb-flip-gutter {
	display: block; position: absolute; top: 0; bottom: 0; left: 50%;
	width: 40px; transform: translateX(-50%); pointer-events: none; z-index: 2;
	background: linear-gradient(90deg,
		rgba(0,0,0,0) 0%, rgba(0,0,0,.10) 44%, rgba(0,0,0,.22) 50%, rgba(0,0,0,.10) 56%, rgba(0,0,0,0) 100%);
}
.psb-flip-book.is-cover .psb-flip-verso,
.psb-flip-book.is-cover .psb-flip-gutter { display: none; }
.psb-flip-page {
	flex: 0 0 auto; position: relative; overflow: hidden;
	background: #fff; border-radius: 0 2px 2px 0;
}
.psb-flip-book.is-cover .psb-flip-page { border-radius: 2px; }
.psb-flip-page img { width: 100%; height: 100%; object-fit: cover; }

.psb-flip-blank {
	display: flex; width: 100%; height: 100%; align-items: center; justify-content: center;
	background: repeating-linear-gradient(45deg, #fbfbfc, #fbfbfc 14px, #f1f3f5 14px, #f1f3f5 28px);
	color: #9aa1ab; font-size: 13px; font-weight: 700; letter-spacing: .1em; text-transform: uppercase;
}


/* ==================================================================== busy */

.psb-busy {
	position: absolute; inset: 0; z-index: 12; display: none;
	flex-direction: column; align-items: center; justify-content: center; gap: 14px;
	background: rgba(255,253,250,.97); color: var(--psb-ink);
	box-shadow: inset 0 0 0 1px var(--psb-line), 0 10px 30px rgba(50,40,24,.24);
}
.psb-busy.is-on { display: flex; }
.psb-busy-ring { width: 34px; height: 34px; border-radius: 50%; border: 3px solid var(--psb-line); border-top-color: var(--psb-accent); animation: psb-spin .8s linear infinite; }
@keyframes psb-spin { to { transform: rotate(360deg); } }
.psb-busy b { font-size: 14px; font-weight: 600; }
.psb-busy span { font-size: 12px; color: var(--psb-ink-soft); }
.psb-busy-bar { width: 220px; height: 4px; border-radius: 2px; background: var(--psb-line); overflow: hidden; }
.psb-busy-bar i { display: block; height: 100%; width: 0; background: var(--psb-accent); transition: width .25s; }

/* one at a time, and never over the thing it is about */
.psb-toast {
	position: absolute; left: 50%; bottom: 50%; transform: translate(-50%, 12px);
	z-index: 11; background: var(--psb-ink); color: #fff; border-radius: 999px;
	padding: 11px 18px; font-size: 13px; font-weight: 500; text-align: center;
	opacity: 0; pointer-events: none; transition: opacity .18s, transform .18s;
	max-width: 84%;
}
.psb-toast.is-on { opacity: 1; transform: translate(-50%, 0); }

/* ================================================================= reduced
   Somebody who has asked the system not to animate things means it. The turn
   still happens, it just happens at once (editor.js checks the same query). */

@media (prefers-reduced-motion: reduce) {
	.psb-ghostpage.is-turning, .psb-tray, .psb-spread { transition: none; }
}

/* ================================================================= desktop
   From here up it is a two-pane tool: the tray becomes a permanent rail, the
   dock is not needed, and everything gets back the room it was cramped out of. */

@media (min-width: 861px) {
	#psb-root { --psb-bar-h: 60px; --psb-film-h: 132px; }

	.psb-verso-label { display: block; }

	.psb-bar { padding: 0 14px 0 6px; gap: 10px; }
	.psb-ghost span { display: inline; }
	.psb-ghost { padding: 0 14px; border: 1px solid var(--psb-line); height: 38px; }
	.psb-ghost:hover:not(:disabled) { color: var(--psb-ink); border-color: #d8d1c4; background: #faf7f1; }
	.psb-cta { height: 42px; padding: 0 20px; font-size: 14px; gap: 10px; }

	.psb-body { display: flex; flex: 1 1 auto; min-height: 0; }

	/* THE PHOTOS ARE A DRAWER, NOT A RAIL.

	   There used to be a permanent 332px column of every photo standing next
	   to a filmstrip of every page - and with one photo per page those are the
	   same twenty pictures, listed twice, spending a third of the screen to do
	   it. The filmstrip IS the book, so it stays; the photos are called for
	   when they are wanted, which is how the phone has always worked.

	   What the book gets back is the whole width. */
	.psb-tray {
		left: auto; right: 0; top: 0; bottom: 0;
		width: var(--psb-tray-w); max-height: none;
		border-radius: 0; border-left: 1px solid var(--psb-line);
		box-shadow: -20px 0 56px rgba(50,40,24,.18);
		transform: translateX(103%);
		padding-bottom: 0; padding-top: 8px;
	}
	.psb-tray-grip { display: none; }
	.psb-tray-head { padding: 14px 16px 12px; }
	.psb-tray-grid { grid-template-columns: repeat(3, 1fr); gap: 10px; padding: 14px; }

	.psb-change { width: auto !important; padding: 0 12px 0 10px; gap: 6px; }
	.psb-change span { display: inline; font-size: 13px; font-weight: 600; }

	.psb-dock { display: none; }

	/* Desktop has the room for the tools to simply be there, so there is no
	   reading mode and no Done. */
	#psb-root:not(.is-editing) .psb-tools { display: flex; }
	#psb-root.is-editing .psb-pager { display: flex; }
	.psb-pages {
		position: absolute; left: 50%; right: auto; bottom: 50%;
		transform: translate(-50%, 50%) scale(.96); opacity: 0; pointer-events: none;
		width: min(760px, 86vw); max-height: 76%;
		border-radius: var(--psb-radius);
		transition: opacity .18s, transform .18s;
	}
	.psb-pages.is-open { transform: translate(-50%, 50%) scale(1); opacity: 1; pointer-events: auto; }
	.psb-pages-grid { grid-template-columns: repeat(5, 1fr); }

	.psb-stage { padding: 24px 24px 14px; gap: 16px; }
	/* The bottom padding is the page tag's room. At 26px the tag sat on the
	   page's own bottom edge; the book needs to finish before the label that
	   describes it starts. */
	.psb-sheet-wrap { padding: 0 24px; }
	.psb-pager { padding-top: 14px; gap: 14px; }
	.psb-nav:hover:not(:disabled) { transform: scale(1.06); }

	.psb-tools { gap: 4px; padding: 6px 8px; overflow: visible; }
	.psb-tools input[type=range] { display: inline-block; width: 108px; }
	.psb-zoom-val { display: inline; font-size: 11px; color: var(--psb-ink-soft); width: 34px; text-align: right; font-variant-numeric: tabular-nums; }

	.psb-film { padding: 0 14px; gap: 10px; }
	.psb-film-scroll { gap: 10px; padding: 10px 2px; }
	.psb-leaf-face { --psb-thumb-h: 74px; }
	.psb-film-add {
		display: flex; flex: 0 0 auto; width: 92px; height: 66px;
		flex-direction: column; align-items: center; justify-content: center; gap: 2px;
		border: 1px dashed #cfd3da; border-radius: 4px;
		color: var(--psb-ink-soft); font-size: 11px;
	}
	.psb-film-add:hover { border-color: var(--psb-accent); color: var(--psb-accent); }
	.psb-film-add svg { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; }

	.psb-toast { bottom: calc(var(--psb-film-h) + 18px); }

	.psb-flip-top { padding: 16px 16px 14px 24px; }
	/* Wide enough for the arrows to flank the book again. */
	.psb-flip-stage { flex-wrap: nowrap; gap: 18px; padding: 0 18px; }
	/* Wide enough for the book to sit between the arrows instead of above
	   them. fitFlip() measures what is left and sizes the book to it. */
	.psb-flip-book { flex: 0 0 auto; margin: 0; order: 0; }
	.psb-flip-arrow { margin-top: 0; }
	.psb-flip-gutter { width: 46px; }
	.psb-flip-verso span { font-size: 11px; }
}
