/* Snowdrop & Sparrow storefront, v2 (Oct 2026): one WebGL journey (night → book → dawn → shelf), a WebGL flipbook
   and a WebGL book viewer, with the rest of the page in DOM. Palette and type follow the Etsy storefront kit
   (design/upload-kit/storefront.mjs) and Otto's night palette. */

:root {
  --cream: #F7F1E6; --cream-2: #EFE5D3; --paper: #FFFDF8; --paper-2: #FBF6EA;
  --ink: #2A2320; --ink-soft: #4a3f39; --muted: #6E6258; --line: rgba(42, 35, 32, .14);
  --brown: #8C5A3C; --green: #3F6B55; --green-deep: #2E5443;
  --night: #1F2A4A; --night-deep: #0F1630; --ochre: #E3A93B; --ochre-deep: #C98E21; --teal: #3C8F8C; --brick: #C4482F; --mist: #9FD0CC;
  --shadow: 0 22px 48px rgba(60, 40, 20, .2), 0 4px 10px rgba(60, 40, 20, .08);
  --shadow-soft: 0 10px 26px rgba(60, 40, 20, .12);
  --font-display: 'Fraunces', Georgia, 'Times New Roman', serif;
  --font-body: 'Atkinson Hyperlegible', system-ui, -apple-system, 'Segoe UI', sans-serif;
  --font-hand: 'Caveat', 'Segoe Print', cursive;
  --radius: 18px;
  --gutter: clamp(16px, 4vw, 52px);
  --grain: url(../assets/brand/grain.png);
  --ease-out: cubic-bezier(.2, .8, .2, 1);
  --top-h: 68px;
  --tear-h: 26px;
}

* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; text-size-adjust: 100%; }
html.lenis, html.lenis body { height: auto; }
.lenis.lenis-smooth { scroll-behavior: auto !important; }
.lenis.lenis-smooth [data-lenis-prevent] { overscroll-behavior: contain; }
body {
  margin: 0; background: var(--cream) var(--grain); color: var(--ink);
  font-family: var(--font-body); font-size: 17px; line-height: 1.55;
  -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden; overflow-x: clip;
}
img { max-width: 100%; display: block; }
a { color: inherit; }
button { font: inherit; color: inherit; cursor: pointer; -webkit-tap-highlight-color: transparent; }
:focus-visible { outline: 3px solid var(--ochre); outline-offset: 3px; border-radius: 6px; }
h1, h2, h3 { font-family: var(--font-display); font-weight: 520; line-height: 1.04; margin: 0; letter-spacing: -.015em;
  font-variation-settings: 'opsz' 120, 'SOFT' 60, 'WONK' 0; }
h2 { font-size: clamp(32px, 4.5vw, 60px); max-width: 17ch; text-wrap: balance; }
h3 { font-size: clamp(22px, 2.3vw, 30px); font-variation-settings: 'opsz' 48, 'SOFT' 50; }
p { margin: 0; }
.container { width: min(1220px, 100% - 2 * var(--gutter)); margin-inline: auto; }
.kicker { font-family: var(--font-hand); font-size: 27px; font-weight: 600; color: var(--brown); line-height: 1; margin-bottom: 10px; }
.kicker.small { font-size: 22px; margin-bottom: 4px; }
.sub { color: var(--muted); max-width: 60ch; margin-top: 14px; font-size: 18px; }
.hand { font-family: var(--font-hand); font-size: 23px; font-weight: 600; color: var(--muted); }
.sr-only { position: absolute !important; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; }
.skip { position: fixed; left: -999px; top: 8px; background: var(--ochre); color: var(--night-deep); padding: 10px 14px; border-radius: 10px; z-index: 200; font-weight: 700; }
.skip:focus { left: 8px; }
section { position: relative; }

/* hover vs touch hints */
.hint-touch, .hint-drag { display: none; }
@media (hover: none), (pointer: coarse) {
  .hint-hover, .hint-scroll { display: none; }
  .hint-touch, .hint-drag { display: inline; }
}

/* ---------- buttons ---------- */
.btn { display: inline-flex; align-items: center; justify-content: center; gap: 8px; padding: 13px 22px; min-height: 48px; border-radius: 999px; border: 2px solid transparent; font-weight: 700; text-decoration: none; transition: transform .25s var(--ease-out), box-shadow .25s, background .25s, border-color .25s; white-space: nowrap; }
.btn:active { transform: scale(.98); }
.btn.small { padding: 9px 16px; min-height: 40px; font-size: 15px; }
.btn-ochre { background: var(--ochre); color: var(--night-deep); box-shadow: 0 10px 26px rgba(227, 169, 59, .34), inset 0 1px 0 rgba(255, 255, 255, .4); }
.btn-ochre:hover { background: #F0B74A; }
/* clear over the scene, as it has always looked: a backdrop blur here never showed in Chrome (the hero copy's old permanent
   will-change hid the scene from it) and would now come and go as the copy fades (DESIGN.md, Quality on every device) */
.btn-ghost { background: rgba(247, 241, 230, .04); color: var(--cream); border-color: rgba(247, 241, 230, .45); }
.btn-ghost:hover { border-color: var(--cream); background: rgba(247, 241, 230, .1); }
.btn-ghost-ink { background: transparent; color: var(--ink); border-color: var(--line); }
.btn-ghost-ink:hover { border-color: var(--ink); }
.btn-green { background: var(--green); color: #fff; box-shadow: 0 10px 24px rgba(63, 107, 85, .3); }
.btn-green:hover { background: var(--green-deep); }
.icon-btn { width: 44px; height: 44px; border-radius: 50%; border: 2px solid var(--line); background: var(--paper); display: inline-grid; place-items: center; font-size: 22px; line-height: 1; transition: border-color .2s, transform .2s; }
.icon-btn:hover { border-color: var(--ink); }
.icon-btn:active { transform: scale(.94); }

/* ---------- topbar ---------- */
.topbar {
  position: fixed; inset: 0 0 auto 0; z-index: 60; height: var(--top-h); display: flex; align-items: center; gap: 22px;
  padding: 0 var(--gutter); padding-top: env(safe-area-inset-top); color: var(--cream);
  transition: background .4s, color .4s, box-shadow .4s, backdrop-filter .4s, -webkit-backdrop-filter .4s;
}
/* the night hero keeps a clear bar (cream on the dark room); from the morning shop on, the bar is frosted paper, so the
   links never sit on the book covers as the shelf scrolls up under it */
.topbar.is-day, .topbar.is-solid { background: rgba(247, 241, 230, .9); color: var(--ink); box-shadow: 0 1px 0 var(--line); backdrop-filter: blur(12px) saturate(1.2); -webkit-backdrop-filter: blur(12px) saturate(1.2); }
.brand { display: flex; align-items: center; gap: 10px; text-decoration: none; font-family: var(--font-display); font-size: 21px; font-weight: 560; white-space: nowrap; font-variation-settings: 'opsz' 30, 'SOFT' 80; }
.brand img { border-radius: 50%; background: var(--cream); box-shadow: 0 0 0 2px rgba(255, 255, 255, .45); }
.brand em { font-style: italic; color: var(--ochre); }
.topbar.is-day .brand em, .topbar.is-solid .brand em { color: var(--brown); }
.nav { display: flex; gap: 24px; margin-left: auto; font-size: 15px; font-weight: 700; }
.nav a { text-decoration: none; opacity: .86; padding: 8px 0; background: linear-gradient(currentColor, currentColor) 0 100% / 0 2px no-repeat; transition: opacity .2s, background-size .35s var(--ease-out); white-space: nowrap; }
.nav a:hover { opacity: 1; background-size: 100% 2px; }
.picks-btn { display: inline-flex; align-items: center; gap: 8px; min-height: 42px; padding: 8px 14px; border-radius: 999px; border: 2px solid currentColor; background: transparent; font-weight: 700; }
.picks-btn b { background: var(--ochre); color: var(--night-deep); border-radius: 999px; min-width: 22px; height: 22px; display: grid; place-items: center; font-size: 13px; }
.menu-btn { display: none; width: 46px; height: 46px; border-radius: 50%; border: 2px solid currentColor; background: transparent; position: relative; }
.menu-btn span:not(.sr-only) { position: absolute; left: 12px; right: 12px; height: 2px; background: currentColor; border-radius: 2px; transition: transform .35s var(--ease-out), top .35s var(--ease-out); }
.menu-btn span:nth-child(1) { top: 17px; } .menu-btn span:nth-child(2) { top: 25px; }
.menu-btn[aria-expanded="true"] span:nth-child(1) { top: 21px; transform: rotate(45deg); }
.menu-btn[aria-expanded="true"] span:nth-child(2) { top: 21px; transform: rotate(-45deg); }
/* the nav needs about 1200 px beside the full name (at 1101-1195 px "The shelf" was printed over "Sparrow"), and the
   one-line name with the labelled Picks button about 680 px (below that the name ran under the round buttons), so the
   menu button takes over under 1240 px and the name goes onto two lines under 720 px. The buttons keep their full size
   down to 460 px; under 540 px the Picks pill shows only its count, the word hidden but still read out ("Picks 0") */
@media (max-width: 1240px) { .nav { display: none; } .picks-btn { margin-left: auto; } .menu-btn { display: inline-block; } }
@media (max-width: 720px) { .brand { font-size: 17px; gap: 8px; } .brand img { width: 34px; height: 34px; } .topbar { gap: 8px; } }
@media (max-width: 540px) { .picks-btn { min-height: 46px; } .picks-btn span { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; } }
@media (max-width: 460px) { .picks-btn { padding: 6px 10px; gap: 6px; min-height: 40px; } .menu-btn { width: 42px; height: 42px; } .menu-btn span:not(.sr-only) { left: 11px; right: 11px; } .menu-btn span:nth-child(1) { top: 15px; } .menu-btn span:nth-child(2) { top: 23px; } .menu-btn[aria-expanded="true"] span:nth-child(1), .menu-btn[aria-expanded="true"] span:nth-child(2) { top: 19px; } }
/* the buttons keep their size; on the narrowest phones the name gives way instead */
.menu-btn, .sound-btn, .picks-btn { flex: none; }
.brand { min-width: 0; }
@media (max-width: 720px) { .brand > span:last-child { white-space: normal; line-height: 1.04; max-width: 6.4em; } }
/* the name breaks only before "& Sparrow" (a no-break space in index.html), so it needs that line's width: under 400 px
   the menu button joins the round buttons at 36 px and the Picks pill slims, and under 359 px the name steps down a size */
@media (max-width: 400px) { .brand { font-size: 15.5px; gap: 7px; } .topbar { gap: 6px; } .picks-btn { padding-inline: 8px; } .menu-btn { width: 36px; height: 36px; } .menu-btn span:not(.sr-only) { left: 9px; right: 9px; } .menu-btn span:nth-child(1) { top: 12px; } .menu-btn span:nth-child(2) { top: 20px; } .menu-btn[aria-expanded="true"] span:nth-child(1), .menu-btn[aria-expanded="true"] span:nth-child(2) { top: 16px; } }
@media (max-width: 358px) { .brand { font-size: 14px; } }
@media (max-width: 350px) { .brand > span:last-child { display: none; } }      /* the name gives way; the mark stays */

.menu { position: fixed; inset: 0; z-index: 55; background: var(--night-deep) var(--grain); color: var(--cream); padding: calc(var(--top-h) + 30px) var(--gutter) 40px; display: flex; flex-direction: column; justify-content: space-between;
  clip-path: circle(0% at calc(100% - 40px) 34px); transition: clip-path .7s cubic-bezier(.7, 0, .2, 1); }
.menu.is-open { clip-path: circle(150% at calc(100% - 40px) 34px); }
.menu[hidden] { display: none; }
.menu nav { display: grid; gap: 6px; }
.menu nav a { font-family: var(--font-display); font-size: clamp(34px, 8vw, 60px); text-decoration: none; line-height: 1.15; display: flex; align-items: baseline; gap: 14px; font-variation-settings: 'opsz' 144, 'SOFT' 80, 'WONK' 1; }
.menu nav a small { font-family: var(--font-body); font-size: 14px; color: var(--ochre); font-weight: 700; }
.menu-etsy { font-weight: 700; color: var(--ochre); text-decoration: none; }
.topbar.menu-open { color: var(--cream); background: transparent; box-shadow: none; backdrop-filter: none; -webkit-backdrop-filter: none; }

/* ---------- the journey ---------- */
.journey { background: var(--cream); height: 760vh; }
.stage { position: sticky; top: 0; height: 100vh; height: 100lvh; overflow: hidden; background: var(--night-deep); }
.gl { position: absolute; inset: 0; width: 100%; height: 100%; display: block; touch-action: pan-y; -webkit-user-select: none; user-select: none; opacity: 0; transition: opacity .9s ease .1s; }
.poster { position: absolute; inset: 0; transition: opacity 1s ease .1s; }
.poster-sky { position: absolute; inset: 0; background: radial-gradient(120% 90% at 72% 100%, #33416F 0%, #18234A 48%, #0A1027 100%); }
.poster-book { position: absolute; width: min(36vh, 34vw); aspect-ratio: 1; height: auto; left: 71%; top: 50%; transform: translate(-50%, -50%) perspective(1600px) rotateY(-26deg); border-radius: 3px 8px 8px 3px; box-shadow: 0 30px 60px rgba(0, 0, 0, .5); }
/* The canvas stacks above the poster, so it stays see-through until the scene is ready (its hidden warm-up frames never
   show) and then fades in over it; the first live frame has replaced the warm-up one before the fade starts. The poster
   goes only once the canvas covers it, so the fade never dips through the stage's navy. Calm motion keeps it: a fade
   has no travel. */
html.gl-ready .gl { opacity: 1; }
html.gl-ready .poster { opacity: 0; pointer-events: none; transition-delay: 1s; }
html.no-gl .gl { display: none; }
@media (max-aspect-ratio: 41/50) { .poster-book { width: 52vw; left: 50%; top: 32%; transform: translate(-50%, -50%) perspective(1600px) rotateY(-18deg); } }

.journey-ui { position: absolute; left: 0; right: 0; top: 0; height: 100vh; height: 100svh; pointer-events: none; }
.journey-ui > * { pointer-events: auto; }
.hero-copy { position: absolute; left: var(--gutter); top: 50%; transform: translateY(calc(-46% + var(--lift, 0px))); width: min(590px, 46vw); color: var(--cream); }   /* its own layer only while it fades (app.js, onFrame) */
.eyebrow { font-size: 13px; letter-spacing: .2em; text-transform: uppercase; font-weight: 700; color: rgba(247, 241, 230, .7); margin-bottom: 22px; }
.hero-copy h1 { font-size: clamp(46px, 6.6vw, 100px); font-weight: 420; color: #FBF6EA; font-variation-settings: 'opsz' 144, 'SOFT' 100, 'WONK' 1; letter-spacing: -.025em; text-wrap: balance; }
.hero-copy h1 .w-colour { color: var(--ochre); font-style: italic; }
.hero-copy h1 .w-solve { color: var(--mist); }
/* The lede stops short of the pop-up, which stands nearer the copy below about 1520 px wide; the copy column keeps its
   width so the headline never rewraps. From about 1180 to 1520 px an em measure gives the same four lines at every
   width, ending on "take it home." rather than a lone "it home.". A fourth line lifts the vertically centred copy into
   the top bar on short windows, though (a 1366x768 laptop once the browser's bars take their share). So from 16:9 up,
   where the book stands further off, and on windows too short for it (under 611 px tall, or under 631 px at 17:10 and
   wider), the lede keeps three lines: 26.5em holds two sentences. */
.lede { margin-top: 24px; font-size: clamp(17px, 1.35vw, 20px); color: rgba(247, 241, 230, .82); max-width: min(48ch, 34vw); text-wrap: pretty; }
@media (max-width: 1519px) { .lede { max-width: min(48ch, 34vw, 23.5em); } }
@media (max-width: 1519px) and (min-aspect-ratio: 177/100), (min-width: 1001px) and (max-width: 1519px) and (max-height: 630px) and (min-aspect-ratio: 17/10),
  (min-width: 1001px) and (max-width: 1519px) and (max-height: 610px) { .lede { max-width: min(48ch, 26.5em); } }
.cta-row { display: flex; gap: 12px; flex-wrap: wrap; margin-top: 30px; }
.hero-hint { margin-top: 26px; color: rgba(247, 241, 230, .94); text-shadow: 0 1px 2px rgba(12, 22, 44, .6), 0 0 16px rgba(12, 22, 44, .5); }
.caption { position: absolute; left: 50%; bottom: max(28px, 5svh); transform: translate(-50%, var(--lift, 0px)); width: min(620px, 100% - 2 * var(--gutter)); text-align: center; color: var(--cream); opacity: 0; visibility: hidden; text-shadow: 0 2px 18px rgba(8, 12, 30, .6); }
.caption .hand { color: var(--ochre); font-size: 26px; }
.caption p:last-child { font-size: clamp(16px, 1.3vw, 19px); color: rgba(247, 241, 230, .9); margin-top: 4px; }
.caption .cap-soon { color: var(--ochre); font-weight: 700; white-space: nowrap; }
.shelf-head { position: absolute; left: var(--gutter); right: var(--gutter); top: calc(var(--top-h) + 1vh); transform: translateY(var(--lift, 0px)); display: flex; flex-direction: column; align-items: stretch; gap: 12px; opacity: 0; visibility: hidden; color: var(--ink); }
.shelf-head .kicker { margin-bottom: 4px; }
.shelf-head h2 { font-size: clamp(24px, 2.6vw, 40px); max-width: none; }
.shelf-head .sub { margin-top: 6px; font-size: 15px; }
.chips { display: flex; gap: 8px; flex-wrap: nowrap; overflow-x: auto; margin-inline: calc(var(--gutter) * -1); padding: 2px var(--gutter) 6px; scrollbar-width: none; -webkit-mask: linear-gradient(90deg, transparent, #000 var(--gutter), #000 calc(100% - var(--gutter)), transparent); mask: linear-gradient(90deg, transparent, #000 var(--gutter), #000 calc(100% - var(--gutter)), transparent); }
.chips::-webkit-scrollbar { display: none; }
.chip { flex: none; }
@media (min-width: 1500px) and (min-aspect-ratio: 3/2) {
  .shelf-head { flex-direction: row; align-items: flex-end; justify-content: space-between; }
  .chips { flex-wrap: wrap; justify-content: flex-end; max-width: 640px; margin: 0; padding: 0; overflow: visible; -webkit-mask: none; mask: none; }
}
.chip { padding: 8px 14px; min-height: 40px; border-radius: 999px; border: 1.5px solid var(--line); background: rgba(255, 253, 248, .82); font-weight: 700; font-size: 14px; transition: background .2s, color .2s, border-color .2s; backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px); }
.chip:hover { border-color: var(--brown); }
.chip.is-on { background: var(--ink); color: var(--cream); border-color: var(--ink); }
.chip small { opacity: .6; font-weight: 400; margin-left: 4px; }
/* the tag hangs flush under the pulled book (journey.js projects the book's bottom edge), opaque so no cover shows through */
.shelf-tag { position: absolute; left: 0; top: 0; z-index: 3; display: flex; align-items: center; gap: 10px; padding: 8px 8px 8px 16px; border-radius: 999px; background: rgb(42, 35, 32); color: var(--cream); white-space: nowrap; box-shadow: 0 12px 30px rgba(42, 35, 32, .3); font-size: 15px; transform: translateX(-50%); transition: opacity .2s; max-width: calc(100vw - 24px); }
.shelf-tag b { font-weight: 700; overflow: hidden; text-overflow: ellipsis; max-width: min(68ch, calc(100vw - 220px)); }
.shelf-tag span { color: var(--ochre); font-weight: 700; }
.shelf-tag span, .tag-open { flex: none; }
/* phones: the price goes under the title, so the title has the tag's width and up to two lines (in one line beside the
   price and the button, 30 of the 38 titles were cut short at 390 px) */
@media (max-width: 600px) {
  .shelf-tag { display: grid; grid-template-columns: minmax(0, 1fr) auto; grid-template-areas: "name open" "price open"; column-gap: 12px; row-gap: 1px; white-space: normal; width: max-content; border-radius: 22px; }
  .shelf-tag b { grid-area: name; max-width: none; line-height: 1.2; display: -webkit-box; -webkit-line-clamp: 2; line-clamp: 2; -webkit-box-orient: vertical; }
  .shelf-tag span { grid-area: price; font-size: 14px; }
  .tag-open { grid-area: open; }
}
.tag-open { border: 0; background: var(--ochre); color: var(--night-deep); font-weight: 700; border-radius: 999px; padding: 6px 14px; min-height: 34px; }
.shelf-tag[hidden] { display: none; }
.rail { position: absolute; right: calc(var(--gutter) * .6); top: 50%; transform: translateY(-50%); list-style: none; margin: 0; padding: 0; display: grid; gap: 14px; pointer-events: none; }
.rail li { position: relative; width: 8px; height: 8px; border-radius: 50%; background: currentColor; opacity: .35; font-size: 0; color: var(--cream); transition: opacity .3s, transform .3s, color .4s; }
.rail li.is-on { opacity: 1; transform: scale(1.5); }
.rail.is-day li { color: var(--ink); }
.scroll-cue { position: absolute; left: 50%; bottom: 22px; width: 26px; height: 42px; border: 2px solid rgba(247, 241, 230, .4); border-radius: 14px; transform: translateX(-50%); pointer-events: none; }
.scroll-cue span { position: absolute; left: 50%; top: 7px; width: 4px; height: 8px; margin-left: -2px; border-radius: 2px; background: var(--ochre); animation: cue 1.8s ease-in-out infinite; }
.scroll-cue[data-gone] span { animation: none; }       /* scrolled away (app.js): no loop off screen */
@keyframes cue { 0% { transform: translateY(0); opacity: 1 } 70% { transform: translateY(16px); opacity: 0 } 100% { opacity: 0 } }
.anchor { position: absolute; left: 0; width: 1px; height: 1px; top: 370vh; }
@media (max-aspect-ratio: 41/50) {
  .hero-copy { top: auto; bottom: max(22px, 4svh); transform: translateY(var(--lift, 0px)); width: auto; right: var(--gutter); }
  .hero-copy h1 { font-size: clamp(38px, 10.5vw, 64px); }
  .eyebrow { margin-bottom: 12px; font-size: 11px; }
  .lede { margin-top: 14px; font-size: 16px; max-width: 48ch; }   /* (below the scene here, so the full measure) */
  .cta-row { margin-top: 18px; }
  .hero-hint { margin-top: 12px; }
  .scroll-cue, .rail { display: none; }
}
@media (max-aspect-ratio: 41/50) and (max-height: 700px) { .lede { display: none; } }
@media (min-aspect-ratio: 41/50) and (max-width: 1000px) { .hero-copy { width: min(480px, 52vw); } .hero-copy h1 { font-size: clamp(40px, 6vw, 72px); } }
@media (max-height: 560px) and (min-aspect-ratio: 41/50) { .lede, .hero-hint, .eyebrow { display: none; } .shelf-head .sub { display: none; } }

/* shelf list: a keyboard and screen-reader mirror of the 3D shelf; becomes the visible shelf when WebGL is missing */
.shelf-list { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); margin: 0; padding: 0; list-style: none; }
.shelf-list button { all: unset; }
html.no-gl .journey { height: auto !important; }
html.no-gl .stage { position: relative; height: auto; min-height: 100vh; padding-bottom: 60px; background: var(--night-deep); }
html.no-gl .journey-ui { position: relative; height: auto; min-height: 100vh; }
html.no-gl .caption, html.no-gl .rail, html.no-gl .scroll-cue, html.no-gl .hero-hint { display: none; }
html.no-gl .shelf-head { position: relative; inset: auto; opacity: 1; visibility: visible; color: var(--cream); padding: 0 var(--gutter); margin-top: -10vh; }
html.no-gl .shelf-head .sub { color: rgba(247, 241, 230, .75); }
html.no-gl .shelf-list { position: relative; width: auto; height: auto; clip-path: none; overflow: visible; display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 26px 18px; padding: 30px var(--gutter) 0; }
html.no-gl .shelf-list button { display: grid; gap: 8px; cursor: pointer; color: var(--cream); font-size: 14px; font-weight: 700; }
html.no-gl .shelf-list img { width: 100%; border-radius: 3px 6px 6px 3px; box-shadow: 0 14px 30px rgba(0, 0, 0, .45); }
html.no-gl .shelf-list[data-filter] li { display: none; }
html.no-gl .shelf-list[data-filter] li.match { display: list-item; }

/* ---------- torn paper edges (alpha masks generated in js/app.js) ---------- */
/* Each torn sheet lies over the one before it. Its torn edge is a strip of its own (.seam, made by tearEdges in
   js/app.js, aria-hidden): 26 px of tear plus 10 px of the sheet's own paper as slack, cut by the static alpha mask, so
   it is transparent above the tear (never a filled rectangle). The sheet's paper (::before) starts under the tear, and
   the section itself is not masked, so a thing moving inside it no longer makes the browser composite the whole
   section through a mask (DESIGN.md, "the cost is raster work"). Strip and paper place their grain (and the night
   sheet its sky) from the section's own top-left corner, so the joins do not show: pixel for pixel, the page looks as
   it did with one mask over each section. Without JavaScript none of this applies and the sheets are plain. */
.torn-top { margin-top: calc(var(--tear-h) * -1); padding-top: calc(var(--tear-h) + 70px); z-index: 2; }
.torn-top.torn-bottom.is-torn { margin-bottom: calc(var(--tear-h) * -1); padding-bottom: calc(var(--tear-h) + 90px); z-index: 3; }
.torn-top.is-torn { background: none; }
/* The four busy sheets (Advent, the finder, Try a page, the cast) keep a compositor layer of their own, as their masks
   used to give them, but a plain one: no mask, so no render surface to composite them through. Without it, every frame
   of a scroll had to sort the whole lower page's paint into layers around the things moving in it (the flipbook's and
   the colouring page's canvases, the cast's drift, the rises): on the slowed phone (CPU 6x) that work tripled
   (Layerize 2.6 -> 10 ms a frame) and the lower page fell from about 30 to 19 fps. (opacity, not transform: a
   will-change: transform layer keeps its first raster scale.) The free page has one in full motion only, while the
   footer's mark moves under its foot (the entrance, then its idle life): with it the footer is layered as it was when
   the free page was masked, and the logo stays pixel for pixel in both motions (tools/site-check/logo-baseline.mjs; in
   full motion without it, 9 of the 64 captures moved along the sparrow's edges, by up to 559 pixels; in calm with it,
   3 phone captures moved two snowflakes). The night sheet is quiet and needs none. These are the documented exception
   to "will-change only while moving" (DESIGN.md): the layers their masks gave them. On desktop their text is drawn with
   greyscale smoothing, not subpixel, as in any layer that is not opaque (the torn edge is transparent). */
.advent.is-torn, .finder.is-torn, .try.is-torn, .cast.is-torn { will-change: opacity; }
html:not([data-motion="calm"]) .free.is-torn { will-change: opacity; }
/* The footer keeps its one mask, as before: the loved logo lives in it, and its captures must stay pixel for pixel
   (tools/site-check/logo-baseline.mjs); as a strip, or on a layer of its own, its antialiasing moved by a level or two */
.torn-top.is-torn-mask {
  -webkit-mask: var(--tear-top) top / 100% var(--tear-h) no-repeat, linear-gradient(#000 0 0) 0 calc(var(--tear-h) - 1px) / 100% calc(100% - var(--tear-h) + 1px) no-repeat;
  mask: var(--tear-top) top / 100% var(--tear-h) no-repeat, linear-gradient(#000 0 0) 0 calc(var(--tear-h) - 1px) / 100% calc(100% - var(--tear-h) + 1px) no-repeat; }
.torn-top.is-torn::before { content: ''; position: absolute; inset: var(--tear-h) 0 0; z-index: -1; pointer-events: none;
  background: var(--sheet) var(--grain) 0 calc(var(--tear-h) * -1); }
.torn-top.torn-bottom.is-torn::before { bottom: var(--tear-h); }
.seam { position: absolute; left: 0; right: 0; top: 0; height: calc(var(--tear-h) + 10px); z-index: -1; pointer-events: none;
  background: var(--sheet) var(--grain);
  -webkit-mask: var(--tear-top) top / 100% var(--tear-h) no-repeat, linear-gradient(#000 0 0) 0 calc(var(--tear-h) - 1px) / 100% calc(100% - var(--tear-h) + 1px) no-repeat;
  mask: var(--tear-top) top / 100% var(--tear-h) no-repeat, linear-gradient(#000 0 0) 0 calc(var(--tear-h) - 1px) / 100% calc(100% - var(--tear-h) + 1px) no-repeat; }
/* a torn bottom edge (the night sheet over How we make them): its paper placed from the section's top, so it needs the
   section's height (--sec-h, kept by tearEdges) */
.seam.seam-bottom { top: auto; bottom: 0; background-position: 0 calc(var(--tear-h) + 10px - var(--sec-h, 0px));
  -webkit-mask: linear-gradient(#000 0 0) 0 0 / 100% calc(100% - var(--tear-h) + 1px) no-repeat, var(--tear-bottom) bottom / 100% var(--tear-h) no-repeat;
  mask: linear-gradient(#000 0 0) 0 0 / 100% calc(100% - var(--tear-h) + 1px) no-repeat, var(--tear-bottom) bottom / 100% var(--tear-h) no-repeat; }

/* ---------- finder ---------- */
.finder { --sheet: var(--cream); padding-bottom: 90px; background: var(--sheet) var(--grain); }
.moments { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(100%, 330px), 1fr)); gap: 16px; margin-top: 34px; }
.moment { position: relative; text-align: left; padding: 22px 22px 20px; min-height: 164px; border-radius: var(--radius); border: 2px solid transparent; background: var(--paper) var(--grain); box-shadow: var(--shadow-soft); overflow: hidden; transition: transform .35s var(--ease-out), box-shadow .35s, border-color .25s; isolation: isolate; }
.moment:hover { transform: translateY(-5px); box-shadow: var(--shadow); }
.moment.is-on { border-color: var(--green); }
.moment b { display: block; font-family: var(--font-display); font-size: 23px; font-weight: 560; max-width: 54%; line-height: 1.12; font-variation-settings: 'opsz' 48, 'SOFT' 80; }
.moment span { display: block; color: var(--muted); font-size: 14px; margin-top: 6px; max-width: 52%; }
.moment .fan { position: absolute; right: -6px; bottom: -26px; width: 150px; height: 140px; perspective: 600px; }
.moment .fan img { position: absolute; bottom: 0; width: 80px; border-radius: 2px 5px 5px 2px; box-shadow: 0 10px 20px rgba(60, 40, 20, .32); transition: transform .45s var(--ease-out); transform-origin: 50% 100%; }
.moment .fan img:nth-child(1) { left: 0; transform: rotate(-11deg); }
.moment .fan img:nth-child(2) { left: 34px; bottom: 8px; transform: rotate(2deg); z-index: 2; }
.moment .fan img:nth-child(3) { left: 68px; transform: rotate(13deg); }
.moment:hover .fan img:nth-child(1) { transform: rotate(-19deg) translateY(-10px); }
.moment:hover .fan img:nth-child(2) { transform: rotate(0deg) translateY(-18px); }
.moment:hover .fan img:nth-child(3) { transform: rotate(21deg) translateY(-10px); }
.finder-result { margin-top: 28px; display: flex; gap: 18px; overflow-x: auto; padding: 10px 4px 18px; scroll-snap-type: x proximity; scrollbar-width: thin; }
.finder-result[hidden] { display: none; }
.mini { flex: none; width: 176px; scroll-snap-align: start; background: none; border: 0; padding: 0; text-align: left; perspective: 900px; }
.mini .mini-cover { height: 196px; display: flex; align-items: flex-end; margin-bottom: 12px; }
.mini img { max-height: 196px; width: auto; border-radius: 2px 6px 6px 2px; box-shadow: var(--shadow-soft); transition: transform .45s var(--ease-out), box-shadow .45s; transform-origin: 0 50%; }
.mini:hover img { transform: rotateY(-16deg) translateX(4px); box-shadow: 18px 16px 30px rgba(60, 40, 20, .28); }
.mini b { display: block; font-size: 15px; line-height: 1.25; }
.mini span { display: block; color: var(--muted); font-size: 13px; margin-top: 2px; }

/* ---------- try ---------- */
.try { --sheet: var(--cream-2); padding: 100px 0; background: var(--sheet) var(--grain); }
.tabs { position: relative; display: flex; gap: 6px; margin-top: 30px; border-bottom: 2px solid var(--line); overflow-x: auto; overflow-y: hidden; scrollbar-width: none; }
.tabs::-webkit-scrollbar { display: none; }
.tab { flex: none; padding: 14px 18px; min-height: 48px; border: 0; background: none; font-weight: 700; color: var(--muted); transition: color .2s; }
.tab.is-on { color: var(--ink); }
.tab-ink { position: absolute; left: 0; bottom: -2px; height: 3px; width: 0; background: var(--green); border-radius: 3px; transition: transform .5s var(--ease-out), width .5s var(--ease-out); }
/* The six formats need about 900 px in one row. Below 1080 px wide (tablets, phones, small laptops) they become a grid
   of chips, so every format is in view and none hides behind a sideways scroll. The row keeps about 100 px to spare,
   so a wider fallback font or a longer label still fits. */
@media (max-width: 1079px) {
  .tabs { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px; border-bottom: 0; overflow: visible; }
  .tab { padding: 8px 10px; border: 2px solid var(--line); border-radius: 999px; background: var(--paper); line-height: 1.2; transition: color .2s, background-color .2s, border-color .2s; }
  .tab.is-on { background: var(--ink); border-color: var(--ink); color: var(--cream); }
  .tab-ink { display: none; }
}
@media (max-width: 1079px) and (hover: hover) { .tab:hover { border-color: var(--ink); } }
@media (max-width: 599px) { .tabs { grid-template-columns: repeat(2, minmax(0, 1fr)); } .tab { padding-inline: 6px; font-size: 15px; } }
.panel { padding-top: 34px; }
.panel[hidden] { display: none; }
.panel-grid { display: grid; grid-template-columns: minmax(0, 1.55fr) minmax(280px, .85fr); gap: clamp(24px, 4vw, 60px); align-items: start; }
.panel-side h3 { margin-top: 6px; margin-bottom: 12px; }
.panel-side p { color: var(--muted); }
.price-card { margin-top: 26px; padding: 18px; border-radius: var(--radius); background: var(--paper) var(--grain); box-shadow: var(--shadow-soft); display: grid; gap: 12px; }
.price-card b { font-family: var(--font-display); font-size: 20px; display: block; font-weight: 560; }
.price-card > div > span { display: block; color: var(--muted); font-size: 14px; }
.price-pills { display: flex; gap: 8px; flex-wrap: wrap; }
.price-pills span { padding: 6px 12px; border-radius: 999px; background: var(--cream-2); font-weight: 700; font-size: 14px; }
.price-pills span.soft { background: transparent; border: 1.5px dashed var(--line); color: var(--muted); }
@media (max-width: 960px) { .panel-grid { grid-template-columns: 1fr; } }

/* flipbook */
.flip-stage { position: relative; width: 100%; aspect-ratio: 16 / 9.4; border-radius: var(--radius); outline: none; }
.flip-stage:focus-visible { box-shadow: 0 0 0 3px var(--ochre); }
.flip-gl { position: absolute; inset: 0; width: 100%; height: 100%; touch-action: pan-y; cursor: grab; -webkit-user-select: none; user-select: none; }
.flip-gl:active { cursor: grabbing; }
.flip-fallback { position: absolute; inset: 6%; display: flex; align-items: center; justify-content: center; }
.flip-fallback img { max-width: 100%; max-height: 100%; border-radius: 4px; box-shadow: var(--shadow); }
.flip-fallback[hidden] { display: none; }
.flip-hint { position: absolute; left: 50%; top: 0; transform: translateX(-50%); pointer-events: none; transition: opacity .5s; white-space: nowrap; }
.flip-hint.is-gone { opacity: 0; }
/* Read to me sits in the bar under the book, which is the same height on every spread, so play and pause never move
   while the book turns its own pages. Its width fits the longer "Pause the story" label too. It goes under the page
   arrows unless the book column leaves it about 30 px of air beside the next-page arrow (745 px; the widest column
   is about 753 px, on the biggest screens). */
.flip-wrap { container-type: inline-size; }
.flip-bar { display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; gap: 12px; margin-top: 16px; }
.flip-nav { grid-column: 2; display: flex; align-items: center; gap: 18px; }
.flip-bar .read-btn { justify-self: end; min-width: 11.5em; }
@container (max-width: 744px) { .flip-bar { grid-template-columns: auto; justify-content: center; } .flip-nav { grid-column: auto; } .flip-bar .read-btn { justify-self: center; } }
.flip-count { color: var(--muted); font-weight: 700; min-width: 170px; text-align: center; font-size: 15px; }
.flip-text { font-family: var(--font-display); font-size: 19px; color: var(--ink) !important; font-variation-settings: 'opsz' 20, 'SOFT' 50; min-height: 6.5em; }
.flip-text b { color: var(--brown); font-weight: 650; }
@media (max-aspect-ratio: 5/4) { .flip-stage { aspect-ratio: 4 / 4.4; } }
@media (max-width: 960px) and (min-aspect-ratio: 5/4) { .flip-stage { aspect-ratio: 16 / 10; } }

/* colouring */
.color-wrap { display: grid; gap: 14px; }
.crayons { display: flex; gap: 9px; flex-wrap: wrap; align-items: flex-end; padding-top: 16px; }
.crayon { width: 34px; height: 66px; border: 0; padding: 0; border-radius: 6px 6px 4px 4px; position: relative; transition: transform .3s var(--ease-out); background: var(--c); box-shadow: inset -6px 0 0 rgba(0, 0, 0, .12), inset 4px 0 0 rgba(255, 255, 255, .18), 0 6px 12px rgba(60, 40, 20, .2); }
.crayon::before { content: ''; position: absolute; left: 50%; top: -14px; width: 0; height: 0; transform: translateX(-50%); border-left: 17px solid transparent; border-right: 17px solid transparent; border-bottom: 14px solid var(--c); filter: brightness(1.08); }
.crayon::after { content: ''; position: absolute; left: 0; right: 0; top: 16px; height: 9px; background: rgba(255, 255, 255, .5); }
.crayon.is-on { transform: translateY(-14px); }
.crayon.eraser { background: #fff; box-shadow: inset 0 0 0 2px var(--line); }
.crayon.eraser::before { border-bottom-color: #fff; }
.canvas-frame { background: #fff; border-radius: 10px; padding: 10px; box-shadow: var(--shadow); }
#colorCanvas { width: 100%; height: auto; aspect-ratio: 1; cursor: crosshair; touch-action: manipulation; display: block; }
.color-bar { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
.page-pick { display: flex; gap: 8px; }
.page-pick button { width: 56px; height: 56px; border-radius: 10px; border: 2px solid var(--line); padding: 0; overflow: hidden; background: #fff; }
.page-pick button img { width: 100%; height: 100%; object-fit: cover; }
.page-pick button.is-on { border-color: var(--green); box-shadow: 0 0 0 3px rgba(63, 107, 85, .25); }
.color-tools { display: flex; gap: 8px; align-items: center; }
@media (pointer: coarse) { .crayon { width: 38px; height: 70px; } .crayon::before { border-left-width: 19px; border-right-width: 19px; } }
/* The 13 crayons wrap into two rows below about 655 px wide with touch-size crayons (600 px with a mouse): phones, small
   tablets and split screens. There, shorter crayons and a row gap wider than the tip plus the lift (28 px) keep a picked
   crayon in the second row clear of the one above it, in the same height as before. */
@media (max-width: 679px) { .crayons { row-gap: 32px; padding-top: 30px; } .crayon { height: 52px; } .crayon::after { top: 12px; } }
/* On two columns the page is sized by the screen's height as well as its width, so the crayons and the whole page fit
   under the top bar of a laptop (196 px holds the top bar, the crayons and a little air; 520 px is the floor). It sits
   centred in the usual column, so the side column and its price card stay where they are on every tab. The page
   picker, Undo and Save move up into the side column (try-color.js). The crayon gaps close up a little when the page
   is narrow, so the 13 crayons always stay on one row. */
@media (min-width: 961px) {
  .color-wrap { width: min(100%, max(520px, 100svh - 196px)); justify-self: center; }
  .crayons { column-gap: clamp(2px, (100% - 13 * 34px) / 12, 9px); }
  #panel-color .panel-side .color-bar { margin-top: 22px; justify-content: flex-start; }
}
@media (min-width: 961px) and (pointer: coarse) { .crayons { column-gap: clamp(2px, (100% - 13 * 38px) / 12, 9px); } }

/* reveal */
.reveal-wrap { display: grid; gap: 12px; }
.reveal-frame { position: relative; background: #fff; border-radius: 10px; padding: 10px; box-shadow: var(--shadow); container-type: inline-size; }
/* On two columns the grid is sized by the screen's height too, so the whole puzzle and the picture it becomes fit under
   the top bar (158 px holds the top bar, the frame's padding, the progress bar and a little air; .783 is the grid's
   36 : 46 shape). Its squares never go under 16 px (598 px wide), so the codes stay easy to read and to drag over. Like
   the colouring page, it sits centred in the usual column. */
@media (min-width: 961px) { .reveal-wrap { width: min(100%, max(598px, (100svh - 158px) * .783 + 20px)); justify-self: center; } }
#revealCanvas { width: 100%; height: auto; display: block; touch-action: none; cursor: cell; }
.reveal-done { position: absolute; inset: 0; display: grid; place-items: center; align-content: center; gap: 4px; pointer-events: none; }
.reveal-done b { font-family: var(--font-display); font-size: clamp(28px, min(8.5cqw, 5vw), 64px); color: var(--brick); background: rgba(255, 253, 248, .92); padding: 6px 22px; border-radius: 12px; box-shadow: var(--shadow); }   /* sized by the frame, so a smaller grid is not covered more, and never above 5vw, so a wide one-column grid is not covered more either */
.reveal-done .hand { font-size: 30px; }
.reveal-done[hidden] { display: none; }
.reveal-progress { position: relative; height: 10px; border-radius: 999px; background: var(--paper); box-shadow: inset 0 0 0 1px var(--line); }
.reveal-progress div { height: 100%; width: 0; border-radius: 999px; background: linear-gradient(90deg, var(--green), var(--ochre)); transition: width .3s; }
.reveal-progress span { position: absolute; right: 0; top: 14px; font-size: 13px; color: var(--muted); font-weight: 700; }
.key { list-style: none; padding: 0; margin: 14px 0 0; display: grid; gap: 6px; }
.key li { display: grid; grid-template-columns: 38px 1fr auto; align-items: center; gap: 10px; padding: 6px 8px; min-height: 48px; border-radius: 12px; background: var(--paper); border: 2px solid transparent; cursor: pointer; transition: border-color .2s, opacity .3s; }
.key li:hover { border-color: var(--line); }
.key li.is-on { border-color: var(--ink); }
.key li.is-done { opacity: .5; }
.key .sw { width: 38px; height: 38px; border-radius: 9px; display: grid; place-items: center; font-weight: 700; color: #fff; text-shadow: 0 1px 2px rgba(0, 0, 0, .45); box-shadow: inset 0 0 0 1px rgba(0, 0, 0, .15); }
.key .nm { font-size: 14px; font-weight: 700; }
.key .nm small { display: block; color: var(--muted); font-weight: 400; }
.key .fill { padding: 6px 12px; min-height: 34px; border-radius: 999px; border: 2px solid var(--line); background: var(--paper); font-size: 12px; font-weight: 700; }
.key .fill:hover { border-color: var(--ink); }

/* ---------- cast ---------- */
.cast { --sheet: var(--cream); padding-bottom: 60px; background: var(--sheet) var(--grain); overflow: clip; }
.cast-rows { margin-top: 36px; display: grid; gap: 56px; }
.cast-row { width: min(1220px, 100% - 2 * var(--gutter)); margin-inline: auto; display: grid; grid-template-columns: minmax(0, 1.4fr) minmax(260px, .7fr); gap: 34px; align-items: center; }
.cast-row:nth-child(even) { grid-template-columns: minmax(260px, .7fr) minmax(0, 1.4fr); }
.cast-row:nth-child(even) .cast-fig { order: 2; }
.cast-art { position: relative; border-radius: var(--radius); background: var(--paper); box-shadow: var(--shadow-soft); overflow: hidden; aspect-ratio: 16 / 9; }
/* The picture's box is taller than the card for the drift (below: the frame's view() timeline), so cover would crop the sides of the 16:9 art; contain shows every character whole, and the white fill
   (the art's own paper) covers the drift above and below. The name tags' left% are in the picture's own coordinates. */
.cast-pic > img:not([data-mk-layer]) { position: absolute; inset: -8% 0; width: 100%; height: 116%; object-fit: contain; background: #fff; }
/* the picture's frame: it rises in (its translate) and drifts (its transform); the making's layers ride inside it */
.cast-pic { position: absolute; inset: 0; }
/* the names are printed (data-printed): in place from the first frame, never faded in or held back by the scroll */
.cast-tag { position: absolute; translate: -50% -4px; background: var(--ink); color: var(--cream); font-family: var(--font-hand); font-size: 21px; font-weight: 600; padding: 2px 12px; border-radius: 999px; white-space: nowrap; transition: translate .4s var(--ease-out), scale .4s var(--ease-out); pointer-events: none; }
.cast-copy h3 { margin-bottom: 10px; }
.cast-copy p { color: var(--muted); }
.cast-books { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 16px; }
.cast-books button { display: inline-flex; align-items: center; gap: 8px; padding: 6px 12px 6px 6px; min-height: 44px; border-radius: 999px; border: 2px solid var(--line); background: var(--paper); font-size: 14px; font-weight: 700; text-align: left; }
.cast-books button img { width: 30px; height: 30px; object-fit: cover; border-radius: 50%; }
.cast-books button:hover { border-color: var(--ink); }
@media (max-width: 900px) { .cast-row, .cast-row:nth-child(even) { grid-template-columns: 1fr; } .cast-row:nth-child(even) .cast-fig { order: 0; } }
/* the gentle drift: the picture's frame moves ±2% as its card crosses the screen, on a named view timeline declared on
   the card (an anonymous view() would bind to the card itself, which clips and so is a scroll container that never
   scrolls); compositor-only, and off in calm. The picture is 116% tall inside the frame, so no edge ever shows. The
   making's layers live inside the frame and drift with it. */
@supports (animation-timeline: view()) {
  .cast-art { view-timeline: --cast-drift block; }
  html[data-motion="full"] .cast-pic { animation: cast-drift linear both; animation-timeline: --cast-drift; animation-range: cover; }
}
@keyframes cast-drift { from { transform: translateY(-2%); } to { transform: translateY(2%); } }
/* the printed caption under a made picture (Crumblecombe's colouring) */
.cast-fig { margin: 0; min-width: 0; }
.cast-caption { margin: 12px 0 0; font-size: 19px; color: var(--muted); text-align: center; }
/* (its line is a whole 24 px on a phone: at the inherited 1.55 it was 24.8, which moved the whole page below it off the
   device-pixel grid it had, and the footer mark's snowflakes drew differently: make-check's logo rule) */
@media (max-width: 600px) { .cast-caption { font-size: 16px; line-height: 24px; margin-top: 8px; } }

/* ---------- series ---------- */
.series { --sheet: var(--night); color: var(--cream); background: var(--sheet) var(--grain); padding-bottom: 110px; }
.series { --sky: radial-gradient(60% 50% at 85% 10%, rgba(227, 169, 59, .16), transparent 70%), radial-gradient(70% 60% at 0% 100%, rgba(60, 143, 140, .14), transparent 70%); }
.series-sky { position: absolute; inset: 0; pointer-events: none; background: var(--sky); }
/* Torn, the sky over the sheet (and over its words, as it always lay) stops 10 px short of the tears, and the two
   strips paint it under their own tears instead: it never shows as a rectangle above a tear, and where the strip's
   slack meets the sky element there is exactly one sky. Every copy is sized and placed by the whole section. */
.series.is-torn .series-sky { inset: calc(var(--tear-h) + 10px) 0; background-size: 100% var(--sec-h, 100%); background-position: 0 calc(var(--tear-h) * -1 - 10px); background-repeat: no-repeat; }
.series.is-torn .seam { background: var(--sky), var(--sheet) var(--grain); background-size: 100% var(--sec-h, 100%), 100% var(--sec-h, 100%), auto; background-repeat: no-repeat, no-repeat, repeat; }
.series.is-torn .seam.seam-bottom { background-position: 0 calc(var(--tear-h) + 10px - var(--sec-h, 0px)); }
.series .kicker { color: var(--ochre); }
/* Rows of up to three 330 px-or-wider cards, as auto-fit columns would give, but a short last row is centred instead of
   leaving an empty cell (five series: 3 + 2, or 2 + 2 + 1 on a tablet). */
.series-grid { position: relative; display: flex; flex-wrap: wrap; justify-content: center; gap: 22px; margin-top: 38px; container-type: inline-size; }
.series-grid > .series-card { flex: 0 0 100%; min-width: 0; }
@container (min-width: 682px) { .series-grid > .series-card { flex-basis: calc((100% - 22px) / 2); } }
@container (min-width: 1034px) { .series-grid > .series-card { flex-basis: calc((100% - 44px) / 3); } }
/* A column, so the covers and the price row sit on one line across a grid row: a two-line title only takes some of the
   space above its covers. The stack is the cover height plus room for the hover lift. */
.series-card { position: relative; display: flex; flex-direction: column; background: rgba(255, 255, 255, .045); border: 1px solid rgba(255, 255, 255, .1); border-radius: var(--radius); padding: 22px 22px 20px; overflow: hidden; }
.series-card h3 { font-weight: 480; font-variation-settings: 'opsz' 48, 'SOFT' 80, 'WONK' 1; }
.series-card .meta { color: rgba(247, 241, 230, .7); font-size: 14px; margin-top: 4px; }
.stack { position: relative; flex: none; height: 188px; margin: auto 0 16px; perspective: 1000px; }
.stack button { position: absolute; bottom: 0; padding: 0; border: 0; background: none; transform-origin: 50% 100%; transition: transform .55s var(--ease-out); }
.stack.is-placing button { transition: none; }      /* a layout written in place (covers arriving, a resize): js/app.js renderSeries */
.stack img { height: 156px; width: auto; border-radius: 2px 5px 5px 2px; box-shadow: 0 16px 30px rgba(0, 0, 0, .45); }
.series-foot { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
/* The price line is pieces that each stay whole ("singles CA$x" never leaves its price behind), joined by a dot. Every
   piece carries its dot in front, and the line is shifted left by one dot and clipped there: the dot of whichever piece
   starts a line falls in the clipped strip, so a wrapped line never ends or begins with a lone separator. */
.series-foot .save { display: flex; flex-wrap: wrap; align-items: baseline; margin-left: -1.1em; clip-path: inset(-1em -1em -1em 1.1em); font-size: 14px; color: rgba(247, 241, 230, .78); }
.series-foot .save > span { white-space: nowrap; }
.series-foot .save > span::before { content: '·'; content: '·' / ''; display: inline-block; width: 1.1em; text-align: center; }
.series-foot .save b { color: var(--ochre); font-size: 18px; font-family: var(--font-display); }

/* ---------- made ---------- */
.made { padding: 120px 0 110px; background: var(--cream) var(--grain); }
.made-grid { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, .9fr); gap: clamp(28px, 5vw, 80px); align-items: center; }
.steps { list-style: none; counter-reset: s; padding: 0; margin: 32px 0 0; display: grid; gap: 24px; position: relative; }
.steps::before, .steps::after { content: ''; position: absolute; left: 21px; top: 24px; bottom: 24px; width: 2px; background: var(--line); }
.steps::after { background: var(--brown); transform-origin: top; transform: scaleY(var(--drawn, 0)); }
.steps li { position: relative; padding-left: 66px; color: var(--muted); }
.steps li b { color: var(--ink); display: block; font-family: var(--font-display); font-size: 21px; font-weight: 560; margin-bottom: 4px; }
.steps li::before { counter-increment: s; content: counter(s); position: absolute; left: 0; top: 0; z-index: 1; width: 44px; height: 44px; border-radius: 50%; background: var(--paper); border: 2px solid var(--brown); color: var(--brown); display: grid; place-items: center; font-family: var(--font-display); font-weight: 700; font-size: 18px; transition: background .4s, color .4s; }
.steps li.is-lit::before { background: var(--brown); color: var(--cream); }
/* Two real pages laid on the desk, each with the check it passed in step 3: a colouring page and a puzzle page. The
   puzzle page is the left half of a Look inside spread. Captions sit on one line under both. */
.made-fig { margin: 0; position: relative; display: grid; grid-template-columns: 1fr 1fr; gap: clamp(16px, 2.6vw, 30px); align-items: end; }
.proof { margin: 0; }
.proof-page { background: #fff; border-radius: 3px; box-shadow: var(--shadow); overflow: hidden; transform: rotate(-1.6deg); }
.proof + .proof .proof-page { transform: rotate(1.3deg); }
.proof-page img { display: block; width: 100%; height: auto; }
.proof-cbn { aspect-ratio: 800 / 1036; }
.proof-cbn img { width: 200%; max-width: none; }
.proof figcaption { margin-top: 16px; text-align: center; font-family: var(--font-hand); font-size: 22px; font-weight: 600; line-height: 1.15; color: var(--brown); }
@media (max-width: 900px) { .made-grid { grid-template-columns: 1fr; } .made-fig { width: min(100%, 620px); margin-inline: auto; } }
@media (max-width: 600px) { .proof figcaption { margin-top: 12px; font-size: min(18px, 4.4vw); } }      /* one line each, down to 360 px */

/* ---------- free ---------- */
.free { --sheet: var(--green); padding-bottom: 90px; background: var(--sheet) var(--grain); color: #fff; }
.free .kicker { color: var(--ochre); }
.free .sub { color: rgba(255, 255, 255, .82); }
.free-inner { display: grid; grid-template-columns: 1.2fr 1fr; gap: 40px; align-items: center; }
/* the words span the form's two rows: the field and its button, then the consent line under them */
.free-inner > div:first-child { grid-column: 1; grid-row: 1 / span 2; }
.free-form { grid-column: 2; grid-row: 1; align-self: end; display: flex; gap: 10px; background: rgba(255, 255, 255, .1); padding: 10px; border-radius: 999px; }
.free-form input { flex: 1; min-width: 0; min-height: 48px; border: 0; background: #fff; border-radius: 999px; padding: 12px 18px; font: inherit; color: var(--ink); }
/* the consent line under the email field (printed: never faded, held back or covered; make-check finds it by
   data-consent). Its privacy link waits for the privacy page, a launch blocker of its own. Whole-pixel lines, so the
   page under it keeps its pixel grid (see the footer mark in DESIGN.md). */
.free-consent { grid-column: 2; grid-row: 2; align-self: start; margin: -28px 0 0 14px; font-size: 14px; line-height: 20px; color: rgba(255, 255, 255, .78); }
@media (max-width: 900px) { .free-inner { grid-template-columns: 1fr; } .free-inner > div:first-child, .free-form, .free-consent { grid-column: 1; grid-row: auto; } .free-form { flex-direction: column; border-radius: 24px; } .free-consent { margin-left: 6px; } }

/* ---------- footer ---------- */
/* a torn top like every other sheet (index.html): positioned so the tear lies over the free page, and padded past the tear */
.footer { --sheet: var(--night-deep); position: relative; padding: calc(var(--tear-h) + 54px) 0 calc(60px + env(safe-area-inset-bottom)); background: var(--sheet) var(--grain); color: rgba(247, 241, 230, .8); font-size: 15px; }
.footer-grid { display: grid; grid-template-columns: auto 1fr; gap: 24px 48px; align-items: center; }
.foot-brand { display: flex; align-items: center; gap: 14px; }
.foot-brand img { border-radius: 50%; }
.foot-brand b { display: block; font-family: var(--font-display); font-size: 20px; color: #fff; font-weight: 560; }
.foot-links { display: flex; gap: 22px; flex-wrap: wrap; font-weight: 700; }
.foot-links a { text-decoration: none; opacity: .85; }
.foot-links a:hover { opacity: 1; }
/* the footer note is printed (data-printed: opacity 1, always): its softer tone is in its colour, .8 x .7 of the cream */
.foot-note { grid-column: 1 / -1; max-width: 80ch; color: rgba(247, 241, 230, .56); }
.foot-legal { grid-column: 1 / -1; display: flex; flex-wrap: wrap; gap: 6px 20px; margin-top: -10px; font-size: 14px; line-height: 20px; }
.foot-legal a { color: rgba(247, 241, 230, .72); text-underline-offset: 3px; }
.foot-legal a:hover { color: #fff; }
.free-form .hp { position: absolute; left: -10000px; width: 1px; height: 1px; overflow: hidden; }
.free-consent a { color: inherit; text-decoration: underline; text-underline-offset: 3px; }
@media (max-width: 700px) { .footer-grid { grid-template-columns: 1fr; } }

/* ---------- viewer (3D book + torn-paper panel) ---------- */
.viewer { position: fixed; inset: 0; z-index: 80; }
.viewer[hidden] { display: none; }
.viewer-backdrop { position: absolute; inset: 0; background: rgba(239, 229, 211, .62); backdrop-filter: blur(14px) saturate(1.1); -webkit-backdrop-filter: blur(14px) saturate(1.1); opacity: 0; transition: opacity .5s; }
.viewer.is-open .viewer-backdrop { opacity: 1; }
.viewer-gl { position: absolute; inset: 0; width: 100%; height: 100%; touch-action: none; cursor: grab; }
.viewer-gl:active { cursor: grabbing; }
.viewer-fallback { position: absolute; right: 6vw; top: 50%; width: min(36vw, 420px); transform: translateY(-50%); perspective: 1600px; }
.viewer-fallback[hidden] { display: none; }
.viewer-panel { position: absolute; left: 0; top: 0; bottom: 0; width: min(560px, 44vw); background: var(--paper-2) var(--grain); box-shadow: 30px 0 60px rgba(60, 40, 20, .14);
  transform: translateX(-104%); transition: transform .7s var(--ease-out); overflow: hidden; }
.viewer-panel.is-torn {
  -webkit-mask: linear-gradient(#000 0 0) 0 0 / calc(100% - 30px) 100% no-repeat, var(--tear-side) right / 31px 100% no-repeat;
  mask: linear-gradient(#000 0 0) 0 0 / calc(100% - 30px) 100% no-repeat, var(--tear-side) right / 31px 100% no-repeat;
}
.viewer.is-open .viewer-panel { transform: none; }
.viewer-body { height: 100%; overflow-y: auto; overscroll-behavior: contain; padding: calc(var(--top-h) + 10px) 22px 48px clamp(22px, 3vw, 44px); margin-right: 32px; -webkit-overflow-scrolling: touch; scrollbar-width: thin; scrollbar-color: rgba(42,35,32,.25) transparent; }
.viewer-tools { position: absolute; right: 18px; top: calc(14px + env(safe-area-inset-top)); display: flex; gap: 10px; align-items: center; z-index: 2; }
.viewer-tools .btn { background: rgba(255, 253, 248, .85); }
.viewer-hint { position: absolute; right: 0; bottom: 22px; width: calc(100% - min(560px, 44vw)); text-align: center; pointer-events: none; opacity: 0; transition: opacity .6s .6s; }
.viewer.is-open .viewer-hint { opacity: 1; }
@media (max-width: 760px), (max-aspect-ratio: 105/100) {
  .viewer-panel { left: 0; right: 0; top: auto; width: auto; height: 58%; transform: translateY(104%); box-shadow: 0 -24px 50px rgba(60, 40, 20, .16); }
  .viewer-panel.is-torn {
    -webkit-mask: var(--tear-top) top / 100% 28px no-repeat, linear-gradient(#000 0 0) 0 27px / 100% calc(100% - 27px) no-repeat;
    mask: var(--tear-top) top / 100% 28px no-repeat, linear-gradient(#000 0 0) 0 27px / 100% calc(100% - 27px) no-repeat;
  }
  .viewer-body { padding: 40px var(--gutter) calc(40px + env(safe-area-inset-bottom)); margin-right: 0; }
  .viewer-hint { width: 100%; bottom: auto; top: calc(42% - 40px); }
  .viewer-fallback { right: 50%; top: 21%; width: 40vw; transform: translate(50%, -50%); }
}
.d-kind { display: flex; gap: 8px; flex-wrap: wrap; font-size: 13px; font-weight: 700; color: var(--muted); }
.d-kind span { padding: 4px 10px; border-radius: 999px; background: var(--paper); border: 1px solid var(--line); }
.viewer h2 { font-size: clamp(28px, 3vw, 38px); margin-top: 14px; max-width: none; font-variation-settings: 'opsz' 72, 'SOFT' 80; }
.d-hero { font-family: var(--font-display); font-style: italic; font-size: 20px; color: var(--brown); margin-top: 6px; }
.d-blurb { margin-top: 14px; }
.d-bullets { margin: 14px 0 0; padding-left: 20px; color: var(--muted); display: grid; gap: 6px; }
.d-specs { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 16px; }
.d-specs span { font-size: 13px; font-weight: 700; padding: 6px 10px; border-radius: 8px; background: var(--cream-2); }
.d-buy { margin-top: 22px; display: grid; gap: 10px; }
.buy-row { display: grid; grid-template-columns: 1fr auto; align-items: center; gap: 12px; padding: 14px 16px; min-height: 64px; border-radius: 14px; background: var(--paper); box-shadow: var(--shadow-soft); text-decoration: none; transition: transform .25s var(--ease-out), box-shadow .25s; }
a.buy-row:hover { transform: translateY(-2px); box-shadow: var(--shadow); }
.buy-row b { display: block; }
.buy-row small { color: var(--muted); }
.buy-row.is-soft { box-shadow: none; border: 1.5px dashed var(--line); background: transparent; }
.buy-row .price { font-family: var(--font-display); font-size: 22px; font-weight: 560; }
.d-gallery { display: flex; align-items: flex-start; gap: 12px; overflow-x: auto; margin-top: 24px; padding: 4px 2px 12px; scroll-snap-type: x mandatory; scrollbar-width: thin; }
.d-gallery button { flex: none; padding: 0; border: 0; background: none; border-radius: 10px; scroll-snap-align: start; cursor: zoom-in; }
.d-gallery img { display: block; height: clamp(220px, 34vh, 360px); width: auto; max-width: none; border-radius: 10px; box-shadow: var(--shadow-soft); background: var(--paper); }
.lightbox { position: fixed; inset: 0; z-index: 95; display: grid; place-items: center; background: rgba(15, 22, 48, .86); padding: 4vh 4vw; cursor: zoom-out; }
.lightbox[hidden] { display: none; }
.lightbox img { max-width: 100%; max-height: 92vh; width: auto; height: auto; border-radius: 10px; box-shadow: 0 30px 80px rgba(0, 0, 0, .5); }
.lightbox .lb-nav { position: absolute; top: 50%; transform: translateY(-50%); width: 52px; height: 52px; border-radius: 50%; border: 0; background: rgba(255, 253, 248, .92); font-size: 26px; cursor: pointer; }
.lightbox .lb-prev { left: 2vw; } .lightbox .lb-next { right: 2vw; }
.lightbox .lb-close { position: absolute; right: 2vw; top: 2vh; width: 46px; height: 46px; border-radius: 50%; border: 0; background: rgba(255, 253, 248, .92); font-size: 24px; cursor: pointer; }
.d-related { margin-top: 26px; }
.d-related h4 { font-family: var(--font-display); font-weight: 560; margin: 0 0 10px; font-size: 18px; }
.d-related-row { display: flex; gap: 12px; overflow-x: auto; padding-bottom: 6px; }
.d-related-row button { flex: none; width: 94px; border: 0; background: none; padding: 0; text-align: left; font-size: 12px; color: var(--muted); }
.d-related-row img { border-radius: 2px 5px 5px 2px; box-shadow: var(--shadow-soft); margin-bottom: 6px; height: 104px; width: auto; max-width: 94px; object-fit: contain; }

/* ---------- picks ---------- */
.overlay { position: fixed; inset: 0; background: rgba(15, 22, 48, .5); z-index: 85; }
.overlay[hidden] { display: none; }
.picks { position: fixed; top: 0; right: 0; bottom: 0; width: min(420px, 100%); background: var(--paper) var(--grain); z-index: 90; box-shadow: -20px 0 60px rgba(0, 0, 0, .3); transform: translateX(100%); transition: transform .45s var(--ease-out); display: flex; flex-direction: column; padding-top: env(safe-area-inset-top); }
.picks.is-open { transform: translateX(0); }
.picks[hidden] { display: none; }
.picks-head { display: flex; align-items: center; justify-content: space-between; padding: 18px 20px; border-bottom: 1px solid var(--line); }
.picks-head h3 { font-size: 24px; }
.picks-body { flex: 1; overflow-y: auto; padding: 14px 20px; display: grid; gap: 12px; align-content: start; overscroll-behavior: contain; }
.pick { display: grid; grid-template-columns: 56px 1fr auto; gap: 12px; align-items: center; }
.pick img { height: 72px; width: 56px; object-fit: contain; }
.pick b { display: block; font-size: 15px; line-height: 1.25; }
.pick a { font-size: 13px; font-weight: 700; color: var(--green); }
.pick .rm { border: 0; background: none; color: var(--muted); font-size: 22px; width: 44px; height: 44px; }
.picks-empty { color: var(--muted); text-align: center; padding: 40px 10px; }
.picks-note { padding: 14px 20px calc(20px + env(safe-area-inset-bottom)); font-size: 13px; color: var(--muted); border-top: 1px solid var(--line); }

/* ---------- toast ---------- */
.toast { position: fixed; left: 50%; bottom: calc(24px + env(safe-area-inset-bottom)); transform: translate(-50%, 20px); background: var(--ink); color: var(--cream); padding: 12px 18px; border-radius: 12px; font-weight: 700; opacity: 0; pointer-events: none; transition: opacity .3s, transform .3s; z-index: 100; max-width: calc(100% - 32px); }
.toast.is-on { opacity: 1; transform: translate(-50%, 0); }

/* ---------- motion helpers ---------- */
/* an entrance moves by the translate property, never transform, so a hover's transform (or a drift) never fights it */
html.js .rise { opacity: 0; translate: 0 26px; }
html.js .rise.is-in { opacity: 1; translate: none; transition: opacity .9s ease, translate 1s var(--ease-out); }
.word-mask { display: inline-block; overflow: hidden; vertical-align: top; padding: 0 .06em .1em 0; margin: 0 -.06em -.1em 0; }
/* no will-change here: riseHeading (app.js) gives a heading's words one only while the heading rises; a permanent hint kept
   every heading word on a layer of its own at rest (DESIGN.md, Quality on every device) */
.word { display: inline-block; }
html.js .split-wait .word { transform: translateY(105%); }
.split-in .word { transform: none; transition: transform 1s var(--ease-out); transition-delay: calc(var(--i) * 45ms); }
/* calm motion (js/motion.js: the visitor's choice, else the system's reduce-motion setting): nothing slides in */
html[data-motion="calm"].js .rise { opacity: 1; translate: none; }
html[data-motion="calm"].js .split-wait .word { transform: none; }
html[data-motion="calm"] .scroll-cue span { animation: none; }
html[data-motion="calm"] .menu { transition: none; }
html[data-motion="calm"] :is(.viewer-panel, .picks, .btn, .moment, .mini img, .stack button) { transition: none !important; }
@media (prefers-reduced-motion: reduce) { html.no-js .scroll-cue span { animation: none; } }

/* no WebGL: the hero is a poster, the shelf is a grid */
html.no-gl .poster { height: 100vh; height: 100svh; bottom: auto; }
/* the still's top under the top bar is night, so the hanging piece's thread never runs through the links, whatever the
   screen's shape crops or scales (the live scene fades its own thread below the bar) */
html.no-gl .poster::before { content: ''; position: absolute; inset: 0 0 auto; z-index: 1; height: calc(var(--top-h) + 64px); pointer-events: none;
  background: linear-gradient(to bottom, var(--night-deep) calc(var(--top-h) + 4px), rgba(15, 22, 48, 0)); }
html.no-gl .hero-copy { position: relative; top: auto; left: auto; right: auto; transform: none; width: min(590px, 100% - 2 * var(--gutter)); margin-left: var(--gutter); padding: 26vh 0 14vh; min-height: 100vh; opacity: 1 !important; visibility: visible !important; }
html.no-gl .shelf-head { margin-top: 0; transform: none; }
@media (max-aspect-ratio: 41/50) { html.no-gl .hero-copy { padding-top: 58vh; } }
/* No WebGL on a portrait screen. A season without portrait stills keeps the landscape one, cropped, so a scrim keeps
   the copy readable over the painting. Every season has them, though (tools/site-check/poster.mjs --portrait; app.js
   noGl adds them as sources): stills of the live portrait composition, the book at the top and dark desk below, the
   phone one on tall screens and the tablet one up to the portrait limit. Each spans the full width, so the book is
   never cropped, and fades into the stage's navy at its foot. The copy sits at the foot of the screen, as in the live
   hero, but never higher than just under the book, whose lowest edge is at 98.7% of the width in the phone still and
   71.4% in the tablet one (padding percentages are of the width). */
@media (max-aspect-ratio: 41/50) {
  html.no-gl .poster::after { content: ''; position: absolute; inset: 0; background: linear-gradient(to bottom, transparent 38%, rgba(8, 10, 20, .72) 54%, rgba(8, 10, 20, .85)); }
  html.no-gl .stage.portrait-stills .poster { height: auto; }
  html.no-gl .stage.portrait-stills .poster-plate { position: relative; display: block; height: auto; aspect-ratio: 1640 / 2360; object-position: 50% 0; }
  html.no-gl .stage.portrait-stills .poster::after { background: linear-gradient(to bottom, transparent 46%, rgba(15, 22, 48, .45) 66%, rgba(15, 22, 48, .8) 88%, var(--night-deep)); }
  html.no-gl .stage.portrait-stills .hero-copy { display: flex; flex-direction: column; justify-content: flex-end; min-height: 100vh; min-height: 100svh; bottom: auto;
    padding: calc(71.4% + 24px) 0 26px; padding-bottom: max(22px, 4svh); }
}
@media (max-aspect-ratio: 1/2) {
  html.no-gl .stage.portrait-stills .poster-plate { aspect-ratio: 780 / 1688; }
  html.no-gl .stage.portrait-stills .hero-copy { padding-top: calc(98.7% + 24px); }
}

/* the viewer without WebGL: the cover, which flips to the back on tap */
.fb-book { position: relative; transform-style: preserve-3d; transition: transform .9s var(--ease-out); cursor: pointer; }
.fb-book.is-back { transform: rotateY(180deg); }
.fb-book img { width: 100%; border-radius: 3px 8px 8px 3px; box-shadow: var(--shadow); backface-visibility: hidden; -webkit-backface-visibility: hidden; }
.fb-book img.back { position: absolute; inset: 0; transform: rotateY(180deg); border-radius: 8px 3px 3px 8px; }
.shelf-tag.is-focused { outline: 2px solid var(--ochre); outline-offset: 2px; }
html.is-locked { overflow: hidden; }

/* no JavaScript (or no ES modules): a plain poster hero and an honest pointer to the shop */
html.no-js .journey { height: auto; }
html.no-js .stage { position: relative; height: 100vh; }
html.no-js .gl, html.no-js .caption, html.no-js .shelf-head, html.no-js .rail, html.no-js .scroll-cue, html.no-js .shelf-tag, html.no-js .hero-hint { display: none; }
html.no-js .split-wait .word { transform: none; }
html.no-js .rise { opacity: 1; translate: none; }

/* ---------- the mark (js/logo.js): three layers of one clean illustration, alive on the page ---------- */
.brand-mark { position: relative; width: 48px; height: 48px; border-radius: 50%; flex: none; display: grid; place-items: center; overflow: hidden;
  background: radial-gradient(circle at 50% 38%, #fffdf6, #f3e8d4 78%); box-shadow: 0 0 0 2px rgba(227, 169, 59, .95), 0 6px 18px rgba(8, 12, 30, .35); }
.brand-mark img { width: 100%; height: 100%; object-fit: cover; }
.brand-mark .logo-mark { width: 100%; height: 100%; }
/* the still badge (for visitors without JS) and the live mark share one cell, the live one on top. While the script
   takes over, the still one waits hidden, so the entrance starts from an empty disc and nobody sees a finished logo
   taken away; if the script is still missing after 2 s (a slow link) it shows, and logo.js then swaps it for the live
   mark complete, with no entrance. Calm motion shows it at once. */
.brand-mark > * { grid-area: 1 / 1; }
html.js .brand-mark > img { visibility: hidden; animation: lm-late 0s 2s forwards; }
html.js[data-motion="calm"] .brand-mark > img { visibility: visible; animation: none; }
@keyframes lm-late { to { visibility: visible; } }
.topbar.is-solid .brand-mark, .topbar.is-day .brand-mark { box-shadow: 0 0 0 2px rgba(201, 142, 33, .9), 0 4px 12px rgba(60, 40, 20, .18); }
@media (max-width: 720px) { .brand-mark { width: 40px; height: 40px; } }      /* the compact bar (topbar rules) */
.foot-mark { width: 150px; height: 142px; flex: none; }
.foot-mark .logo-mark { width: 100%; height: 100%; }
.menu-mark { position: absolute; right: var(--gutter); bottom: 70px; width: min(40vw, 260px); aspect-ratio: 950 / 900; opacity: .95; pointer-events: none; }
.menu-mark .logo-mark { width: 100%; height: 100%; }
/* HTML layers in a 1024 px box in the art's own pixels (logo.js fits the box to the element), so the moves run on the
   compositor and keep their pace while the page is busy; each part's pivot is set inline, in those pixels. No will-change:
   the browser gives a part its own layer while one of its CSS or Web Animations runs, so a part at rest (the stem and the
   flower in calm motion, the footer's mark until it is seen, the flight frames between flights) costs none */
.logo-mark { display: block; position: relative; overflow: visible; width: 100%; }   /* its height follows the crop's shape unless the holder sets it */
.logo-mark .lm-art { position: absolute; left: 0; top: 0; width: 1024px; height: 1024px; transform-origin: 0 0; pointer-events: none; }
.logo-mark .lm-art span, .logo-mark .lm-art img, .logo-mark .lm-art svg { position: absolute; display: block; }
.logo-mark .lm-layer { left: 0; top: 0; width: 1024px; height: 1024px; }
.logo-mark .lm-art img { max-width: none; border-radius: 0; background: none; box-shadow: none; object-fit: fill; user-select: none; }
.logo-mark .lm-stem { left: 0; top: 0; overflow: visible; }
.logo-mark .lm-lid { border-radius: 50%; transform: scaleY(0); }
.logo-mark .lm-flake { transform-origin: center; }
.logo-mark .lm-speck { border-radius: 50%; background: #b9c8d8; }
/* entrance: hidden only once JS has taken the mark over (no JS = a still, complete mark) */
.logo-mark.is-pending .lm-plant-art { opacity: 0; transform: scale(.7); }
.logo-mark.is-pending .lm-flower, .logo-mark.is-pending .lm-bird, .logo-mark.is-pending .lm-flakes { opacity: 0; }
.logo-mark.is-intro .lm-plant-art { animation: lm-rise .9s cubic-bezier(.2, .8, .2, 1) forwards; }
.logo-mark.is-intro .lm-flower { animation: lm-drop 1s .55s cubic-bezier(.3, 1.2, .5, 1) forwards; }
/* the bird has no rule here: it shows by its flight, scripted in logo.js (a curve CSS keyframes can't draw), which waits
   for the flight frames while the plant rises; if they are late it fades in on the stem instead */
.logo-mark.is-intro .lm-flakes { animation: lm-fade 1.2s 1.1s forwards; }
/* idle life */
.logo-mark.is-live .lm-plant { animation: lm-sway 7s linear infinite; }
.logo-mark.is-live .lm-flower { animation: lm-swing 4.6s linear infinite; }
.logo-mark.is-live .lm-bird { animation: lm-breathe 3.4s ease-in-out infinite; }
.logo-mark.is-live .lm-lid { animation: lm-blink 6.5s infinite; }
.logo-mark.is-intro .lm-flake, .logo-mark.is-live .lm-flake { animation: lm-fall var(--t, 9s) linear infinite; animation-delay: var(--d, 0s); }
/* the hop with a flutter of its wings, on hover, is scripted (logo.js) */
.brand:hover .logo-mark.is-live .lm-flower { animation: lm-swing 1.2s ease-in-out; }
@keyframes lm-rise { to { opacity: 1; transform: none; } }
@keyframes lm-fade { to { opacity: 1; } }
/* each flake falls through its own spot, swaying and turning, fading in above it and out below */
@keyframes lm-fall { 0% { opacity: 0; transform: translate(0, -64px) rotate(0); } 14% { opacity: .95; } 50% { transform: translate(10px, 0) rotate(70deg); } 86% { opacity: .9; } 100% { opacity: 0; transform: translate(-5px, 64px) rotate(140deg); } }
@keyframes lm-drop { 0% { opacity: 0; transform: translateY(-50px) rotate(-16deg); } 55% { opacity: 1; transform: translateY(7px) rotate(7deg); } 100% { opacity: 1; transform: none; } }
/* the plant's sway and the flower's swing start from rest, where the entrance leaves them, and move like a pendulum:
   quick through the middle, easing into each end of the swing (sine-shaped easing per quarter) */
@keyframes lm-sway {
  0%, 50%, 100% { transform: rotate(0); animation-timing-function: cubic-bezier(.61, 1, .88, 1); }
  25% { transform: rotate(1.3deg); animation-timing-function: cubic-bezier(.12, 0, .39, 0); }
  75% { transform: rotate(-1.1deg); animation-timing-function: cubic-bezier(.12, 0, .39, 0); }
}
@keyframes lm-swing {
  0%, 50%, 100% { transform: rotate(0); animation-timing-function: cubic-bezier(.61, 1, .88, 1); }
  25% { transform: rotate(4.5deg); animation-timing-function: cubic-bezier(.12, 0, .39, 0); }
  75% { transform: rotate(-3deg); animation-timing-function: cubic-bezier(.12, 0, .39, 0); }
}
@keyframes lm-breathe { 0%, 100% { transform: none; } 50% { transform: scale(1.01, 1.028); } }
@keyframes lm-blink { 0%, 93%, 100% { transform: scaleY(0); } 95%, 96.5% { transform: scaleY(1); } }
/* calm motion: the bird only breathes and blinks; the stem, the flower and the flakes hold still */
html[data-motion="calm"] .logo-mark :is(.lm-plant, .lm-flower, .lm-flake, .lm-plant-art, .lm-flakes) { animation: none !important; }

/* ---------- sound toggle ---------- */
.sound-btn { width: 42px; height: 42px; border-radius: 50%; border: 2px solid currentColor; background: transparent; display: inline-grid; place-items: center; opacity: .85; transition: opacity .2s, background .2s; }
.sound-btn:hover { opacity: 1; }
.sound-btn .sw-on { display: none; } .sound-btn.is-on .sw-on { display: inline; } .sound-btn.is-on .sw-off { display: none; }
.sound-btn.is-on { background: rgba(227, 169, 59, .18); }
@media (max-width: 1240px) { .motion-btn { margin-left: auto; } .motion-btn + .sound-btn, .sound-btn + .picks-btn { margin-left: 0; } }      /* with the nav gone (topbar rules) */
@media (max-width: 460px) { .sound-btn { width: 38px; height: 38px; } }
@media (max-width: 400px) { .sound-btn { width: 36px; height: 36px; } .sound-btn svg { width: 18px; height: 18px; } }
/* the motion toggle: pause bars while everything moves, a play arrow while calm (js/motion.js) */
.motion-btn .mo-play { display: none; }
html[data-motion="calm"] .motion-btn .mo-pause { display: none; }
html[data-motion="calm"] .motion-btn .mo-play { display: inline; }

/* ---------- read to me ---------- */
.read-btn { gap: 10px; }
.read-btn .ra-icon { width: 0; height: 0; border-left: 11px solid currentColor; border-top: 7px solid transparent; border-bottom: 7px solid transparent; }
.read-btn.is-playing .ra-icon { width: 11px; height: 13px; border: 0; border-left: 4px solid currentColor; border-right: 4px solid currentColor; }
.voices { display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: 6px; margin-top: 12px; }
.voices[hidden] { display: none; }
.voices-label { font-family: var(--font-hand); font-size: 20px; color: var(--muted); margin-right: 4px; }
.voices button { padding: 6px 12px; min-height: 36px; border-radius: 999px; border: 1.5px solid var(--line); background: var(--paper); font-size: 13px; font-weight: 700; }
.voices button[aria-checked="true"] { background: var(--ink); color: var(--cream); border-color: var(--ink); }
.voices-note { flex-basis: 100%; font-size: 12px; color: var(--muted); text-align: center; }
.flip-text .para { display: block; margin-bottom: .7em; }
.flip-text .s { border-radius: 3px; transition: background-color .35s, color .35s; }
.flip-text .s.is-reading { background: linear-gradient(transparent 52%, rgba(227, 169, 59, .42) 52%); color: var(--ink); }
.flip-text .s.is-read { color: var(--muted); }

/* ---------- trailers ---------- */
.trailer-btn { display: grid; grid-template-columns: 132px 1fr; align-items: center; gap: 14px; width: 100%; margin-top: 20px; padding: 8px; border-radius: 14px; border: 0; background: var(--ink); color: var(--cream); text-align: left; box-shadow: var(--shadow-soft); transition: transform .25s var(--ease-out); }
.trailer-btn:hover { transform: translateY(-2px); }
.trailer-btn img { width: 132px; aspect-ratio: 16 / 9; object-fit: cover; border-radius: 9px; }
.trailer-btn b { display: block; font-size: 16px; }
.trailer-btn small { color: rgba(247, 241, 230, .7); }
.trailer { position: fixed; inset: 0; z-index: 96; display: grid; place-items: center; background: rgba(10, 14, 32, .9); padding: 5vh 4vw; }
.trailer[hidden] { display: none; }
.trailer-frame { width: min(1200px, 100%); aspect-ratio: 16 / 9; border-radius: 14px; overflow: hidden; box-shadow: 0 40px 100px rgba(0, 0, 0, .6); background: #000; }
.trailer video { width: 100%; height: 100%; display: block; }
.trailer-close { position: absolute; right: 2vw; top: 2vh; background: rgba(255, 253, 248, .92); }

/* ---------- the hero poster: an image only browsers without WebGL see ---------- */
.poster-plate { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; object-position: 70% 50%; }

/* ---------- v4: the pop-up book. The poster is the dark room (the live scene lights itself up); the image is only
   for browsers without WebGL. ---------- */
.poster-sky { background: radial-gradient(110% 90% at 72% 38%, #24170e 0%, #0b0a0d 58%, #050506 100%); }
html:not(.no-gl) .poster-plate { display: none; }

/* ======================================================================================================== Oct 6 2026
   More to try: look inside, three more formats, the Advent calendar, the gift quiz, shared picks, a cast to meet */

/* ---------- shared bits ---------- */
.btn[hidden], .icon-btn[hidden], .menu-advent[hidden] { display: none; }       /* their own display would beat the attribute */
.pill-pick { display: flex; gap: 8px; flex-wrap: wrap; }
.pill-pick button, .draw-pick button { min-height: 44px; padding: 8px 16px; border-radius: 999px; border: 2px solid var(--line); background: var(--paper); font-weight: 700; font-size: 15px; transition: border-color .2s, background .2s, color .2s; }
.pill-pick button:hover { border-color: var(--ink); }
.pill-pick button[aria-pressed="true"] { background: var(--ink); color: var(--cream); border-color: var(--ink); }
.tool-bar { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
.tool-row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.tool-count { color: var(--muted); font-weight: 700; font-size: 15px; }
.link-btn { border: 0; background: none; padding: 0; font: inherit; font-weight: 700; color: var(--green); text-decoration: underline; text-underline-offset: 3px; cursor: pointer; }
.icon-btn:disabled { opacity: .35; cursor: default; }

/* ---------- look inside ---------- */
.look-btn { display: grid; grid-template-columns: 132px 1fr; align-items: center; gap: 14px; width: 100%; margin-top: 20px; padding: 8px; border-radius: 14px; border: 2px solid var(--line); background: var(--paper); text-align: left; box-shadow: var(--shadow-soft); transition: transform .25s var(--ease-out), border-color .2s; }
.look-btn:hover { transform: translateY(-2px); border-color: var(--brown); }
.look-thumb { display: block; width: 132px; aspect-ratio: 1.55; border-radius: 8px; background: var(--cream-2); overflow: hidden; box-shadow: inset 0 0 0 1px var(--line); }
.look-thumb img { width: 100%; height: 100%; object-fit: cover; }
.look-thumb img:not([src]) { visibility: hidden; }
.look-btn b { display: block; font-size: 16px; }
.look-btn small { color: var(--muted); }
.look { --look-hint: 26px; position: fixed; inset: 0; z-index: 93; display: grid; place-items: center; padding: calc(var(--top-h) * .4) var(--gutter); }
.look[hidden] { display: none; }
.look-backdrop { position: absolute; inset: 0; background: rgba(42, 35, 32, .78); backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); opacity: 0; transition: opacity .4s; }
.look.is-open .look-backdrop { opacity: 1; }
.look-frame { position: relative; width: min(1180px, 100%); display: grid; gap: 10px; color: var(--cream); opacity: 0; transform: translateY(18px); transition: opacity .45s, transform .6s var(--ease-out); }
.look.is-open .look-frame { opacity: 1; transform: none; }
.look-head { text-align: center; }
.look-head .kicker { color: var(--ochre); }
.look-head h3 { font-size: clamp(20px, 2.4vw, 28px); }
/* the stage keeps a line under it for the hint (dvh: a phone's own toolbar is not counted as room) */
.look-stage { position: relative; width: 100%; aspect-ratio: 16 / 8.6; max-height: min(66vh, calc(100vh - 250px - var(--look-hint))); max-height: min(66dvh, calc(100dvh - 250px - var(--look-hint))); margin: 0 auto var(--look-hint); border-radius: var(--radius); outline: none; }
.look-stage:focus-visible { box-shadow: 0 0 0 3px var(--ochre); }
.look-gl { position: absolute; inset: 0; width: 100%; height: 100%; touch-action: pan-y; cursor: grab; -webkit-user-select: none; user-select: none; }
.look-gl:active { cursor: grabbing; }
.look-fallback { position: absolute; inset: 2%; display: flex; align-items: center; justify-content: center; cursor: pointer; }
.look-fallback[hidden] { display: none; }
.look-fallback img { max-width: 100%; max-height: 100%; border-radius: 4px; box-shadow: 0 30px 60px rgba(0, 0, 0, .4); }
.look-hint { position: absolute; left: 50%; top: 100%; transform: translateX(-50%); margin: 0; padding-top: 3px; font-size: 20px; line-height: 1.15; color: rgba(247, 241, 230, .75); pointer-events: none; transition: opacity .5s; white-space: nowrap; }
.look-hint.is-gone { opacity: 0; }
.look-bar { display: flex; align-items: center; justify-content: center; gap: 18px; }
.look-bar .flip-count { color: rgba(247, 241, 230, .8); }
.look-bar .icon-btn { color: var(--ink); }
.look-bar .icon-btn[aria-disabled="true"] { opacity: .35; cursor: default; border-color: var(--line); transform: none; }   /* at the cover, or the end */
.look-foot { display: flex; align-items: center; justify-content: center; gap: 10px 18px; flex-wrap: wrap; text-align: center; font-size: 15px; color: rgba(247, 241, 230, .82); }
.look-buy { display: flex; gap: 8px; flex-wrap: wrap; justify-content: center; }
.look-foot .btn-ghost-ink { color: var(--cream); border-color: rgba(247, 241, 230, .4); }
.look-foot .btn-ghost-ink:hover { border-color: var(--cream); }
.look-close { position: absolute; right: 16px; top: calc(14px + env(safe-area-inset-top)); background: rgba(255, 253, 248, .92); z-index: 2; }
@media (max-aspect-ratio: 5/4) { .look-stage { aspect-ratio: 4 / 4.6; max-height: min(60vh, calc(100vh - 290px - var(--look-hint))); max-height: min(60dvh, calc(100dvh - 290px - var(--look-hint))); } }
/* A phone on its side, or any short window: stacked, the pages would get a strip 140 px tall. They take the whole
   height instead, with the turn buttons and the buy buttons in a column beside them. */
@media (max-height: 520px) and (min-aspect-ratio: 5/4) {
  .look { --look-hint: 19px; padding: 8px max(12px, env(safe-area-inset-right)) 8px max(12px, env(safe-area-inset-left)); }
  .look-frame { height: 100%; grid-template-columns: minmax(0, 1fr) auto; grid-template-rows: auto 1fr auto auto 1fr; grid-template-areas: "head head" "stage ." "stage bar" "stage foot" "stage ."; gap: 4px 14px; }
  .look-head { grid-area: head; }
  .look-head .kicker { display: none; }
  .look-head h3 { font-size: 17px; }
  .look-stage { grid-area: stage; aspect-ratio: auto; max-height: none; }
  .look-hint { font-size: 15px; line-height: 1.1; padding-top: 2px; }
  .look-bar { grid-area: bar; gap: 10px; margin-bottom: 8px; }
  .look-bar .flip-count { min-width: 0; width: 7.5em; font-size: 13px; line-height: 1.2; }   /* fixed, so a longer label never moves the pages */
  .look-foot { grid-area: foot; }
  .look-foot > p { display: none; }
  .look-buy { flex-direction: column; align-items: stretch; }
  .look-buy .btn { font-size: 13px; padding: 8px 12px; }
  .look-close { right: max(16px, env(safe-area-inset-right)); }
}

/* ---------- picks: share a list, or open one someone sent ---------- */
.picks-share { padding: 14px 20px 4px; border-top: 1px solid var(--line); display: grid; gap: 8px; justify-items: start; }
.picks-share[hidden] { display: none; }
.picks-share p { font-size: 13px; color: var(--muted); }
.picks-link { width: 100%; min-height: 40px; border-radius: 10px; border: 1.5px solid var(--line); padding: 8px 10px; font: inherit; font-size: 13px; background: var(--paper-2); }
.picks-link[hidden] { display: none; }
.shared-head { display: grid; gap: 10px; padding: 4px 0 12px; border-bottom: 1px dashed var(--line); }
.shared-head p { color: var(--muted); }
.pick-open { padding: 0; border: 0; background: none; border-radius: 4px; }

/* ---------- the cast: tap a name to meet them ---------- */
.cast-tag { border: 0; cursor: pointer; pointer-events: auto; min-height: 36px; z-index: 2; box-shadow: 0 6px 14px rgba(42, 35, 32, .25); }
.cast-tag:hover { translate: -50% -8px; }
.cast-tag.is-on { background: var(--ochre); color: var(--night-deep); }
.cast-tag.is-on { translate: -50% -10px; scale: 1.08; }
.cast-tag::after { content: ''; position: absolute; inset: -5px 0; }          /* a 44 px target around the small label */
/* On phones the card is only about 200 px tall, so a 34 px pill at 86% ran 2 to 4 px past its bottom edge and was cut.
   The pill is drawn shorter and sits a little higher; its ::after keeps the 44 px target. */
@media (max-width: 600px) {
  .cast-tag { font-size: 16px; line-height: 1.25; padding: 2px 7px; min-height: 28px; }
  .cast-tag::after { inset: -8px 0; }
  .cast-tag { translate: -50% -6px; }
}
.cast-spot { position: absolute; inset: 0; pointer-events: none; opacity: 0; transition: opacity .5s;
  background: radial-gradient(ellipse 12% 46% at var(--fx, 50%) 50%, transparent 55%, rgba(247, 241, 230, .66) 100%); }
.cast-art.has-spot .cast-spot { opacity: 1; }
.cast-who { margin-top: 16px; padding: 14px 16px; border-radius: 14px; background: var(--paper) var(--grain); box-shadow: var(--shadow-soft); border-left: 4px solid var(--ochre); }
.cast-who[hidden] { display: none; }
.cast-who b { font-family: var(--font-display); font-size: 22px; font-weight: 560; display: block; }
.cast-who p { color: var(--ink) !important; margin-top: 2px; }
.cast-who span { display: block; color: var(--muted); font-size: 14px; margin-top: 6px; }
.cast-hint { font-size: 20px; margin-top: 10px; }
.cast-books button { transition: opacity .3s, border-color .2s, transform .3s var(--ease-out); }
.cast-books button.is-dim { opacity: .38; }
.cast-books button.is-lit { border-color: var(--ochre); transform: translateY(-2px); }

/* ---------- the gift quiz ---------- */
/* The first question's height is held for it until js/quiz.js renders it (as it comes within 700 px), so its arrival
   moves nothing below it (it used to push the gift ideas and everything after them 318 px down on a phone). By the
   answers' columns, which follow the quiz's own width (.quiz-hold is the query container), in whole pixels, never more
   than the question needs. */
.quiz-hold { container-type: inline-size; }
.quiz { margin-top: 34px; min-height: 577px; }
@container (min-width: 490px) { .quiz { min-height: 398px; } }
@container (min-width: 728px) { .quiz { min-height: 323px; } }
@container (min-width: 972px) { .quiz { min-height: 344px; } }
@container (min-width: 1208px) { .quiz { min-height: 301px; } }
.quiz-card { position: relative; padding: clamp(20px, 3vw, 34px); border-radius: var(--radius); background: var(--paper) var(--grain); box-shadow: var(--shadow-soft); }
.quiz-steps { display: flex; gap: 6px; margin-bottom: 16px; }
.quiz-steps span { width: 34px; height: 5px; border-radius: 5px; background: var(--cream-2); transition: background .3s; }
.quiz-steps .is-on { background: var(--ochre); }
.quiz-steps .is-done { background: var(--green); }
.quiz-n { font-family: var(--font-hand); font-size: 22px; font-weight: 600; color: var(--brown); }
.quiz-q { font-size: clamp(24px, 2.6vw, 34px); margin: 2px 0 18px; outline: none; }
.quiz-opts { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(100%, 220px), 1fr)); gap: 10px; }
.quiz-opt { min-height: 72px; padding: 14px 16px; border-radius: 14px; border: 2px solid var(--line); background: var(--paper-2); text-align: left; transition: border-color .2s, transform .25s var(--ease-out), background .2s; }
.quiz-opt:hover { border-color: var(--green); transform: translateY(-2px); background: #fff; }
.quiz-opt b { display: block; font-family: var(--font-display); font-size: 19px; font-weight: 560; line-height: 1.15; }
.quiz-opt span { display: block; color: var(--muted); font-size: 14px; margin-top: 4px; }
.quiz-back { margin-top: 16px; border: 0; background: none; color: var(--muted); font-weight: 700; padding: 8px 0; min-height: 44px; }
.quiz-back:hover { color: var(--ink); }
.quiz-result { display: flex; gap: 18px; overflow-x: auto; padding: 6px 4px 16px; scroll-snap-type: x proximity; scrollbar-width: thin; }
.quiz-actions { display: flex; gap: 10px; flex-wrap: wrap; margin-top: 6px; }
.moments-label { margin-top: 30px; }
.moments-label + .moments { margin-top: 12px; }

/* ---------- the Advent calendar ---------- */
.advent { --sheet: var(--night-deep); color: var(--cream); background: var(--sheet) var(--grain); padding-bottom: 90px; }
.advent .kicker { color: var(--ochre); }
.advent .sub { color: rgba(247, 241, 230, .78); }
.advent-head { display: grid; grid-template-columns: minmax(0, 1.2fr) minmax(0, 1fr); gap: 20px 48px; align-items: end; }
@media (max-width: 900px) { .advent-head { grid-template-columns: 1fr; } }
.advent-card { --cols: 6; --gap: clamp(6px, 1.1vw, 14px); position: relative; margin-top: 34px; padding: clamp(14px, 3.4vw, 46px) clamp(12px, 3vw, 40px); border-radius: 22px 22px var(--radius) var(--radius);
  background: #1F2A4A var(--scene, none) var(--ox, 0) var(--oy, 0) / var(--sw, cover) var(--sh, auto) no-repeat; box-shadow: 0 30px 70px rgba(0, 0, 0, .35); overflow: hidden; }
.advent-doors { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: repeat(var(--cols), 1fr); gap: var(--gap); }
@media (min-width: 1000px) and (min-aspect-ratio: 1/1) { .advent-card { --cols: 8; } }
@media (max-aspect-ratio: 1/1), (max-width: 640px) { .advent-card { --cols: 4; } }
.door { aspect-ratio: 1; perspective: 700px; }
.door button { position: relative; display: block; width: 100%; height: 100%; padding: 0; border: 0; background: none; border-radius: 10px; }
.door-in { position: absolute; inset: 0; border-radius: 10px; background: #12182e; box-shadow: inset 0 4px 12px rgba(0, 0, 0, .55); overflow: hidden; }
.door-art { position: absolute; inset: 7%; border-radius: 6px; background: center / cover no-repeat; }
.door-flap { position: absolute; inset: 0; transform-origin: 0 50%; transform-style: preserve-3d; transition: transform .9s cubic-bezier(.3, 1.3, .5, 1); }
.door-front, .door-back { position: absolute; inset: 0; border-radius: 10px; backface-visibility: hidden; -webkit-backface-visibility: hidden; }
.door-front { background: #2a3560 var(--scene, none) no-repeat; background-size: var(--sw) var(--sh); outline: 1.5px dashed rgba(255, 253, 248, .7); outline-offset: -5px; box-shadow: 0 6px 14px rgba(0, 0, 0, .35), inset 0 0 0 1px rgba(255, 255, 255, .18); }
.door-front b { position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); min-width: 1.9em; height: 1.9em; padding: 0 .3em; display: grid; place-items: center; border-radius: 999px; background: rgba(255, 253, 248, .94); color: var(--ink); font-family: var(--font-display); font-weight: 650; font-size: clamp(15px, 2vw, 24px); box-shadow: 0 3px 10px rgba(0, 0, 0, .3); }
.door-back { transform: rotateY(180deg); background: var(--paper-2) var(--grain); box-shadow: inset 0 0 0 1px var(--line); }
.door.is-open .door-flap { transform: rotateY(-108deg); }
.door.is-open .door-front { box-shadow: none; }
.door button:hover .door-flap { transform: rotateY(-8deg); }
.door.is-open button:hover .door-flap { transform: rotateY(-112deg); }
.door.is-locked button:hover .door-flap { transform: none; }
.door.is-locked .door-front b { background: rgba(255, 253, 248, .7); color: var(--muted); }
/* Today's door glows by opacity alone: its usual shadows and its glow are two pre-made layers that trade places, so
   nothing is painted again while it breathes (it used to animate box-shadow, a repaint every frame). The two shadow
   lists never interpolated (inset against outset), so the old glow switched at a quarter and three quarters of each
   cycle; the layers switch at the same moments (step-end), the same look. */
.door.is-today:not(.is-open) .door-front { outline: 2.5px solid var(--ochre); outline-offset: -4px; box-shadow: none; }
.door.is-today:not(.is-open) .door-front::before, .door.is-today:not(.is-open) .door-front::after { content: ''; position: absolute; inset: 0; border-radius: inherit; pointer-events: none; animation: door-glow 2.4s step-end infinite; }
.door.is-today:not(.is-open) .door-front::before { box-shadow: 0 6px 14px rgba(0, 0, 0, .35), inset 0 0 0 1px rgba(255, 255, 255, .18); animation-name: door-unglow; }
.door.is-today:not(.is-open) .door-front::after { box-shadow: 0 0 0 4px rgba(227, 169, 59, .35), 0 0 28px rgba(227, 169, 59, .55); opacity: 0; }
.door.is-today:not(.is-open) .door-front b { background: var(--ochre); color: var(--night-deep); }
@keyframes door-glow { 0% { opacity: 0; } 25% { opacity: 1; } 75%, 100% { opacity: 0; } }
@keyframes door-unglow { 0% { opacity: 1; } 25% { opacity: 0; } 75%, 100% { opacity: 1; } }
.door button:focus-visible { outline: 3px solid var(--ochre); outline-offset: 3px; }
.advent-note { margin-top: 22px; min-height: 120px; }
.advent-idle { display: grid; gap: 6px; }
.advent-idle .hand { font-size: 27px; color: var(--cream); }
.advent-idle p:not(.hand) { color: rgba(247, 241, 230, .8); }
.advent .link-btn { color: var(--ochre); }
.advent-pick { display: grid; grid-template-columns: 140px 1fr; gap: 20px; align-items: center; padding: 16px; border-radius: var(--radius); background: rgba(255, 255, 255, .06); border: 1px solid rgba(255, 255, 255, .12); }
.advent-thumb { display: block; width: 140px; aspect-ratio: 1; border-radius: 12px; background: #fff center / cover no-repeat; box-shadow: 0 10px 24px rgba(0, 0, 0, .35); }
.advent-pick h3 { margin: 2px 0 6px; }
.advent-pick p:not(.kicker) { color: rgba(247, 241, 230, .82); max-width: 60ch; }
.advent-pick .tool-row { margin-top: 12px; }
.advent .btn-ghost-ink { color: var(--cream); border-color: rgba(247, 241, 230, .35); }
.advent .btn-ghost-ink:hover { border-color: var(--cream); }
.advent-pick > div { min-width: 0; }
@media (max-width: 560px) { .advent-pick { grid-template-columns: 72px 1fr; gap: 14px; align-items: start; } .advent-thumb { width: 72px; } .advent-pick .btn { white-space: normal; text-align: left; } }

/* ---------- find the words ---------- */
.ws-wrap { display: grid; gap: 14px; }
.ws-page { padding: clamp(16px, 3vw, 32px); border-radius: 12px; background: #fff; box-shadow: var(--shadow); }
.ws-head { text-align: center; margin-bottom: 14px; }
.ws-kicker { font-size: 12px; letter-spacing: .2em; text-transform: uppercase; color: var(--muted); font-weight: 700; }
.ws-head h3 { margin-top: 4px; font-weight: 640; font-variation-settings: 'opsz' 60, 'SOFT' 0; }
.ws-ask { font-family: var(--font-display); font-style: italic; color: var(--ink-soft); margin-top: 6px; font-size: clamp(15px, 1.6vw, 19px); }
.ws-ask[hidden] { display: none; }
.ws-board { position: relative; width: min(100%, 560px); margin: 0 auto; border: 2px solid var(--ink); border-radius: 18px; padding: 2.5%; }
.ws-board.is-big { width: min(100%, 620px); }
.ws-grid { position: relative; display: grid; grid-template-columns: repeat(var(--n), 1fr); aspect-ratio: 1; touch-action: none; -webkit-user-select: none; user-select: none; outline: none; z-index: 1; cursor: pointer; container-type: inline-size; }
.ws-grid span { display: grid; place-items: center; font-family: var(--font-body); font-weight: 700; font-size: calc(100cqi / var(--n) * .52); line-height: 1; border-radius: 50%; color: var(--ink); transition: background .2s, color .2s; }
.ws-grid span.is-anchor { background: rgba(227, 169, 59, .55); }
.ws-grid span.is-cursor { box-shadow: inset 0 0 0 2px var(--ochre); }
/* a hint pulses three times: the letter grows (transform) over a pre-made glow whose opacity alone changes (it used
   to animate the background colour, a repaint every frame; the same look, as a fade of that colour is an alpha fade) */
.ws-grid span.is-hint { position: relative; z-index: 0; animation: ws-hint .8s ease-in-out 3; }
.ws-grid span.is-hint::before { content: ''; position: absolute; inset: 0; z-index: -1; border-radius: 50%; background: rgba(227, 169, 59, .7); opacity: 0; animation: ws-hint-glow .8s ease-in-out 3; }
.ws-grid span.is-left { color: var(--brick); }
@keyframes ws-hint { 50% { transform: scale(1.18); } }
@keyframes ws-hint-glow { 50% { opacity: 1; } }
.ws-marks { position: absolute; inset: 2.5%; width: 95%; height: 95%; pointer-events: none; overflow: visible; }
.ws-marks line { stroke-width: .74; stroke-linecap: round; }
.ws-list { list-style: none; padding: 0; margin: 18px auto 0; max-width: 560px; display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 6px 14px; font-size: 16px; }
.ws-list li { display: flex; align-items: center; gap: 8px; transition: color .3s; }
.ws-o { flex: none; width: 15px; height: 15px; border-radius: 50%; border: 2px solid var(--ink); transition: background .3s; }
.ws-list li.is-found { color: var(--muted); text-decoration: line-through; text-decoration-color: var(--brick); }
.ws-list li.is-found .ws-o { background: var(--green); border-color: var(--green); }
@media (max-width: 520px) { .ws-list { grid-template-columns: repeat(2, minmax(0, 1fr)); font-size: 15px; } }
.ws-clue { margin: 18px auto 0; max-width: 620px; text-align: center; }
.ws-clue[hidden] { display: none; }
.ws-clue > p:first-child { font-size: 14px; color: var(--muted); }
.ws-boxes { display: flex; flex-wrap: wrap; justify-content: center; gap: 4px; margin-top: 8px; }
.ws-boxes span { width: clamp(18px, 4.4vw, 26px); aspect-ratio: .82; border: 2px solid var(--ink); border-radius: 5px; display: grid; place-items: center; font-weight: 700; font-size: clamp(12px, 2.6vw, 16px); color: var(--brick); }
.ws-solved { margin-top: 12px; }
.ws-solved[hidden] { display: none; }
.ws-solved b { font-family: var(--font-display); font-size: clamp(19px, 2.2vw, 26px); font-weight: 560; color: var(--ink); }

/* ---------- draw along ---------- */
.draw-wrap { display: grid; gap: 16px; min-width: 0; }
.ws-wrap, .wb-wrap, .draw-step, .draw-pad { min-width: 0; }
.draw-pick { display: flex; gap: 10px; flex-wrap: wrap; }
.draw-pick button { display: grid; justify-items: center; gap: 2px; padding: 6px 10px 6px; border-radius: 14px; min-width: 76px; }
.draw-pick button canvas { width: 54px; height: 54px; border-radius: 8px; background: #fff; }
.draw-pick button span { font-size: 13px; }
.draw-pick button:hover { border-color: var(--ink); }
.draw-pick button[aria-pressed="true"] { border-color: var(--green); box-shadow: 0 0 0 3px rgba(63, 107, 85, .2); }
.draw-grid { display: grid; grid-template-columns: minmax(0, .8fr) minmax(0, 1.2fr); gap: 18px; align-items: start; }
@media (max-width: 700px) { .draw-grid { grid-template-columns: minmax(0, 1fr); } .draw-step { grid-template-columns: 112px minmax(0, 1fr); align-items: center; } .draw-step-bar { grid-column: 1 / -1; } }
.draw-step { margin: 0; display: grid; gap: 10px; padding: 14px; border-radius: 14px; background: #fff; box-shadow: var(--shadow-soft); }
.draw-step canvas { width: 100%; height: auto; aspect-ratio: 1; border: 2px solid var(--ink); border-radius: 12px; }
.draw-step figcaption { display: grid; gap: 2px; font-size: 16px; min-height: 4.4em; }
.draw-n { font-weight: 700; color: var(--brown); font-size: 13px; letter-spacing: .08em; text-transform: uppercase; }
.draw-step-bar { display: flex; align-items: center; justify-content: space-between; gap: 6px; }
.draw-dots { display: flex; gap: 0; justify-content: center; min-width: 0; }
.draw-dots button { width: 19px; height: 44px; flex: none; border: 0; padding: 0; background: none; position: relative; }
.draw-dots button::after { content: ''; position: absolute; left: 50%; top: 50%; width: 10px; height: 10px; transform: translate(-50%, -50%); border-radius: 50%; background: var(--cream-2); box-shadow: inset 0 0 0 1.5px var(--line); transition: background .2s, transform .2s; }
.draw-dots button.is-on::after { background: var(--ink); transform: translate(-50%, -50%) scale(1.25); }
.draw-pad { display: grid; gap: 8px; }
.draw-turn { font-size: 26px; color: var(--ink); margin-bottom: -4px; }
.draw-paper { position: relative; aspect-ratio: 4 / 3; border-radius: 14px; background: #fff radial-gradient(circle, rgba(42, 35, 32, .2) 1.1px, transparent 1.4px) 0 0 / 18px 18px; border: 2px dashed rgba(42, 35, 32, .45); box-shadow: var(--shadow); overflow: hidden; }
.draw-paper canvas { position: absolute; inset: 0; width: 100%; height: 100%; }
.draw-guide { opacity: .2; pointer-events: none; }
.draw-ink { touch-action: none; cursor: crosshair; }
.draw-tools { display: flex; align-items: center; justify-content: space-between; gap: 10px; flex-wrap: wrap; margin-top: 14px; }
.pencils { display: flex; gap: 6px; align-items: flex-end; flex-wrap: wrap; }
.pencil { position: relative; width: 26px; height: 48px; border: 0; padding: 0; border-radius: 4px 4px 3px 3px; background: var(--c); box-shadow: inset -4px 0 0 rgba(0, 0, 0, .14), 0 4px 8px rgba(60, 40, 20, .2); transition: transform .25s var(--ease-out); }
.pencil::before { content: ''; position: absolute; left: 0; right: 0; top: -12px; height: 12px; background: linear-gradient(to bottom, var(--c) 0 35%, #e8cfa6 35%); clip-path: polygon(50% 0, 100% 100%, 0 100%); }
.pencil.is-on { transform: translateY(-10px); }
.pencil.eraser { --c: #f2b8c0; background: linear-gradient(#f2b8c0 0 60%, #c9c9c9 60%); }
.pencil.eraser::before { display: none; }
.trace-toggle { display: inline-flex; align-items: center; gap: 6px; min-height: 44px; padding: 0 12px; border-radius: 999px; border: 2px solid var(--line); background: var(--paper); font-weight: 700; font-size: 14px; cursor: pointer; }
.trace-toggle input { width: 18px; height: 18px; accent-color: var(--green); }

/* ---------- write back ---------- */
.wb-wrap { display: grid; gap: 14px; }
.wb-book { --single: 0; --badge: #A8414A; --tag: #A8414A; --page: #FBF4E8; --rule: #d9cdb8; display: grid; grid-template-columns: 1fr 1fr; padding: 14px; border-radius: 16px; background: #e9dcc6; box-shadow: var(--shadow); position: relative; }
.wb-book::after { content: ''; position: absolute; left: 50%; top: 14px; bottom: 14px; width: 26px; transform: translateX(-50%); background: linear-gradient(90deg, transparent, rgba(60, 40, 20, .18) 45%, rgba(60, 40, 20, .28) 50%, rgba(60, 40, 20, .18) 55%, transparent); pointer-events: none; }
.wb-book[data-theme="switchback"] { --badge: #2E5A5C; --page: #F5EBDC; --rule: #cfd6d3; background: #e2d6c2; }
.wb-page { display: grid; align-content: start; gap: 6px; padding: clamp(16px, 2.4vw, 28px); background: var(--page); min-height: 420px; transition: opacity .3s, box-shadow .3s; }
.wb-page:first-child { border-radius: 8px 2px 2px 8px; }
.wb-page:last-child { border-radius: 2px 8px 8px 2px; }
.wb-page:not(.is-active) { opacity: .62; }
.wb-page.is-active { box-shadow: inset 0 0 0 2px rgba(227, 169, 59, .7); }
.wb-badge { justify-self: start; padding: 4px 12px; border-radius: 999px; background: var(--badge); color: #fff; font-size: 11px; font-weight: 700; letter-spacing: .14em; text-transform: uppercase; }
.wb-section { font-size: 11px; letter-spacing: .16em; text-transform: uppercase; color: var(--muted); margin-top: 6px; }
.wb-q { margin: 0; font-family: var(--font-display); font-weight: 480; font-size: clamp(17px, 1.6vw, 20px); line-height: 1.3; font-variation-settings: 'opsz' 24, 'SOFT' 30; }
.wb-lines { --lh: 30px; width: 100%; min-height: calc(var(--lh) * 8); margin-top: 10px; border: 0; padding: 0 2px; resize: vertical; background: repeating-linear-gradient(transparent 0 calc(var(--lh) - 1px), var(--rule) calc(var(--lh) - 1px) var(--lh)) local; font-family: var(--font-hand); font-size: 23px; line-height: var(--lh); color: #2b3a67; outline: none; }
.wb-lines:focus { background-color: rgba(255, 255, 255, .45); }
.wb-foot { font-size: 10px; letter-spacing: .16em; text-transform: uppercase; color: var(--muted); margin-top: auto; padding-top: 8px; }
.wb-note { font-size: 13px; color: var(--muted); }
.wb-rule { margin: 12px 0 0; padding: 10px 14px; border-left: 4px solid var(--ochre); background: var(--paper); border-radius: 0 10px 10px 0; font-family: var(--font-display); font-style: italic; color: var(--ink); }
@media (max-width: 640px) {
  .wb-book { --single: 1; grid-template-columns: 1fr; padding: 10px; }
  .wb-book::after { display: none; }
  .wb-page { border-radius: 8px !important; min-height: 380px; }
  .wb-page:not(.is-active) { display: none; }
  .wb-page.is-active { box-shadow: none; }
}

/* calm motion: no swinging doors or rising options, and today's door holds its glow still */
html[data-motion="calm"] :is(.door-flap, .look-frame, .look-backdrop, .quiz-opt, .cast-spot, .cast-books button) { transition: none !important; }
html[data-motion="calm"] .door.is-today .door-front { animation: none; box-shadow: 0 0 0 4px rgba(227, 169, 59, .35); }
html[data-motion="calm"] .door.is-today .door-front::before, html[data-motion="calm"] .door.is-today .door-front::after { animation: none; opacity: 0; }
html[data-motion="calm"] .ws-grid span.is-hint { animation: none; }
html[data-motion="calm"] .ws-grid span.is-hint::before { animation: none; opacity: 1; }

/* ======================================================================================================== Oct 29 2026
   Hero modes (js/gl/heroes/, DESIGN.md Hero modes): a light room (Morning, Afternoon) gives the hero dark ink. Set by
   app.js only when the address asks for such a mode (html[data-hero-tone="light"]); without it nothing here applies.
   The ochre and the mist of the headline are deepened to read on cream (large type: over 3:1 on the brightest wall). */
html[data-hero-tone="light"] .stage { background: #EFE3CB; }
html[data-hero-tone="light"] .poster-sky { background: radial-gradient(110% 90% at 72% 38%, #FBF2E2 0%, #EFE2C9 58%, #E4D0AE 100%); }
html[data-hero-tone="light"] .topbar:not(.is-day):not(.is-solid):not(.menu-open) { color: var(--ink); }
html[data-hero-tone="light"] .topbar:not(.is-day):not(.is-solid):not(.menu-open) .brand em { color: var(--brown); }
html[data-hero-tone="light"] .topbar:not(.is-day):not(.is-solid):not(.menu-open) .brand-mark { box-shadow: 0 0 0 2px rgba(201, 142, 33, .9), 0 4px 12px rgba(60, 40, 20, .18); }
html[data-hero-tone="light"] .hero-copy { color: var(--ink); --hero-veil: 250, 241, 224; --hero-veil-a: .5; }
html[data-hero="afternoon"] .hero-copy { --hero-veil: 255, 238, 214; --hero-veil-a: .66; }
/* a soft light behind the copy column (it reads as the window's light), so the ink holds its contrast over the
   sunlit wall, the pane shadows and the desk alike; it fades and lifts away with the copy */
html[data-hero-tone="light"] .hero-copy::before { content: ''; position: absolute; z-index: -1; inset: -16% -24% -24% -30%; pointer-events: none;
  background: radial-gradient(closest-side at 46% 60%, rgba(var(--hero-veil), var(--hero-veil-a)), rgba(var(--hero-veil), calc(var(--hero-veil-a) * .8)) 68%, rgba(var(--hero-veil), 0)); }
/* portrait screens set the copy over the desk at the bottom: the light rises from the foot of the screen instead */
@media (max-aspect-ratio: 41/50) {
  html[data-hero-tone="light"] .hero-copy { --hero-veil-a: .6; }
  html[data-hero="afternoon"] .hero-copy { --hero-veil-a: .74; }
  html[data-hero-tone="light"] .hero-copy::before { inset: -14% calc(-1 * var(--gutter)) min(-22px, -4svh) calc(-1 * var(--gutter));
    background: linear-gradient(to bottom, rgba(var(--hero-veil), 0), rgba(var(--hero-veil), calc(var(--hero-veil-a) * .85)) 20%, rgba(var(--hero-veil), var(--hero-veil-a)) 45%); }
}
html[data-hero-tone="light"] .hero-copy .eyebrow { color: var(--ink); text-shadow: 0 0 10px rgba(255, 246, 232, .7); }
html[data-hero-tone="light"] .hero-copy h1 { color: var(--ink); }
html[data-hero-tone="light"] .hero-copy h1 .w-colour { color: #7E4A0A; }
html[data-hero-tone="light"] .hero-copy h1 .w-solve { color: #1F5551; }
/* (the lede and the hint can sit over the desk's wood, so they carry a soft paper halo) */
html[data-hero-tone="light"] .hero-copy .lede { color: var(--ink); text-shadow: 0 0 2px rgba(255, 249, 238, .9), 0 0 10px rgba(255, 246, 232, .85), 0 0 22px rgba(255, 244, 228, .6); }
html[data-hero-tone="light"] .hero-hint { color: var(--ink); text-shadow: 0 0 2px rgba(255, 249, 238, .9), 0 0 10px rgba(255, 246, 232, .85), 0 0 20px rgba(255, 244, 228, .6); }
html[data-hero-tone="light"] .btn-ghost { color: var(--ink); border-color: rgba(42, 35, 32, .5); background: rgba(255, 253, 248, .3); }
html[data-hero-tone="light"] .btn-ghost:hover { border-color: var(--ink); background: rgba(255, 253, 248, .6); }
html[data-hero-tone="light"] .scroll-cue { border-color: rgba(42, 35, 32, .38); }
html[data-hero-tone="light"] .rail li { color: var(--ink); }
html[data-hero-tone="light"] .caption { color: var(--ink); text-shadow: 0 0 3px rgba(255, 250, 240, .85), 0 0 18px rgba(255, 248, 236, .8); }
html[data-hero-tone="light"] .caption .hand, html[data-hero-tone="light"] .caption .cap-soon { color: #7E4A0A; }
html[data-hero-tone="light"] .caption p:last-child { color: rgba(42, 35, 32, .92); }

/* Paper → table at night (js/gl/heroes/paper-table.js, Oct 30 2026): the copy keeps the night's light ink over a
   full-screen paper scene, so it gets the same soft veil as a light room's, in the night's dark blue (the world's own
   mist is the other half); its lede and hint carry a dark halo. Only with the scene drawn (not over the still). */
html[data-hero="paper-table"][data-hero-light="night"]:not(.no-gl) .hero-copy { --hero-veil: 9, 12, 26; --hero-veil-a: .46; }
html[data-hero="paper-table"][data-hero-light="night"]:not(.no-gl) .hero-copy::before { content: ''; position: absolute; z-index: -1; inset: -16% -24% -24% -30%; pointer-events: none;
  background: radial-gradient(closest-side at 46% 60%, rgba(var(--hero-veil), var(--hero-veil-a)), rgba(var(--hero-veil), calc(var(--hero-veil-a) * .8)) 68%, rgba(var(--hero-veil), 0)); }
html[data-hero="paper-table"][data-hero-light="night"]:not(.no-gl) .hero-copy .lede,
html[data-hero="paper-table"][data-hero-light="night"]:not(.no-gl) .hero-hint { text-shadow: 0 0 2px rgba(9, 12, 26, .9), 0 0 10px rgba(9, 12, 26, .8), 0 0 22px rgba(9, 12, 26, .55); }
html[data-hero="paper-table"][data-hero-light="night"]:not(.no-gl) .hero-copy .eyebrow { text-shadow: 0 0 10px rgba(9, 12, 26, .8); }
@media (max-aspect-ratio: 41/50) {
  html[data-hero="paper-table"][data-hero-light="night"]:not(.no-gl) .hero-copy { --hero-veil-a: .62; }
  html[data-hero="paper-table"][data-hero-light="night"]:not(.no-gl) .hero-copy::before { inset: -14% calc(-1 * var(--gutter)) min(-22px, -4svh) calc(-1 * var(--gutter));
    background: linear-gradient(to bottom, rgba(var(--hero-veil), 0), rgba(var(--hero-veil), calc(var(--hero-veil-a) * .85)) 20%, rgba(var(--hero-veil), var(--hero-veil-a)) 45%); }
}
/* its captions sit over the busy pop-up and the props at the lean and while the pages turn: a soft veil of their own,
   paper-light by day, the night's dark blue at night */
html[data-hero="paper-table"]:not(.no-gl) .caption { --cap-veil: 250, 243, 230; --cap-veil-a: .93; }
html[data-hero="paper-table"][data-hero-light="night"]:not(.no-gl) .caption { --cap-veil: 9, 12, 26; --cap-veil-a: .72; }
html[data-hero="paper-table"]:not(.no-gl) .caption::before { content: ''; position: absolute; z-index: -1; inset: -40% -12% -46%; pointer-events: none;
  background: radial-gradient(closest-side, rgba(var(--cap-veil), var(--cap-veil-a)), rgba(var(--cap-veil), calc(var(--cap-veil-a) * .8)) 62%, rgba(var(--cap-veil), 0)); }
