/* The storybook homepage — one continuous scroll styled as a single unrolling
   parchment letter. Ported from "Tap-A-Post Storybook Site v2.dc.html"; the
   prototype's inline styles are the pixel source of truth and are transcribed
   here as classes. Values are not rounded or "tidied".

   Scroll-driven pieces (flap, letter rise, name wipe, story fan, treasure
   trail) are set from static/js/storybook.js. Every one has a static end-state
   below so the page is complete with JS off or reduced motion on. */

.sb { font-family: var(--font-body); color: var(--ink); overflow-x: clip; position: relative; }

/* ---- Backdrop: fixed scene layers, cross-blended by scroll ---------------
   27 Sep: the backdrop was a flat fill, and flat is what made it read as a
   screen rather than a place. Under the scene layers now: a lamp behind the
   letter, the room falling into shadow below it, and a fine grain over all
   of it. storybook.js sets only background-COLOR per chapter, so these
   image layers survive every chapter change. */
.sb-backdrop { position: fixed; inset: 0; z-index: 0; background-color: #26322C;
  background-image:
    radial-gradient(ellipse 64% 54% at 50% 42%, rgba(255,228,176,.12), rgba(255,228,176,0) 72%),
    linear-gradient(180deg, rgba(255,255,255,.04), rgba(0,0,0,.22)); }
.sb-layer { position: absolute; inset: 0; opacity: 0; }
.sb-vignette { position: absolute; inset: 0;
  background: radial-gradient(ellipse at 50% 42%, rgba(0,0,0,0) 46%, rgba(0,0,0,.36) 100%); }
/* One tile of SVG noise, drawn once and composited like any other image: no
   filter on a moving layer, which is the kind of thing a phone pays for on
   every frame of the scroll. */
.sb-grain { position: absolute; inset: 0; pointer-events: none; opacity: .5;
  background-size: 180px 180px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix values='0 0 0 0 1 0 0 0 0 .96 0 0 0 0 .88 .5 0 0 0 -.18'/%3E%3C/filter%3E%3Crect width='180' height='180' filter='url(%23g)'/%3E%3C/svg%3E"); }

/* Clouds were blurred rectangles, which on a flat fill read as smudges on
   the glass. A soft-edged ellipse is a cloud without a blur filter. */
.sb-cloud { position: absolute; border-radius: 50%;
  background: radial-gradient(closest-side, rgba(255,253,246,.3), rgba(255,253,246,.1) 55%, rgba(255,253,246,0)); }
.sb-cloud:nth-child(1) { left: 0; top: 8%; width: 480px; height: 150px; animation: tapDrift 26s ease-in-out infinite alternate; }
.sb-cloud:nth-child(2) { right: 2%; top: 34%; width: 560px; height: 170px; opacity: .8; animation: tapDrift 34s ease-in-out infinite alternate-reverse; }
.sb-cloud:nth-child(3) { left: 22%; top: 66%; width: 420px; height: 130px; opacity: .65; animation: tapDrift 30s ease-in-out infinite alternate; }

.sb-flake { position: absolute; top: -6vh; border-radius: 50%; background: rgba(255,255,255,.9);
  animation: tapSnow2 11s linear infinite; }

.sb-starfield { position: absolute; left: 8%; top: 10%; width: 2px; height: 2px; border-radius: 50%;
  background: var(--gold-pale);
  box-shadow: 12vw 4vh 0 #EAD98F, 26vw -2vh 0 rgba(234,217,143,.7), 40vw 7vh 0 #EAD98F,
    55vw 1vh 0 rgba(234,217,143,.6), 70vw 9vh 0 #EAD98F, 82vw 3vh 0 rgba(234,217,143,.75),
    18vw 18vh 0 rgba(234,217,143,.5), 48vw 22vh 0 rgba(234,217,143,.8), 66vw 28vh 0 rgba(234,217,143,.45),
    5vw 30vh 0 rgba(234,217,143,.6), 34vw 34vh 0 rgba(234,217,143,.4), 76vw 40vh 0 rgba(234,217,143,.55); }
.sb-twinkle { position: absolute; width: 3px; height: 3px; border-radius: 50%;
  background: var(--parchment-paper); animation: tapTwinkle 3.2s ease-in-out infinite; }
.sb-moon { position: absolute; right: 12%; top: 9%; width: 84px; height: 84px; }
.sb-moon i { position: absolute; inset: 0; border-radius: 50%; background: var(--gold-pale);
  box-shadow: 0 0 46px 8px rgba(234,217,143,.32); }
.sb-moon b { position: absolute; left: -16%; top: -12%; width: 88%; height: 88%;
  border-radius: 50%; background: var(--midnight); }

.sb-lamp-a { position: absolute; left: -8%; bottom: -12%; width: 56vw; height: 56vh;
  background: radial-gradient(ellipse at 30% 80%, rgba(232,164,74,.5), rgba(232,164,74,0) 65%);
  animation: tapLamp 3.4s ease-in-out infinite; }
.sb-lamp-b { position: absolute; right: -6%; top: -10%; width: 40vw; height: 40vh;
  background: radial-gradient(ellipse at 80% 20%, rgba(232,164,74,.22), rgba(232,164,74,0) 60%); }

.sb-envelope-float { position: absolute; top: 0; background: rgba(255,253,246,.14);
  border: 1.5px solid rgba(255,253,246,.3); border-radius: 3px; animation: tapRise 26s linear infinite; }

.sb-dust { position: absolute; width: 4px; height: 4px; border-radius: 50%;
  background: var(--gold-pale); animation: tapTwinkle 2.8s ease-in-out infinite; }

/* ---- Chrome -------------------------------------------------------------- */
.sb-washi { position: fixed; left: 0; right: 0; height: 9px; z-index: 590;
  background: repeating-linear-gradient(-45deg, #B33A2C 0 16px, #F7F1E3 16px 32px, #23305B 32px 48px, #F7F1E3 48px 64px); }
.sb-washi--top { top: 0; box-shadow: 0 1px 4px rgba(0,0,0,.3); }
.sb-washi--bottom { bottom: 0; box-shadow: 0 -1px 4px rgba(0,0,0,.3); }

.sb-head { position: fixed; top: 9px; left: 0; right: 0; z-index: 600; display: flex;
  align-items: center; justify-content: space-between; padding: 12px clamp(14px, 3vw, 32px);
  pointer-events: none; }
.sb-head > * { pointer-events: auto; }
.sb-wordmark { text-decoration: none; background: rgba(247,241,227,.92);
  border: 1px solid rgba(31,36,48,.15); border-radius: 999px; padding: 8px 18px;
  box-shadow: 0 3px 12px rgba(0,0,0,.25); color: var(--ink);
  display: flex; align-items: baseline; gap: 8px; }
.sb-wordmark:hover { color: var(--ink); }
.sb-wordmark b { font-family: var(--font-display); font-weight: 700; font-size: 21px; }
.sb-wordmark span { font-size: 11px; letter-spacing: .18em; color: rgba(42,36,22,.55); }
/* On a phone the pill and "Send one" did not fit one row, so both wrapped
   inside themselves: "Tap-A- / Post" beside "Send / one". The strapline is
   the part that can go; the hero says "Post that plays" a line below. */
@media (max-width: 480px) { .sb-wordmark span { display: none; } }
.sb-send { display: inline-flex; align-items: center; min-height: 44px; padding: 10px 22px;
  border: 1px solid rgba(0,0,0,.2); border-radius: 999px; background: var(--postbox);
  color: var(--parchment-paper); font-family: var(--font-body); font-weight: 700; font-size: 16px;
  text-decoration: none; box-shadow: 0 3px 12px rgba(0,0,0,.3); }
.sb-send:hover { background: var(--postbox-hover); color: var(--parchment-paper); }
.sb-send:active { transform: translateY(1px); }

/* ---- Stamp rail ---------------------------------------------------------- */
.sb-stamps { position: fixed; top: 50%; right: clamp(4px, 1.4vw, 20px);
  transform: translateY(-50%); z-index: 600; display: flex; flex-direction: column; gap: 9px; }
.sb-stamp { position: relative; width: 44px; height: 53px; padding: 0; border: none;
  background: transparent; cursor: pointer; transition: transform 200ms ease; min-height: 0; }
.sb-stamp:hover { transform: rotate(0deg) scale(1.12) !important; }
/* The rail is scaled to 0.76 below 680px, which drops the stamps to ~41px —
   under the 44px the design handover requires. Extend the hit area without
   changing the drawn size; 8px of growth still clears the 9px rail gap. */
.sb-stamp::after { content: ""; position: absolute; inset: -4px -8px; }
.sb-stamp > svg { position: absolute; inset: 0; width: 100%; height: 100%;
  filter: drop-shadow(1px 2px 2px rgba(0,0,0,.35)); }
.sb-stamp__plate { position: absolute; inset: 5px 6px; border: 1px solid rgba(42,36,22,.3);
  display: flex; align-items: center; justify-content: center;
  font-family: var(--font-display); font-size: 19px; color: rgba(42,36,22,.75); line-height: 1; }
/* With art in it the plate is the picture, so its own centring and type go. */
.sb-stamp__plate.has-art { padding: 0; overflow: hidden; }
.sb-stamp__plate.has-art img { display: block; width: 100%; height: 100%; object-fit: cover; }
/* The numeral sits over the art now, so it gets the little parchment chip a
   real stamp prints its denomination on — otherwise it vanishes into a dark
   sky on four of the seven. */
.sb-stamp__num { position: absolute; right: 6px; bottom: 5px;
  font-family: var(--font-display); font-size: 8px; color: rgba(42,36,22,.85);
  background: rgba(247,241,227,.88); padding: 0 3px; border-radius: 2px; line-height: 1.35; }
.sb-stamp__cancel { position: absolute; inset: 0; width: 100%; height: 100%;
  pointer-events: none; opacity: 0; transition: opacity 300ms ease; }
.sb-stamp[aria-current="true"] .sb-stamp__cancel { opacity: 1; }
.sb-stamp[aria-current="true"] .sb-stamp__plate { border-color: rgba(142,43,32,.5); color: var(--liner); }
.sb-stamp[aria-current="true"] .sb-stamp__num { color: var(--liner); }

/* ---- Hero ---------------------------------------------------------------- */
.sb-main { position: relative; z-index: 10; }
.sb-hero { height: 185vh; position: relative; z-index: 1; --sb-reserve: 430px; }
.sb-hero__inner { position: sticky; top: 0; height: 100vh; display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: clamp(10px, 2.6vh, 30px);
  padding: 40px 24px 24px; overflow: hidden; }
.sb-hero__head { text-align: center; color: var(--parchment-paper); }
.sb-kicker { font-size: 12px; font-weight: 600; letter-spacing: .34em; color: rgba(247,241,227,.6); }
.sb-hero__head h1 { font-family: var(--font-display); font-weight: 700;
  font-size: clamp(34px, min(7vw, 9.5vh), 84px); line-height: 1.02;
  margin: clamp(6px, 1.6vh, 14px) 0 0; text-shadow: 0 3px 18px rgba(0,0,0,.4); }
.sb-hero__head p { font-size: clamp(16.5px, 1.9vw, 20px); line-height: 1.55; max-width: 36em;
  margin: clamp(6px, 1.6vh, 14px) auto 0; color: rgba(247,241,227,.85); }
/* A short sentence that must not be split: the line breaks before it or not
   at all. "This / page is one of them." on a 390px phone (27 Sep). */
.sb-keep { white-space: nowrap; }

/* ---- The welcome offer ---------------------------------------------------
   Moonpig's pink strip, made see-through (founder, 27 Sep): a frosted pane
   that takes its colour from whichever scene is behind it, rather than a
   block of brand colour stuck on top. It heads the headline, above the
   kicker; on a desk-sized screen that puts it between the header's buttons.
   The code is a ticket, dashed like the reply slip, in the seal's gold. One
   tap selects all of it on a phone, and storybook.js adds a Copy button. */
.sb-offer { display: inline-flex; flex-wrap: wrap; align-items: center; justify-content: center;
  gap: 4px 10px; max-width: min(660px, 100%); margin: 0 auto clamp(14px, 2.6vh, 24px);
  padding: 6px 8px 6px 20px; border-radius: 999px;
  background: rgba(247,241,227,.12); border: 1px solid rgba(247,241,227,.36);
  box-shadow: 0 8px 26px -12px rgba(0,0,0,.5), inset 0 1px 0 rgba(255,253,246,.2);
  -webkit-backdrop-filter: blur(10px) saturate(125%); backdrop-filter: blur(10px) saturate(125%);
  color: var(--parchment-paper); font-size: 15.5px; line-height: 1.35; text-align: center;
  text-shadow: 0 1px 6px rgba(0,0,0,.35); }
.sb-offer__lead { font-weight: 700; }
.sb-offer__deal strong { color: var(--gold-pale); font-weight: 700; }
.sb-offer__code { font-family: var(--font-body); font-weight: 700; font-size: 15px;
  letter-spacing: .12em; color: var(--gold-pale); padding: 6px 12px; border-radius: 9px;
  border: 1.5px dashed rgba(234,217,143,.8); background: rgba(10,14,20,.22);
  -webkit-user-select: all; user-select: all; text-shadow: none; }
.sb-offer__until { font-size: 13.5px; color: rgba(247,241,227,.78); }
.sb-offer__copy { position: relative; min-height: 34px; padding: 5px 14px; border-radius: 999px;
  border: 1px solid rgba(247,241,227,.5); background: rgba(247,241,227,.14);
  color: var(--parchment-paper); font-family: var(--font-body); font-size: 14px;
  font-weight: 700; cursor: pointer; }
/* 34px drawn, 44px to a thumb: the handover's minimum, without a taller strip. */
.sb-offer__copy::after { content: ""; position: absolute; inset: -5px -4px; }
.sb-offer__copy:hover { background: rgba(247,241,227,.24); }
.sb-offer__copy:focus-visible { outline: 3px solid var(--gold-pale); outline-offset: 2px; }
/* Below about 1240px the strip no longer fits between the wordmark and the
   buttons, so the headline starts under the header row instead of level
   with it (and the envelope gives up that height: see --sb-reserve). */
@media (max-width: 1239px) {
  .sb-hero--offer .sb-hero__inner { padding-top: 78px; }
}
/* A narrow screen wraps it onto two or three lines, and a full pill that tall
   reads as a lozenge; a card's corners suit it better. */
@media (max-width: 720px) {
  .sb-offer { border-radius: 16px; padding: 10px 14px; font-size: 15px; }
  .sb-offer__lead { flex-basis: 100%; }
}
/* A short phone cannot hold the header, the offer, the headline, the
   envelope, a two-line tap line and the cue at once: 360x640 overflowed by
   the height of the cue, and the tap line landed on it. The cue's words go
   first (its arrow stays), then the gaps tighten, then the offer's greeting,
   because "your first order" already says who it is for. */
@media (max-height: 740px) {
  .sb-hero__inner { gap: 10px; }
  .sb-cue__label { display: none; }
}
@media (max-width: 480px) and (max-height: 740px) {
  .sb-offer__lead { display: none; }
  .sb-offer { margin-bottom: 10px; }
}

.sb-scene { position: relative; will-change: transform, opacity; }
/* The envelope takes whatever height the words above and below it leave, and
   is as wide as that height allows at its own 10:6.6 -- hence the / .66.
   --sb-reserve (on .sb-hero) is those words and the cue under them,
   measured: 430px since the tap line grew (27 Sep), 460px when the offer
   heads the headline. The old formula multiplied by 1.9 against 330px, which
   over-promised the envelope's height on a 768px laptop, and the bigger tap
   line landed on the cue. */
.sb-hero--offer { --sb-reserve: 460px; }
/* Under 1240px the offer drops below the header row, and that height comes
   out of the envelope too. A phone's envelope is sized by its width long
   before its height, so a phone gets less back. */
@media (max-width: 1239px) { .sb-hero--offer { --sb-reserve: 500px; } }
@media (max-width: 720px) { .sb-hero--offer { --sb-reserve: 440px; } }
.sb-env { position: relative; width: min(560px, 86vw, calc((100vh - var(--sb-reserve)) / .66));
  min-width: 250px; aspect-ratio: 10 / 6.6; perspective: 1200px; }
.sb-env__body { position: absolute; inset: 0; border-radius: 8px;
  background: linear-gradient(168deg, #F3E8CA, #E9DAB2 55%, #E0CFA2);
  box-shadow: 0 44px 90px -22px rgba(0,0,0,.6), 0 10px 26px -8px rgba(0,0,0,.4),
    inset 0 0 0 1px rgba(94,74,14,.14); }
.sb-env__liner { position: absolute; inset: 0; border-radius: 8px;
  background: repeating-linear-gradient(45deg, #8E2B20 0 3px, #7A241A 3px 6px);
  clip-path: polygon(0 0, 100% 0, 50% 47%); }
.sb-env__letter { position: absolute; left: 8%; top: 3%; width: 84%; height: 94%;
  background: linear-gradient(180deg, #FBF5E4, #F6ECD4); border-radius: 3px;
  box-shadow: 0 -8px 24px -10px rgba(0,0,0,.35), inset 0 0 0 1px rgba(94,74,14,.1);
  transform: translateY(0); will-change: transform; padding: 18px 22px; overflow: hidden; }
.sb-env__letter .script { font-size: 30px; color: var(--ink); }
.sb-rule { height: 3px; background: rgba(42,36,22,.22); border-radius: 2px; }
.sb-env__front { position: absolute; inset: 0; border-radius: 8px;
  background: linear-gradient(168deg, #F1E5C4, #E7D7AE 50%, #DECBA0);
  clip-path: polygon(0 0, 50% 46%, 100% 0, 100% 100%, 0 100%);
  box-shadow: inset 0 0 0 1px rgba(94,74,14,.12); }
.sb-env__front i { position: absolute; inset: 0;
  background: linear-gradient(105deg, rgba(94,74,14,.1), rgba(94,74,14,0) 32%),
    linear-gradient(-105deg, rgba(94,74,14,.1), rgba(94,74,14,0) 32%),
    linear-gradient(0deg, rgba(94,74,14,.08), rgba(94,74,14,0) 34%);
  clip-path: polygon(0 0, 50% 46%, 100% 0, 100% 100%, 0 100%); }
.sb-env__crease { position: absolute; top: 1%; width: 47%; height: 1.5px; background: rgba(94,74,14,.22); }
.sb-env__crease--l { left: 2%; transform: rotate(42.5deg); transform-origin: left top; }
.sb-env__crease--r { right: 2%; transform: rotate(-42.5deg); transform-origin: right top; }

.sb-env__flap { position: absolute; left: 0; top: 0; width: 100%; height: 56%;
  transform-origin: 50% 0; will-change: transform; z-index: 6; }
.sb-env__flapskin { position: absolute; inset: 0;
  background: linear-gradient(180deg, #F5EACB, #E6D6AC 78%, #DCC898);
  clip-path: polygon(0 0, 100% 0, 50% 96%); box-shadow: inset 0 -2px 0 rgba(94,74,14,.18); }

.sb-seal { position: absolute; left: 50%; top: 96%; transform: translate(-50%, -50%);
  width: 88px; height: 86px; border: none; padding: 0; cursor: pointer;
  background: radial-gradient(circle at 34% 28%, #E7CF74, #C9A227 52%, #8F6E14 96%);
  border-radius: 46% 54% 50% 50% / 48% 46% 54% 52%;
  box-shadow: inset 0 3px 8px rgba(255,252,235,.5), inset 0 -5px 10px rgba(90,66,10,.45),
    0 6px 16px -4px rgba(0,0,0,.5);
  transition: opacity 160ms linear; }
.sb-seal:hover { filter: brightness(1.06); }
.sb-seal:active { transform: translate(-50%, -50%) scale(.96); }
.sb-seal:focus-visible { outline: 3px solid var(--parchment-paper); outline-offset: 3px; }
.sb-seal__ring { position: absolute; inset: 11px; border: 1.5px solid rgba(94,74,14,.5); border-radius: 50%; }
.sb-seal__icon { position: absolute; left: 50%; top: 50%; width: 26px; height: 26px;
  transform: translate(-46%, -50%); }
.sb-seal__drip { position: absolute; background: radial-gradient(circle at 40% 35%, #D8B94C, #A9871C); }
.sb-seal__drip--a { left: -14px; top: 38%; width: 18px; height: 14px;
  border-radius: 60% 40% 55% 45% / 55% 60% 40% 45%; }
.sb-seal__drip--b { right: -10px; bottom: 26%; width: 13px; height: 11px; border-radius: 50%; }

.sb-note { position: absolute; color: var(--gold-pale); opacity: 0; text-shadow: 0 2px 8px rgba(0,0,0,.4); }
/* The script's x-height is tiny: at the 19-28px this used to be, it read like
   11px type and testers could not make it out on a phone or a PC (27 Sep).
   30px is the floor on a phone, 46px the ceiling on a desk. Full cream and a
   tighter shadow, because a hairline script loses its strokes in a soft one.
   Each sentence is an inline-block so a narrow screen breaks between them. */
.sb-tapline { text-align: center; margin: clamp(10px, 2.2vh, 22px) 0 0; line-height: 1.12;
  font-family: var(--font-script); font-size: clamp(30px, min(6.4vw, 5.2vh), 46px);
  color: #F7F1E3; text-shadow: 0 1px 2px rgba(0,0,0,.55), 0 2px 14px rgba(0,0,0,.5); }
.sb-tapline span { display: inline-block; }

/* In the flow, under the tap line, since 27 Sep. Pinned to the bottom of the
   screen it knew nothing about the words above it, and once the tap line grew
   the two met on every 768px-tall laptop. */
.sb-cue { text-align: center; color: rgba(247,241,227,.75); transition: opacity 400ms ease; }
.sb-cue__label { font-size: 11.5px; font-weight: 600; letter-spacing: .28em; }
.sb-cue__arrow { font-size: 18px; animation: tapCue 1.7s ease-in-out infinite; }

/* ---- The parchment scroll ------------------------------------------------ */
/* Above the hero: the scroll must occlude the envelope rather than blend with
   it, so the handover is never a translucent sandwich. */
.sb-roll { max-width: min(880px, 94vw); margin: -16vh auto 0; position: relative; z-index: 20; }
.sb-roller { position: relative; z-index: 12; height: 36px;
  background: linear-gradient(180deg, #DCC898, #B49A66 30%, #8A6F3E 52%, #B49A66 74%, #D9C79E);
  border-radius: 18px; }
.sb-roller--top { margin: 0 -16px -16px; box-shadow: 0 10px 22px -8px rgba(0,0,0,.55); }
.sb-roller--bottom { margin: -16px -16px 0;
  background: linear-gradient(180deg, #D9C79E, #B49A66 30%, #8A6F3E 52%, #B49A66 74%, #DCC898);
  box-shadow: 0 14px 26px -8px rgba(0,0,0,.6); }
.sb-roller i { position: absolute; top: 3px; width: 14px; height: 30px; border-radius: 50%; }
.sb-roller i:first-of-type { left: -6px; background: radial-gradient(ellipse at 60% 50%, #6E5730, #4C3B1E); }
.sb-roller i:last-of-type { right: -6px; background: radial-gradient(ellipse at 40% 50%, #6E5730, #4C3B1E); }

.sb-paper { position: relative;
  background: linear-gradient(180deg, #F7EED8, #F1E4C4 30%, #F5EBD1 70%, #EFE2BE);
  box-shadow: 0 34px 90px -20px rgba(0,0,0,.55), inset 0 0 80px rgba(120,90,40,.14),
    inset 0 0 0 1px rgba(94,74,14,.08);
  padding: clamp(30px, 6vw, 76px) clamp(22px, 6vw, 80px); }
.sb-dateline { text-align: right; font-style: italic; font-size: 15.5px; color: rgba(42,36,22,.6); }
.sb-salute { font-family: var(--font-script); font-size: clamp(40px, 6vw, 58px); margin-top: 18px; }
.sb-body { font-size: clamp(17.5px, 2vw, 19.5px); line-height: 1.68; margin: 16px 0 0;
  max-width: 62ch; text-wrap: pretty; }

.sb-chapter { margin-top: clamp(30px, 5vh, 54px); }
.sb-chapter:first-of-type { margin-top: clamp(48px, 9vh, 90px); }
/* The -30px this used to carry was there to tuck the heading up beside the
   postmark that floated above it. The postmark franks the card now, so the
   pull-up had nothing left to clear and dragged the heading into the picture
   instead. */
/* One line, every chapter, every width.
   Two separate things were breaking that. On desktop the type already fits —
   the longest title needs 52px of the 726px column and it is set at 46 — but
   `max-width: 12em` clamped the box to 552px and wrapped it anyway. On a phone
   the box was fine and the type was too big: 31px against the 22px the longest
   title has room for. So the cap goes, and the floor comes down to meet the
   narrowest column. Measured, not guessed: "Chapter VI · Letters Still to
   Come" is the tightest of the seven. */
.sb-chapter h2 { font-family: var(--font-display); font-weight: 700;
  font-size: clamp(21px, 5.6vw, 46px); line-height: 1.12; margin: 6px 0 0;
  max-width: none; white-space: nowrap; }
/* Between about 780px and 1050px the column grows more slowly than 5.6vw, so
   the ceiling arrives before the room does. Hold the type back through that
   band rather than letting one title tip onto a second line. */
@media (min-width: 700px) and (max-width: 1080px) {
  .sb-chapter h2 { font-size: clamp(21px, 4.6vw, 46px); }
}
/* The chapter's first letter. It was a 58px Almendra drop cap floated over
   two lines, and testers read "ecord" (27 Sep): a letter that big, in a face
   that ornate, stops being part of its word. It stays in the line now, in the
   body face, just large enough to mark where the chapter starts. */
.sb-initial { font-family: var(--font-body); font-weight: 700; font-size: 1.42em;
  line-height: 1; color: var(--postbox); }

/* Postmarks live on the cards now (.sb-plate__frank); the disc and its tones
   stay here because that is where they are described. */
.sb-pm { display: flex; align-items: center; gap: 10px; mix-blend-mode: multiply; }
.sb-pm__disc { width: 104px; height: 104px; border: 2.5px solid rgba(35,48,91,.6); border-radius: 50%;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 2px;
  color: rgba(35,48,91,.75); text-align: center; }
.sb-pm__disc b { font-size: 9px; letter-spacing: .2em; font-weight: 700; }
.sb-pm__disc strong { font-size: 10.5px; letter-spacing: .12em; font-weight: 700; }
.sb-pm__disc span { font-size: 9px; letter-spacing: .16em; }
.sb-pm--red .sb-pm__disc { border-color: rgba(142,43,32,.55); color: rgba(142,43,32,.7); }
.sb-pm--green .sb-pm__disc { border-color: rgba(30,91,58,.55); color: rgba(30,91,58,.75); }

.sb-crease { margin: clamp(44px, 8vh, 80px) calc(clamp(22px, 6vw, 80px) * -1) 0; height: 14px;
  background: linear-gradient(180deg, rgba(120,90,40,0) 0%, rgba(120,90,40,.16) 38%,
    rgba(255,253,240,.6) 52%, rgba(120,90,40,.1) 66%, rgba(120,90,40,0) 100%);
  display: flex; align-items: center; justify-content: center; }
.sb-crease span { background: #F3E7C8; padding: 0 14px; color: var(--gold); font-size: 14px; }

.sb-row { display: flex; flex-wrap: wrap; align-items: center; gap: clamp(22px, 4vw, 44px); margin-top: 30px; }
/* The chapter's words beside its little demo, on a screen with room for
   both; stacked words-first on a phone. Two short sentences no longer need a
   row of their own above the card, and the chapter is a screen shorter. */
.sb-row--words { margin-top: 4px; }
.sb-row--words > .sb-body { flex: 1 1 260px; margin: 0; }
.sb-row--words > :not(.sb-body) { flex: none; }

/* Demo cards */
.sb-card { position: relative; background: var(--parchment-input);
  border: 1px solid rgba(94,74,14,.2); border-radius: 4px;
  box-shadow: 5px 7px 18px -6px rgba(80,60,20,.4); padding: 20px 20px 22px; }
.sb-card--voice { width: min(300px, 78vw); transform: rotate(-2deg); }
.sb-card--santa { width: min(320px, 78vw); background: var(--parchment-letter);
  border-color: rgba(94,74,14,.22); transform: rotate(1.5deg); padding: 22px; }
.sb-card__seal { position: absolute; top: 12px; right: 12px; width: 46px; height: 45px;
  border: none; padding: 0; cursor: pointer;
  background: radial-gradient(circle at 34% 28%, #E7CF74, #C9A227 52%, #8F6E14 96%);
  border-radius: 46% 54% 50% 50% / 48% 46% 54% 52%;
  box-shadow: inset 0 2px 4px rgba(255,252,235,.5), inset 0 -3px 6px rgba(90,66,10,.45),
    0 3px 8px -2px rgba(0,0,0,.4); min-height: 0; }
.sb-card__seal:hover { filter: brightness(1.06); }
.sb-card__seal--static { width: 40px; height: 39px; cursor: default;
  box-shadow: inset 0 2px 4px rgba(255,252,235,.5), inset 0 -3px 6px rgba(90,66,10,.45); }
.sb-wave { display: flex; gap: 3px; align-items: flex-end; height: 20px; margin-top: 14px; opacity: .25; }
.sb-wave span { width: 5px; background: var(--gold); }
.sb-hint { font-size: 11px; letter-spacing: .16em; color: rgba(42,36,22,.5); margin-top: 8px; font-weight: 600; }
.sb-office { font-size: 10px; letter-spacing: .22em; font-weight: 700; color: var(--postbox); }
.sb-dear { display: flex; align-items: baseline; gap: 8px; margin-top: 12px; }
.sb-dear .script { font-size: 27px; }
.sb-name { font-family: var(--font-script); font-size: 31px; color: var(--forest); display: inline-block; }

/* Chapter CTAs keep the prototype's flat 2px shadow, not the pill button. */
.sb-cta { display: inline-flex; align-items: center; min-height: 48px; padding: 13px 26px;
  border-radius: 12px; font-family: var(--font-body); font-weight: 700; font-size: 17px;
  text-decoration: none; color: var(--parchment-paper); }
.sb-cta--red { background: var(--postbox); box-shadow: 0 2px 0 var(--postbox-shadow); }
.sb-cta--red:hover { background: var(--postbox-hover); color: var(--parchment-paper); }
.sb-cta--green { background: var(--forest); box-shadow: 0 2px 0 var(--forest-shadow); }
.sb-cta--green:hover { background: var(--forest-hover); color: var(--parchment-paper); }
.sb-cta--navy { background: var(--navy); box-shadow: 0 2px 0 var(--navy-shadow); }
.sb-cta--navy:hover { background: #2E3F73; color: var(--parchment-paper); }
.sb-cta:active { transform: translateY(1px); }
/* --- the buy row ----------------------------------------------------------
   Button hard left, price hard right, on every chapter. They used to be
   arranged per chapter -- one had the price tucked against the button, one
   had no price at all, one sat in a nested flex of its own -- so no two lined
   up and the eye had nothing to follow down the page.
   margin-left:auto rather than space-between: with only a button in the row
   (Chapter II has no price line) space-between would leave it centred in its
   own column instead of where every other button sits. */
.sb-buy { display: flex; align-items: center; flex-wrap: wrap;
  gap: 10px clamp(16px, 3vw, 30px); width: 100%; }
.sb-row + .sb-buy { margin-top: clamp(18px, 2.4vh, 26px); }
.sb-buy .sb-price { margin-left: auto; text-align: right; }

/* Dark gold, and translucent enough to sit under the button rather than
   compete with it -- but the alpha is capped by legibility, not by taste.
   #C9A227, the brand gold, manages 1.9:1 on parchment and is unreadable as
   text; --gold-deep only reaches 3.8:1 at FULL opacity, which is still under
   the 4.5:1 a 16px line needs. This is a deeper gold at .86, measured across
   the lightest and darkest parchment the page uses: 5.1:1 at worst.
   tapapost/tests/test_stylesheets.py holds that line. */
.sb-price { font-size: 16px; color: rgba(92, 70, 13, .86);
  font-variant-numeric: tabular-nums; }
.sb-price strong { color: rgba(92, 70, 13, 1); font-weight: 700; }

/* --- the story fan: three letters ------------------------------------------
   These were postcards -- a title, a picture, a blob of wax -- and a postcard
   is the wrong object. What arrives every Friday is a LETTER, so each sheet is
   built like one: our stamp small at the head, the title ruled off beneath it,
   the story's picture, a few lines of writing running on underneath, and the
   wax that closed it sitting at the foot. Portrait, 136x190, which is very
   nearly the 1:1.41 a sheet of paper actually is.
   The cards are a FIXED height, so the running order top-to-bottom is a
   budget: crest 27 + title 40 + picture 60 + writing 18 + wax 21 = 166 in a
   172 box. Anything added here has to take its pixels from something else. */
.sb-fan { position: relative; width: min(320px, 80vw); height: 225px; }
.sb-fan > div { position: absolute; bottom: 0; width: 136px; height: 190px;
  background: linear-gradient(179deg, #FDFAF0, var(--parchment-letter) 66%, #F2E7CB);
  border: 1px solid rgba(94,74,14,.25); border-radius: 3px;
  transform-origin: 50% 100%; box-shadow: 3px 5px 14px -4px rgba(80,60,20,.45);
  padding: 9px 10px; display: flex; flex-direction: column; align-items: center;
  will-change: transform; }
/* `.sb-fan > ` on the two that change the sheet's colour: `.sb-fan > div`
   above outranks a bare class, so for weeks the unopened letter was drawn
   cream with cream words on it, and "unlocks Friday" could not be read. */
.sb-fan__a { left: 22%; }
.sb-fan > .sb-fan__b { left: 30%; background: linear-gradient(179deg, #FBF5E6, #F6EDD6 66%, #ECDEBC); }
.sb-fan > .sb-fan__c { left: 38%; justify-content: center; text-align: center; gap: 9px;
  background: linear-gradient(179deg, #2C3A6A, var(--navy) 66%, #1B2749);
  border-color: var(--navy-shadow); color: var(--parchment-paper);
  box-shadow: 3px 5px 14px -4px rgba(0,0,0,.5); }

/* Our stamp at the head of the sheet. Small on purpose: a letterhead is
   something you notice second, after the words. */
.sb-fan__crest { width: 26px; height: auto; display: block; flex: none; }
.sb-fan__c .sb-fan__crest { opacity: .9; }

.sb-fan__title { font-family: var(--font-display); font-weight: 700;
  font-size: 12.5px; line-height: 1.16; text-align: center; width: 100%;
  margin-top: 5px; padding-top: 5px; border-top: 1px solid rgba(94,74,14,.22); }
.sb-fan__art { margin-top: 6px; width: 100%; height: 54px; flex: none;
  border: 1.5px dashed rgba(42,36,22,.25); border-radius: 3px;
  background: repeating-linear-gradient(45deg, rgba(35,48,91,.07) 0 8px, rgba(35,48,91,.02) 8px 16px);
  display: flex; align-items: center; justify-content: center; }
.sb-fan__art span { font-family: var(--font-mono); font-size: 8px; color: rgba(42,36,22,.5); }

/* The writing. Ruled lines rather than dummy words: at this size lorem would
   be an illegible smudge, and the fan is aria-hidden decoration anyway.
   Ragged lengths, because handwriting is. */
.sb-fan__lines { width: 100%; margin-top: 7px; display: flex; flex-direction: column; gap: 4px; }
.sb-fan__lines i { display: block; height: 1px; background: rgba(42,36,22,.26); }
.sb-fan__lines i:nth-child(2) { width: 92%; }
.sb-fan__lines i:nth-child(3) { width: 64%; }

/* The wax that closed it, at the foot of the sheet where it would be. */
.sb-fan__blob { width: 22px; height: 21px; flex: none; margin-top: auto;
  background: radial-gradient(circle at 34% 28%, #E7CF74, #C9A227 55%, #8F6E14);
  border-radius: 46% 54% 50% 50% / 48% 46% 54% 52%; }
.sb-fan__c .sb-fan__blob { width: 28px; height: 27px; margin-top: 0; }
.sb-fan__c em { font-family: var(--font-display); font-size: 16px; line-height: 1.25; font-style: normal; }
.sb-fan__c b { font-size: 9px; letter-spacing: .18em; color: var(--gold-pale); font-weight: 700; }


/* Reply slip */
.sb-slip { margin: 26px auto 0; max-width: 520px; border: 2px dashed rgba(42,36,22,.35);
  border-radius: 6px; padding: 20px 22px; position: relative; background: rgba(255,253,246,.5); }
.sb-slip__tab { position: absolute; top: -11px; left: 18px; background: #F3E7C8; padding: 0 8px;
  font-size: 11px; letter-spacing: .2em; color: rgba(42,36,22,.55); font-weight: 600; }
.sb-slip form { display: flex; gap: 10px; flex-wrap: wrap; }
.sb-slip input { flex: 1; min-width: 190px; min-height: 48px; padding: 10px 16px;
  border: 1.5px solid rgba(42,36,22,.4); border-radius: 10px; background: var(--parchment-input);
  font-family: var(--font-body); font-size: 16px; color: var(--ink); }
.sb-slip input:focus { border-color: var(--gold); outline: 3px solid rgba(201,162,39,.4); outline-offset: 1px; }
.sb-slip button { min-height: 48px; padding: 12px 22px; border: none; border-radius: 10px;
  background: var(--forest); color: var(--parchment-paper); font-family: var(--font-body);
  font-weight: 700; font-size: 16px; cursor: pointer; box-shadow: 0 2px 0 var(--forest-shadow); }
.sb-slip button:hover { background: var(--forest-hover); }
.sb-slip__error { font-size: 13.5px; color: var(--postbox); margin-top: 8px; min-height: 18px; }
.sb-slip__done { font-size: 17px; color: var(--forest); font-weight: 700; }

/* P.S. */
.sb-steps { display: flex; flex-wrap: wrap; gap: clamp(18px, 3vw, 34px); margin-top: 26px; }
.sb-steps > div { flex: 1; min-width: 200px; }
.sb-step__n { font-family: var(--font-display); font-weight: 700; font-size: 15px; color: var(--postbox); }
.sb-step__t { font-size: 16px; line-height: 1.6; color: rgba(42,36,22,.8); margin-top: 6px; }
.sb-honest { font-size: 14.5px; line-height: 1.65; color: rgba(42,36,22,.6); margin-top: 22px;
  max-width: 60ch; font-style: italic; }
.sb-signoff { margin-top: clamp(36px, 6vh, 60px); }
.sb-signoff div:first-child { font-size: clamp(17.5px, 2vw, 19.5px); }
.sb-signoff .script { font-size: clamp(38px, 5vw, 52px); color: var(--postbox);
  transform: rotate(-2deg); transform-origin: left center; margin-top: 4px; display: inline-block; }
.sb-foot { margin-top: clamp(30px, 5vh, 50px); border-top: 1px solid rgba(42,36,22,.2);
  padding-top: 18px; display: flex; flex-wrap: wrap; gap: 8px 26px; align-items: baseline; font-size: 15px; }
.sb-foot__c { margin-left: auto; color: rgba(42,36,22,.5); font-size: 13px; }

.sb-tail { height: 12vh; }

@media (max-width: 680px) { .sb-stamps { transform: translateY(-50%) scale(.76); } }

/* ---- The rail waits for the letter, on a phone --------------------------
   Fixed to the right edge, and on a phone there is no margin for it to sit
   in: at the top of the page it lands on the hero's own words. It is chapter
   navigation and there are no chapters on screen up there, so it stays away
   until the envelope has opened (storybook.js decides when).

   opacity and visibility ONLY -- never transform. The rail already carries a
   transform at this width for its own scaling, and a second rule fighting
   over that property is how the scale would quietly go missing.

   `visibility` as well as `opacity`, so an invisible rail is also out of the
   tab order and off the accessibility tree rather than lying in wait. */
@media (max-width: 700px) {
  .sb-stamps {
    opacity: 0;
    visibility: hidden;
    transition: opacity 260ms ease, visibility 260ms;
  }
  .sb-stamps.is-open { opacity: 1; visibility: visible; }

  /* And once it IS open it is a fixed overlay at the right edge, so the
     reading matter runs underneath it. Measured on a 375px screen rather than
     guessed: the longest lines cross the rail by up to 15px, which is a word
     clipped mid-sentence in eight paragraphs, so the inset clears that with
     room to spare.

     .sb-body ONLY. The chapter headings are white-space: nowrap with a clamp
     measured against the full column width; padding them would push that
     measurement out and put a horizontal scroll on the page, which is a worse
     bug than the one being fixed. Their text does not reach the rail anyway. */
  .sb-body { padding-right: 30px; }
}
@media (max-width: 700px) and (prefers-reduced-motion: reduce) {
  .sb-stamps { transition: none; }
}

/* The story letter's picture. The chapter plates that used to live here moved
   into the rail's stamps — one picture per chapter, at the size a stamp
   actually is (see storybook.js and home.html's data-art). */
.sb-fan__art.has-art { border: 0; overflow: hidden; }
.sb-fan__art.has-art img { display: block; width: 100%; height: 100%; object-fit: cover; }

/* Static end-states: no JS, or reduced motion. The scroll set-pieces are
   scrubbed by JS, so without it they must already be at their finished state
   rather than stuck at frame zero. */

@media (prefers-reduced-motion: reduce) {
  .sb-env__flap { transform: rotateX(-152deg); z-index: -1; }
  .sb-env__flap .sb-seal { opacity: 0; }
  .sb-env__flapskin { background: repeating-linear-gradient(45deg, #8E2B20 0 3px, #7A241A 3px 6px); }
  .sb-env__letter { transform: translateY(-34%); }
  .sb-name { clip-path: none !important; }
  .sb-fan__a { transform: rotate(-13deg) translateX(-42px); }
  .sb-fan__b { transform: rotate(-1deg) translateY(-9px); }
  .sb-fan__c { transform: rotate(12deg) translateX(42px); }
  .sb-cue { opacity: 0; }
}
.sb-no-js .sb-name { clip-path: none; }

/* "Track one" — a second, quieter action. Someone returning to an order is
   not a new customer and must not be shouted at with the same button. */
.sb-actions { display: flex; align-items: center; gap: 14px; }
.sb-track { color: var(--parchment-paper); font-size: 15px; text-decoration: none;
  border-bottom: 1px solid rgba(247,241,227,.5); padding-bottom: 1px;
  text-shadow: 0 1px 6px rgba(0,0,0,.5); }
.sb-track:hover { color: var(--gold-pale); border-bottom-color: var(--gold-pale); }

.sb-track-slip { margin-top: clamp(36px, 6vh, 60px); padding: 24px 26px;
  border: 2px dashed rgba(42,36,22,.3); border-radius: 6px;
  background: rgba(255,253,246,.5); }
.sb-track-slip h3 { font-family: var(--font-display); font-size: 1.375rem; margin: 0 0 8px; }
.sb-track-slip p { margin: 0; max-width: 54ch; color: var(--ink-muted); }

@media (max-width: 560px) { .sb-track { display: none; } }

/* ---- Chapter plates: the picture arrives as a postcard --------------------
 *
 * Every chapter opens with one. Where the artwork exists it is the picture
 * side of the card; until then it is the BACK of a postcard — ruled address
 * lines, a stamp box, a line in the message half saying the picture is on its
 * way. That matters: a dashed grey rectangle reads as a hole in the page,
 * while the back of a postcard reads as a real object whose front simply has
 * not been printed yet. Nothing on the page has to apologise for it.
 *
 * `docs/ART_PROMPTS.md` carries the prompt for each slot; dropping a file into
 * static/img and swapping the modifier is the whole job.
 */
/* A plate that FOLLOWS something instead of opening the chapter.
   .sb-plate carries its own margin on the bottom only. Since 27 Sep every
   chapter's picture sits UNDER its heading (testers: the picture above read
   as belonging to the chapter before), so the heading gets the gap here.
   Postie's introduction goes one further -- named and described, then shown
   -- and with no top margin the last line of the paragraph landed flat on the
   top edge of the card. Same on a phone and a desktop, because the missing
   margin was the same on both. */
.sb-chapter h2 + .sb-plate { margin-top: clamp(16px, 2.6vh, 26px); }
.sb-body + .sb-plate,
.sb-body + .sb-ps-pair { margin-top: clamp(20px, 3.4vh, 34px); }

.sb-plate { margin: 0 0 clamp(16px, 2.6vh, 26px); display: flex;
  justify-content: center; }

.sb-plate__card {
  position: relative; width: min(560px, 100%); aspect-ratio: 8 / 5;
  padding: 9px; border-radius: 3px;
  background: var(--parchment-input);
  border: 1px solid rgba(94,74,14,.3);
  box-shadow: inset 0 1px 0 rgba(255,253,243,.75),
              5px 8px 22px -9px rgba(80,60,20,.55);
  transform: rotate(-1.1deg);
}
.sb-plate--right .sb-plate__card { transform: rotate(1.2deg); }

/* Tape, two opposite corners. One piece on its own reads as a card coming
   loose; two reads as one somebody stuck down on purpose. It overhangs the
   card, which is the whole tell — tape that stops at the edge is a border. */
.sb-plate__tape { position: absolute; width: clamp(56px, 13%, 88px); height: 22px;
  background: repeating-linear-gradient(45deg,
    rgba(201,162,39,.34) 0 8px, rgba(255,253,243,.44) 8px 16px);
  box-shadow: 0 1px 3px rgba(80,60,20,.28), inset 0 0 0 1px rgba(255,253,243,.35);
  z-index: 3; pointer-events: none; }
.sb-plate__tape--a { left: -4%; top: 8%; transform: rotate(-42deg); }
.sb-plate__tape--b { right: -4%; bottom: 8%; transform: rotate(-42deg); }

/* The chapter's own postmark, franking the top-right corner — where a postmark
   goes. It used to float between the card and the heading, which crowded the
   heading and read as loose furniture rather than as part of the card.
   row-reverse turns the cancel waves inward across the card instead of
   trailing them off the page. */
/* Fully ON the card. It used to sit at top:-6px, which hung the disc over the
   card's edge and 26px clear of the picture — it read as sliding off. Inset
   far enough that the whole frank, rotation included, lands inside the
   picture area. */
.sb-plate__frank { position: absolute; right: 16px; top: 16px; z-index: 4; }

/* THE INK SUITS THE PICTURE. There is nothing behind the frank.
   The first attempt multiplied a translucent navy, which is right over
   parchment and invisible over a photograph. The second put a parchment wash
   behind it — and that "wash" was an opaque cream rectangle sitting on the
   artwork, which was worse than the problem.
   Dark ink needs a light ground and half these pictures do not have one, so
   the ink changes instead: cream where the artwork is dark, deep ink where it
   is light. Chosen per chapter from the picture's own measured luminance —
   see the guard in tapapost/tests/test_stylesheets.py, which samples the
   shipped file and fails if a swapped picture would hide its postmark. */
/* mix-blend-mode: normal is not a default here — it is an override. The base
   .sb-pm sets multiply so a loose postmark soaks into parchment, and that rule
   still applies inside the frank unless it is turned off by name. */
.sb-plate__frank .sb-pm { display: flex; flex-direction: row-reverse; align-items: center;
  gap: 8px; mix-blend-mode: normal; }
.sb-plate__frank .sb-pm__disc { width: 88px; height: 88px; border-width: 2px;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 2px; border-style: solid; border-radius: 50%; text-align: center; }

/* --- struck twice ---------------------------------------------------------
   A hand stamp rocks on the paper and leaves a ghost beside the impression.
   The under copy sits a couple of pixels below and is 80% transparent; the
   top copy is fractionally heavier — a thicker ring and a hair of stroke on
   the lettering. Together they read as ink pressed into the card rather than
   a circle drawn on it, and the ghost does the job the cream patch was
   wrongly doing: it thickens the mark against a busy picture. */
.sb-plate__frank__under, .sb-plate__frank__top { display: block; }
.sb-plate__frank__under {
  position: absolute; inset: 0; opacity: .2;
  transform: translate(1px, 3px); pointer-events: none; }
/* NOT border-width: Chromium snaps a border to whole device pixels, so 2.6px
   quietly computes back to the 2px it already was and the bolding never
   arrived. A fractional shadow ring antialiases instead, which is what a
   fraction of a pixel of extra ink actually looks like. */
.sb-plate__frank__top .sb-pm__disc {
  box-shadow: 0 0 0 .45px currentColor, inset 0 0 0 .45px currentColor; }
.sb-plate__frank__top .sb-pm__disc b,
.sb-plate__frank__top .sb-pm__disc strong,
.sb-plate__frank__top .sb-pm__disc span { -webkit-text-stroke: .32px currentColor; }
.sb-plate__frank__top .sb-pm svg path { stroke-width: 2.4; }

/* Deep ink, for a picture light enough to take it. */
.sb-plate__frank .sb-pm__disc { border-color: #1B2749; color: #1B2749; }
.sb-plate__frank .sb-pm--red .sb-pm__disc { border-color: #7A2118; color: #7A2118; }

/* Cream ink, for a picture that is not. A soft dark halo keeps it legible
   where the artwork lightens under part of the mark. */
.sb-plate__frank--light .sb-pm__disc,
.sb-plate__frank--light .sb-pm--red .sb-pm__disc,
.sb-plate__frank--light .sb-pm--green .sb-pm__disc {
  border-color: #F7F1E3; color: #F7F1E3; }
.sb-plate__frank--light { filter: drop-shadow(0 1px 2px rgba(10, 14, 26, .55)); }
/* A darker forest than the loose postmark used. The catalogue green is only
   2.9:1 on the parchment wash whatever its alpha — that is the hue, not the
   opacity — and this one measures 3.7. */
.sb-plate__frank .sb-pm--green .sb-pm__disc { border-color: #14422A; color: #14422A; }

/* The picture well. `inset` rather than a child box so the frame stays put
   whatever fills it — an image, a postmark, or the ruled back. */
.sb-plate__pic { position: absolute; inset: 9px; overflow: hidden; border-radius: 2px; }
.sb-plate__pic img { display: block; width: 100%; height: 100%; object-fit: cover; }

/* --- waiting: the back of the card ---------------------------------------- */
.sb-plate--wait .sb-plate__pic {
  background: var(--parchment-input);
  display: grid; grid-template-columns: 1fr 1px 1fr; gap: 0;
}
.sb-plate__msg { padding: clamp(12px, 3%, 22px); display: flex; flex-direction: column;
  justify-content: center; gap: 8px; }
.sb-plate__msg em { font-family: var(--font-script); font-style: normal;
  font-size: clamp(19px, 3.4vw, 27px); color: rgba(42,36,22,.62); line-height: 1.35; }
.sb-plate__msg span { font-family: var(--font-mono); font-size: 8.5px; letter-spacing: .14em;
  color: rgba(42,36,22,.42); }
.sb-plate__spine { background: rgba(94,74,14,.28); }
.sb-plate__addr { padding: clamp(12px, 3%, 22px) clamp(12px, 3%, 22px)
  clamp(12px, 3%, 22px) clamp(16px, 4%, 30px);
  display: flex; flex-direction: column; justify-content: flex-end; gap: 13px; }
.sb-plate__addr i { display: block; height: 1px; background: rgba(94,74,14,.3); }
.sb-plate__addr i:nth-child(1) { width: 86%; }
.sb-plate__addr i:nth-child(2) { width: 74%; }
.sb-plate__addr i:nth-child(3) { width: 80%; }
.sb-plate__addr i:nth-child(4) { width: 58%; }

/* --- a card stood on its end ----------------------------------------------
   THE one statement that a plate is portrait. Chapter IV's map is 3:4, and
   cropping it to the 8:5 the other plates use would have cut off either the
   bookshelf at the top or the X at the bottom -- the two things the picture is
   about. So the card turns instead of the picture. Narrower, so a tall card
   does not tower over the chapters either side of it.
   The Postie pair is portrait too and used to carry its own aspect-ratio; it
   uses this now, and only overrides the width. Two rules agreeing about what
   portrait means is one rule too many -- test_stylesheets.py checks every
   plate's file against this modifier, which is how the duplicate turned up. */
.sb-plate--tall .sb-plate__card { width: min(340px, 76%); aspect-ratio: 3 / 4; }

@media (max-width: 560px) {
  /* Once the row wraps, "hard right" is just an orphan on its own line.
     Let it sit under the button and read left, like the copy above it. */
  .sb-buy .sb-price { margin-left: 0; text-align: left; }
  .sb-plate__card { transform: none; }
  /* Never below the card's 9px padding, or the frank lands on the border
     instead of the picture — which is the "slipping off" it was moved to
     avoid, just one pixel's worth of it. */
  .sb-plate__frank { right: 12px; top: 12px; }
  .sb-plate__frank .sb-pm__disc { width: 60px; height: 60px; border-width: 1.5px; }
  /* A smaller mark wants a smaller rock. */
  .sb-plate__frank__under { transform: translate(.5px, 2px); }
  .sb-plate__frank__top .sb-pm__disc {
    box-shadow: 0 0 0 .3px currentColor, inset 0 0 0 .3px currentColor; }
  .sb-plate__frank .sb-pm__disc b { font-size: 6px; }
  .sb-plate__frank .sb-pm__disc strong { font-size: 7px; }
  .sb-plate__frank .sb-pm__disc span { font-size: 6px; }
  .sb-plate__frank svg { width: 26px; }
  .sb-plate__tape { width: 52px; height: 18px; }
}

/* ---- The seal on the envelope, and the stamp on the letter ---------------
 *
 * The envelope closes with wax, because that is what a child taps on the real
 * thing — a drawn seal now (`gold seal.png`) rather than the CSS blob it used
 * to be. The Tap-A-Post stamp moved to the letter inside, and onto every
 * postcard, which is where a stamp actually goes.
 */
.sb-seal.sb-seal--art {
  width: 96px; height: 96px; background: none; border-radius: 0; box-shadow: none;
  filter: drop-shadow(2px 5px 9px rgba(0,0,0,.45)); }
.sb-seal--art img { display: block; width: 100%; height: 100%; }
.sb-seal.sb-seal--art:hover { filter: drop-shadow(2px 5px 11px rgba(0,0,0,.5)) brightness(1.06); }



/* ---- P.S. — Postie, twice, as two postcards leaning into a V -------------
   Tops splayed apart, bottom inner corners nearly touching. They overlap by a
   little so they read as two cards dropped on a desk rather than two boxes in
   a grid; the left one sits under the right where they meet. */
.sb-ps-pair { display: flex; justify-content: center; align-items: flex-start;
  gap: clamp(4px, 1.4vw, 18px); margin: 0 0 clamp(24px, 4vh, 40px); }
.sb-ps-pair .sb-plate { margin: 0; flex: 0 1 320px; }
/* The 3:4 comes from sb-plate--tall on the plates themselves, not from a
   second aspect-ratio here -- there was one, and it meant two rules had to
   agree about what portrait means. All this needs to do is let the pair fill
   its own flex track instead of the --tall width. */
.sb-ps-pair .sb-plate__card { width: 100%; }
.sb-ps-pair .sb-plate:first-child .sb-plate__card {
  transform: rotate(-5deg) translateX(3%); transform-origin: 50% 100%; z-index: 1; }
.sb-ps-pair .sb-plate:last-child .sb-plate__card {
  transform: rotate(5deg) translateX(-3%); transform-origin: 50% 100%; z-index: 2; }
/* On a phone the pair scales down but stays a pair. Stacking them lost the V,
   which is the only reason they are two cards rather than one picture. The
   card rule that straightens every other plate on mobile is overridden here. */
@media (max-width: 560px) {
  .sb-ps-pair { gap: 2px; }
  .sb-ps-pair .sb-plate { flex: 0 1 47%; }
  .sb-ps-pair .sb-plate:first-child .sb-plate__card {
    transform: rotate(-5deg) translateX(3%); }
  .sb-ps-pair .sb-plate:last-child .sb-plate__card {
    transform: rotate(5deg) translateX(-3%); }
  .sb-ps-pair .sb-plate__tape { width: 34px; height: 13px; }
}
