body {
  padding: 2.5rem 1.5rem 5rem;
  line-height: 1.6;
}
/* The Punchcutter's Case (#228), page members: the shell's h1/.topnav/footer
   display and .tagline flourish bindings live in BaseLayout (#263).
   figcaption here is the world's NAME shown large (not an italic caption), so it
   takes the display face like a title. */
.hunt h2, figcaption {
  font-family: var(--font-display, 'Iowan Old Style', 'Palatino', Georgia, serif);
}
.survey, .status {
  font-family: var(--font-flourish, 'Iowan Old Style', 'Palatino', Georgia, serif);
}
main { max-width: 1080px; }
header { margin-bottom: 1.4rem; }
.dateline { text-align: center; letter-spacing: 0.18em; font-size: 0.82rem;
  text-transform: uppercase; color: var(--ink-faded); margin: 0.4rem 0 1.4rem; }
.status { text-align: center; font-style: italic; color: var(--ink-brown); min-height: 1.4rem; }
figure { margin: 0; }
/* #167 The Surveyor's Glass, Sub 6: geometric pan/zoom on today's chart. #map-viewport
   is the clip/gesture box the shared controller binds to; #map is the transform target.
   overflow:hidden clips ONLY while zoomed (.zoomed, toggled by the controller): at home
   (k=1) the arrival ceremony's translate/rotate and the chart's drop shadow must overflow
   the frame exactly as today, so the idle page stays byte-identical. touch-action:none
   (added via .zoomable by the controller) is REQUIRED for touch drag/pinch; d3-zoom does
   not set it, and without it the browser's native pan/pinch preempts the gesture. */
#map-viewport { position: relative; }
#map-viewport.zoomed { overflow: hidden; }
#map-viewport.zoomable { touch-action: none; }
/* transform-origin 0 0 pivots the CSS scale at the top-left, matching d3-zoom's
   screen-space math (point*k + translate) so gestures land true and the star + soundings
   stay pixel-aligned with their marks. */
#map { position: relative; transform-origin: 0 0; }
#map svg { width: 100%; height: auto; display: block;
  border: 1px solid var(--line-tan); box-shadow: 0 12px 34px rgb(61 47 31 / 0.2); }
#map:not(.solved) svg { cursor: crosshair; }
/* The win star rests still; on a LIVE solve app.js adds .stamp and it stamps in.
   On a solved-day reload the star is placed WITHOUT .stamp, so it sits perfectly
   still (#129; the old code replayed this animation on every reload). The centering
   offset (--pos, #126) is defined once and the entry keyframe layers scale + rotate
   onto it; collapses under the shared prefers-reduced-motion rule in /motion.css. */
.hunt-star { position: absolute; --pos: translate(-50%, -50%); transform: var(--pos);
  color: var(--iron-red); font-size: 1.7rem; line-height: 1; pointer-events: none;
  text-shadow: 0 0 4px var(--parchment-bright), 0 1px 2px rgb(61 47 31 / 0.5); }
.hunt-star.stamp { animation: huntStarIn var(--paper-settle) var(--ease-paper) both; }
@keyframes huntStarIn {
  from { opacity: 0; transform: var(--pos) scale(0.2) rotate(-40deg); }
  to { opacity: 1; transform: var(--pos) scale(1) rotate(0); }
}

/* ==========================================================================
   #129 Today's ceremonies (epic #125, Paper & Ink). Consumes the /motion.css
   vocabulary. This page renders on the main thread, so each ceremony starts
   after the single paint. Styles the LIVE DOM only: this page has no Download
   and no golden, so there are no chart bytes to protect.
   ========================================================================== */

/* The arrival, extended from #127: the chart settles onto the desk while the
   coastline draws itself in ink and the land wash + waterlines dry in behind.
   app.js sets --draw-len + stroke-dasharray on the coast and clears them on
   animationend, restoring the pristine stroke. Plays on every load (the daily
   reveal), including a solved-day reload. */
#map svg.arriving { animation: paperSettle var(--paper-settle) var(--ease-paper) both; }
#map svg.arriving #layer-land path {
  animation: inkDraw var(--ink-draw) var(--ease-paper) both,
             washDry var(--ink-dry) var(--ease-paper) 400ms both;
}
#map svg.arriving #layer-waterlines {
  animation: dryingInk var(--ink-dry) var(--ease-paper) 300ms both;
}

/* The caption, survey line, and blurb dry in after the chart, lightly staggered
   (--dry-delay set per element in app.js). */
.drying { animation: dryingInk var(--ink-dry) var(--ease-paper) var(--dry-delay, 0ms) both; }

/* The clue slips stagger in, 60ms apart (--i per item set in app.js). */
.clues li { animation: dryingInk var(--paper) var(--ease-paper) both;
  animation-delay: calc(var(--i, 0) * 60ms); }

/* A miss leaves a sounding over the map: a spreading ink ring (the shared
   inkRipple) and a surveyor's graphite dot that lingers, then fades. Both are
   pointer-transparent so a lingering dot never eats the next click, and both
   self-remove on animationend. Centered by margin (not transform) so the ripple's
   own scale keeps the transform to itself. The SVG is never touched. */
.sounding-ring, .sounding-dot { position: absolute; pointer-events: none; }
.sounding-ring { width: 44px; height: 44px; margin: -22px 0 0 -22px;
  border-radius: 50%; border: 2px solid var(--iron-red);
  animation: inkRipple 700ms var(--ease-paper) both; }
.sounding-dot { width: 7px; height: 7px; margin: -3.5px 0 0 -3.5px;
  border-radius: 50%; background: var(--ink-brown);
  box-shadow: 0 0 0 1px rgb(247 247 228 / 0.55);
  animation: soundingFade 2600ms var(--ease-paper) both; }
@keyframes soundingFade {
  0% { opacity: 0; } 8% { opacity: 0.9; } 68% { opacity: 0.9; } 100% { opacity: 0; }
}

/* The warmer/colder line keeps its instant aria-live swap; only its rendered ink
   dries in with a brief blur, so a sighted player sees the update land. */
.hunt-status.wet { animation: inkWet var(--paper) var(--ease-paper) both; }
@keyframes inkWet {
  from { filter: blur(2.5px); opacity: 0.45; }
  to   { filter: blur(0); opacity: 1; }
}

/* The reveal unrolls open on a live solve; on a solved-day reload it is simply
   shown, still (app.js adds .unfurl only from a live solve). It carries no
   positioning transform, so the unfurl (self-perspective in the keyframe) rides
   it directly, like the Explorer's scrubber panel (#128). */
.reveal.unfurl { transform-origin: top center;
  animation: paperUnfurl var(--unfurl) var(--ease-unfurl) both; }

/* The Share button rises in on a live solve. No fill, so it reverts to normal
   afterward and the shared button :hover lift stays free (the #146 forwards-fill
   gotcha). */
#share.rise { animation: shareRise var(--paper-settle) var(--ease-paper); }
@keyframes shareRise {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* The streak stamps in when it increments on a live solve. */
.streak.stamp { animation: inkStamp var(--paper) var(--ease-paper) both; }

/* The mobile sticky bar slides up the first time it appears (app.js adds .rise on
   the hidden -> shown transition only, never on subsequent misses). aria-hidden
   stays true: this bar mirrors #hunt-status, which is already an aria-live region,
   so re-announcing it would duplicate screen-reader content. */
.hunt-sticky.rise { animation: stickyRise var(--paper-settle) var(--ease-paper); }
@keyframes stickyRise {
  from { transform: translateY(100%); }
  to   { transform: translateY(0); }
}
figcaption { text-align: center; font-size: 1.3rem; letter-spacing: 0.04em;
  margin-top: 1rem; }
.survey { text-align: center; font-style: italic; color: var(--ink-brown); margin: 0.3rem 0 0; }
.blurb { max-width: 44rem; margin: 1.2rem auto 0; }
.actions { text-align: center; margin-top: 1.6rem; }
.actions a { display: inline-block; border: 1.5px solid var(--ink-dark); border-radius: 4px;
  padding: 0.5rem 1rem; background: var(--parchment-panel); text-decoration: none; color: inherit;
  letter-spacing: 0.04em; }
.actions a:hover { background: var(--parchment-bright); }
/* #88: keep the map and the hunt visible together. On wide screens they sit
   side by side, so the warmer/colder line lives in the right column, always in
   view; the panel is sticky so it trails a taller map as you scroll. On narrow
   screens they stack and a fixed bar (.hunt-sticky) mirrors the latest feedback
   so you never scroll away from the map to read it. */
.stage { display: flex; flex-direction: column; gap: 1.5rem; }
.stage > figure { margin: 0; }
@media (min-width: 760px) {
  .stage { flex-direction: row; align-items: flex-start; }
  .stage > figure { flex: 1.6 1 0; min-width: 0; }
  .stage > .hunt { flex: 1 1 0; margin: 0; max-width: none; position: sticky; top: 1rem; }
}
.hunt-sticky { display: none; }
@media (max-width: 759px) {
  .hunt-sticky.active {
    display: block; position: fixed; left: 0; right: 0; bottom: 0; z-index: 20;
    background: var(--ink-dark); color: var(--parchment-panel); text-align: center;
    padding: 0.7rem 1rem; font-weight: 700; letter-spacing: 0.03em;
    box-shadow: 0 -4px 16px rgb(61 47 31 / 0.32);
  }
}
.hunt { max-width: 44rem; margin: 2rem auto 0; padding: 1.4rem 1.6rem;
  border: 1px solid var(--line-tan); border-radius: 6px; background: var(--parchment-panel); }
.hunt h2 { text-align: center; font-size: 1.2rem; letter-spacing: 0.06em;
  margin: 0 0 0.4rem; }
.hunt-intro { text-align: center; font-size: 0.92rem; color: var(--ink-brown);
  margin: 0 0 1rem; }
.clues { margin: 0 auto; padding-left: 1.4rem; max-width: 36rem;
  font-style: italic; color: var(--ink-dark); }
.clues li { margin: 0.3rem 0; }
.hunt-status { text-align: center; min-height: 1.4rem; font-weight: 700;
  letter-spacing: 0.03em; color: var(--iron-red); margin: 1rem 0 0; }
.reveal { margin-top: 1rem; padding: 1rem 1.2rem; border-radius: 4px;
  background: var(--parchment-bright); border: 1px solid #d8c79c; }
.reveal strong { display: block; font-size: 1.1rem; margin-bottom: 0.4rem; }
.reveal p { margin: 0; font-style: italic; color: var(--ink-dark); }
.hunt-actions { text-align: center; margin-top: 1.2rem; display: flex;
  gap: 1rem; align-items: center; justify-content: center; flex-wrap: wrap; }
#share, #dispatch { border: 1.5px solid var(--ink-dark); border-radius: 4px; padding: 0.5rem 1rem;
  background: var(--parchment-panel); color: inherit; letter-spacing: 0.04em; cursor: pointer;
  font: inherit; }
#share:hover, #dispatch:hover { background: var(--parchment-bright); }
.streak { font-size: 0.9rem; color: var(--ink-brown); }
.note { text-align: center; color: var(--ink-faded); font-size: 0.85rem; margin-top: 1rem; }
footer { margin-top: 4rem; }
