/* Next Stops — the page shell (rebuild Step 3).
 *
 * Masthead, sticky merged chip bar, the three chapter sections, the shore
 * closing scene and this page's footer treatment. Replaces the retired
 * "satellite-to-sands" lower journal (next-stops-lower.css/.js), which no
 * longer loads on this template.
 *
 * FILE BOUNDARIES — deliberate, do not blur them:
 *   next-stops.css        owns the wax-seal live-dispatch banner and the
 *                         tracking chip's state machine. Shipped + QA'd on
 *                         production 2026-08-01; treated as read-only here.
 *   next-stops-chart.css  owns the chart. Verified by 23 browser checks in
 *                         Step 2; it takes the two ground-colour edits this
 *                         step needs and nothing else.
 *   this file             owns everything else on the page.
 *
 * Every colour, gradient stop and size below is measured from the approved
 * "Next Stops · Chart Table v2" export. Two deliberate departures:
 *   1. The export's Oswald 800/900 display weights are normalised to 700.
 *      The theme loads 500/600/700 only, so 800/900 would be browser-
 *      synthesised — a faked weight, not a real cut.
 *   2. None of the export's global utility class names are adopted (its layout
 *      helpers fx / col / fs14 / mt8 / round, its cw- prefixed components, and
 *      its scp0-2 scene layers — written here without their leading dots so a
 *      grep for a banned selector finds nothing but the real thing). They are
 *      generic enough to collide with the theme's shared styles.css, so every
 *      one has an ns- equivalent here, scoped under #ns-root.
 *
 * STEP 3b added the export's four illustrated scenic layers — sun, stars,
 * satellite, moon, drifting fish, sailboat, gulls, wave strokes — at the bottom
 * of this file, plus the chart stage's aspect ratio and the chapter-collapse
 * rules the era filter needs. The sections still read as complete with the
 * decoration switched off; none of it is load-bearing.
 */

/* ------------------------------------------------------------------
   PAGE GROUND
   next-stops.css opens with a dark #ns-root (cream text on #e8dcc4) that the
   old full-bleed map page needed. The shell is a light paper page, so the
   root's ground and default ink are re-set here. Same specificity as the rule
   in next-stops.css — this file is enqueued after it, so it wins on order.
   ------------------------------------------------------------------ */
#ns-root {
  background-color: #EDE3D2;
  background-image: radial-gradient(#DCCBB2 1px, transparent 1px);
  background-size: 26px 26px;
  color: #3A2E22;
  font-family: 'Nunito Sans', system-ui, sans-serif;
}
#ns-root a { color: #B8431F; text-decoration: none; }

/* ------------------------------------------------------------------
   CHART STAGE PROPORTION (Step 3b, correction 1)
   next-stops.css:13 sizes the stage as calc(100dvh - 78px) — a leftover from
   when this page WAS the full-bleed map. At 1440x900 that resolves to ~822px,
   which reads as a full-viewport panel dropped into the middle of the page
   rather than as a chart table set into it.

   The export draws the chart as a 1240x540 band — 2.296:1 — so that is what
   the ratio is derived from. Width stays full-bleed (the export's own layout
   is 1240 wide, so at 1440 the rendered ratio lands on 2.296 either way, and
   constraining the width would move the info card and the ground colour that
   Step 2's 23 checks measured).

   CLAMPS. min 460px: below that the Mercator fit has too little vertical room
   for the Atlantic-to-Pacific spread to stay legible. max 660px: uncapped, a
   1920 viewport would give 836px and a 2560 ultrawide 1115px, both of which
   put the page right back where this correction started. 660 holds the band at
   ~61% of a 1080-tall screen, so masthead above and Chapter 01 below are both
   in frame.

   The projection re-fits to whatever box it is given (fitExtent in
   next-stops-chart.js), and a ResizeObserver re-runs it, so nothing here can
   clip a route: fitExtent is a fit, not a crop. The dataset spans ~240deg of
   longitude against ~45deg of latitude, so a WIDER box actually fills better
   than the 1.75:1 it had.

   This file is enqueued after next-stops.css at equal specificity, so it wins
   on order — the same override route used for the scrim above. next-stops.css
   is treated as read-only here.
   ------------------------------------------------------------------ */
#ns-root .ns-stage {
  height: auto;
  aspect-ratio: 1240 / 540;
  min-height: 460px;
  max-height: 660px;
  /* Air between the sticky chip bar and the chart's top edge, so the bar reads
     as sitting above the table rather than resting on it. */
  margin-top: clamp(18px, 2vw, 28px);
}

/* ------------------------------------------------------------------
   MASTHEAD
   The heading block and the three stats share one row at desktop width; the
   stats sit right-aligned against the lede. Ruled-paper overlay lives here
   rather than on #ns-root: this is the page's one "sheet of log paper", and
   ruling the chapter gradients would read as a printing fault.
   ------------------------------------------------------------------ */
#ns-root .ns-masthead {
  background-image:
    repeating-linear-gradient(#F2E9D9 0px, #F2E9D9 1px, transparent 1px, transparent 34px),
    radial-gradient(#ECE0C8 1px, transparent 1px);
  background-size: 100% 34px, 22px 22px;
  border-bottom: 1px solid rgba(58, 46, 34, .1);
}
#ns-root .ns-shell-inner {
  max-width: 1240px;
  margin: 0 auto;
  padding-left: clamp(16px, 3vw, 40px);
  padding-right: clamp(16px, 3vw, 40px);
}
#ns-root .ns-masthead .ns-shell-inner {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: clamp(24px, 4vw, 56px);
  padding-top: clamp(48px, 6vw, 92px);
  padding-bottom: clamp(40px, 5vw, 76px);
}
#ns-root .ns-mast-head { flex: 1 1 520px; min-width: 0; }
#ns-root .ns-mast-eyebrow {
  font: 700 12px/1 'Oswald', sans-serif;
  letter-spacing: .24em;
  text-transform: uppercase;
  color: #B8431F;
  margin-bottom: 16px;
}
#ns-root h1.ns-mast-title {
  font: 700 clamp(40px, 5.4vw, 68px)/1.02 'Oswald', sans-serif;
  letter-spacing: .005em;
  color: #3A2E22;
  margin: 0;
  text-wrap: balance;
}
#ns-root .ns-mast-lede {
  font: 400 clamp(15px, 1.4vw, 18px)/1.65 'Nunito Sans', sans-serif;
  color: #5C4A37;
  margin: 18px 0 0;
  max-width: 620px;
  text-wrap: pretty;
}

#ns-root .ns-mast-stats {
  flex: 0 1 auto;
  display: flex;
  gap: clamp(22px, 3vw, 44px);
  padding-bottom: 4px;
}
#ns-root .ns-stat { text-align: right; }
#ns-root .ns-stat-num {
  display: block;
  font: 700 clamp(34px, 3.6vw, 46px)/1 'Oswald', sans-serif;
  color: #3A2E22;
}
#ns-root .ns-stat-label {
  display: block;
  font: 700 10px/1.35 'Oswald', sans-serif;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: #8A7358;
  margin-top: 7px;
  max-width: 112px;
  margin-left: auto;
}

/* ------------------------------------------------------------------
   STICKY MERGED CHIP BAR
   One row: "Chapters" label, four era chips, the live-tracking chip.

   `top` is the REAL theme header height, not the export's 62px:
   styles.css:80 sets .site-header to 78px sticky. Below 860px that same
   header drops to 58px AND to position:relative (styles.css:1643-1646) —
   it scrolls away, so the correct offset there is 0, not 58px. That single
   media query is a correction to a measured value, not responsive design
   work (which is Step 7).

   z-index 700 is load-bearing. It must clear the dispatch banner (12) AND
   the info card (600, absolutely positioned inside .ns-stage, which creates
   no stacking context of its own), while staying under the site header
   (raised to 1000 for this page by next-stops.css:10).
   ------------------------------------------------------------------ */
#ns-root .ns-chipbar {
  position: sticky;
  top: 78px;
  z-index: 700;
  background: rgba(252, 246, 236, .96);
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
  border-bottom: 1px solid rgba(58, 46, 34, .13);
  box-shadow: 0 8px 18px -16px rgba(58, 46, 34, .55);
}
#ns-root .ns-chipbar .ns-shell-inner {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 10px 14px;
  padding-top: 11px;
  padding-bottom: 11px;
}
#ns-root .ns-chipbar-label {
  flex: 0 0 auto;
  font: 700 10.5px/1 'Oswald', sans-serif;
  letter-spacing: .2em;
  text-transform: uppercase;
  color: #8A7358;
}
/* The era chips keep .ns-filters/.ns-chip so next-stops-chart.js and
   next-stops-live.js both still find them; only the look is re-set. */
#ns-root .ns-chipbar .ns-filters { margin: 0; gap: 8px; }
#ns-root .ns-chipbar .ns-live-filters { margin: 0 0 0 auto; }

#ns-root .ns-chipbar .ns-chip {
  font: 700 11px/1 'Oswald', sans-serif;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: #3A2E22;
  background: transparent;
  border: 1px solid #3A2E22;
  border-radius: 999px;
  padding: 10px 15px;
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
  transition: background-color .2s ease, color .2s ease, border-color .2s ease;
}
/* TOUCH TARGET — 44px minimum, at EVERY width, desktop included.
   Not a mobile rule. The chips measured 97.9x33, 82.8x33, 75.2x33 and 121.1x33
   at 1440 exactly as they did at 375: the height was set by an 11px line box and
   10px of padding, and 33px is under the 44px pointer-target minimum on a
   touchscreen laptop for the same reason it is on a phone. Height only — the
   horizontal padding already carries every chip well past 44px wide.

   Cost, stated because it is a change to an approved desktop render: the chip
   bar grows from 56px to 67px at every width, which is why the anchor
   scroll-margin below moves with it. */
#ns-root .ns-chipbar .ns-chip {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
}
/* …and the display above must not outrank [hidden]. next-stops.css:93 already
   carries `#ns-root .ns-track-chip[hidden]{display:none}`, but that is 1 id +
   1 class + 1 attribute against this rule's 1 id + 2 classes — a specificity
   TIE, which the later stylesheet wins, and this file is enqueued last.
   Without this line the tracking chip renders a phantom "LIVE TRACKING:
   STANDING BY" control at 208.9x44 in the not_configured and disabled states,
   which is exactly the state the hidden attribute exists to prevent. Caught by
   the 1440 before/after diff, not by eye. */
#ns-root .ns-chipbar .ns-chip[hidden] { display: none; }
#ns-root .ns-chipbar .ns-chip:hover { border-color: #B8431F; color: #B8431F; }
#ns-root .ns-chipbar .ns-chip:focus-visible { outline: 3px solid #B8431F; outline-offset: 3px; }
/* Each era carries its ocean's tint as its resting fill; "Everything" is the
   whole chart, so it stays untinted. */
#ns-root .ns-chipbar .ns-chip--atlantic { background: #DFF2FB; }
#ns-root .ns-chipbar .ns-chip--pacific  { background: #E2E5FA; }
#ns-root .ns-chipbar .ns-chip--med      { background: #D9F3F2; }
#ns-root .ns-chipbar .ns-chip[aria-selected="true"],
#ns-root .ns-chipbar .ns-chip[aria-selected="true"]:hover {
  background: #3A2E22;
  border-color: #3A2E22;
  color: #FCF6EC;
}

/* Live-tracking chip. next-stops.css styles its three states for the old dark
   overlay (light text on near-black); on cream those are unreadable, so each
   state's ink is re-set here. The dots, and their ns-breathe / ns-lit
   animations, are untouched: this file defines no keyframes at all.

   STEP 4 made it a control. It is now the <button> that toggles the vessel
   marker, so the two rules that deliberately de-controlled it — cursor:default
   and the suppressed hover — are reversed here rather than in next-stops.css,
   which is off-limits. These sit inside .ns-chipbar at 1 id + 2 classes, above
   next-stops.css's 1 id + 1 class, so they win on specificity as well as on
   load order. */
#ns-root .ns-chipbar .ns-track-chip {
  background: #FCF6EC;
  border: 1px solid rgba(11, 110, 102, .32);
  color: #0B6E66;
  cursor: pointer;
}
/* The hover lift the chip was denied while it was informational. NOT the base
   .ns-chip gold (#E4B93B border, #FCF6EC ink): that pairing was written for the
   dark map overlay, and on this cream bar #FCF6EC ink on a #FCF6EC chip is
   invisible. The equivalent gesture in the chipbar's own palette is a lift to
   the same full-strength teal the --live state uses for its border. */
#ns-root .ns-chipbar .ns-track-chip:hover {
  border-color: rgba(11, 110, 102, .85);
  color: #07514B;
}
#ns-root .ns-chipbar .ns-track-dot {
  width: 8px;
  height: 8px;
  background: rgba(11, 110, 102, .6);
}
#ns-root .ns-chipbar .ns-track-chip--standby { color: #3F6F6A; }
#ns-root .ns-chipbar .ns-track-chip--stale   { color: #6F7D7A; }
#ns-root .ns-chipbar .ns-track-chip--live    { color: #0B6E66; border-color: rgba(11, 110, 102, .85); }

/* Marker hidden. Pressed-ON is the resting appearance, so the OFF state is what
   gets marked — the state colours (--standby / --stale / --live) already own
   this element, and a second colour system layered on aria-pressed would only
   collide with them.

   Killing the dot's animation is the part that carries meaning: a pulsing dot
   beside a hidden marker is a lie. The border stays lit, so the chip still reads
   as a live control rather than a disabled one.

   These cannot misfire on standby: standby REMOVES aria-pressed rather than
   setting it false, so the attribute selector simply does not match. That
   separation is by construction in setTrackChip() — no --standby guard needed.
   1 id + 2 classes + 1 attribute, so this also outranks the --live dot rule in
   next-stops.css on merit, not just on load order. */
#ns-root .ns-track-chip[aria-pressed="false"] .ns-track-dot {
  opacity: .35;
  animation: none;
}
#ns-root .ns-track-chip[aria-pressed="false"] .ns-track-chip-label {
  opacity: .6;
}

/* ------------------------------------------------------------------
   CHART ANCHORS
   Zero-height fragment targets, one per voyage, parked immediately above the
   stage. They are what makes "Show on the chart ↑" work with no JS at all:
   the button is a real <a href="#voyage-slug">, the browser scrolls here, and
   next-stops-chart.js's own hashchange listener selects and frames the leg.
   scroll-margin-top clears the sticky header (78) + chip bar. The bar is 67px
   since the 44px touch-target minimum above (it was 58), so this moved with it:
   78 + 67 = 145, plus a little air.
   ------------------------------------------------------------------ */
#ns-root .ns-chart-anchors { position: relative; height: 0; }
#ns-root .ns-chart-anchor {
  position: absolute;
  top: 0;
  left: 0;
  width: 0;
  height: 0;
  overflow: hidden;
  scroll-margin-top: 150px;
}

/* ------------------------------------------------------------------
   CHAPTERS
   Three backdrops read as ONE continuous vertical journey: daylight sky, into
   night, into tropical water. Each gradient's first stop is the previous
   gradient's last stop — change one end and the seam shows, so treat these
   seven-stop lists as a single unit.
   ------------------------------------------------------------------ */
#ns-root .ns-chapter { position: relative; }
#ns-root .ns-chapter .ns-shell-inner {
  padding-top: clamp(56px, 6.5vw, 96px);
  padding-bottom: clamp(60px, 7vw, 104px);
}
#ns-root .ns-chapter--atlantic {
  background: linear-gradient(#FCF6EC 0%, #EAF8FD 8%, #C3E9F8 34%, #9BDDF4 60%, #7BB8E8 82%, #3A5CC0 100%);
}
#ns-root .ns-chapter--pacific {
  background: linear-gradient(#3A5CC0 0%, #31439F 9%, #2E327F 18%, #2B2A78 58%, #3A49A4 78%, #6FB4E8 92%, #A3E0F4 100%);
}
#ns-root .ns-chapter--med {
  background: linear-gradient(#A3E0F4 0%, #93DEE9 18%, #7FD8DD 42%, #63CDD4 70%, #48C2CC 100%);
}

/* ------------------------------------------------------------------
   CHAPTER COLLAPSE (Step 3b, correction 2)
   The chip bar is labelled "Chapters", so selecting one has to filter the
   chapters and not just the routes. next-stops-chart.js's setEra() drives both
   from its single `currentEra`; this is only the appearance.

   COLLAPSED, NOT HIDDEN — and that choice is the whole answer to the seam
   problem. The three gradients are one continuous descent: Ch01 closes on
   #3A5CC0 and Ch02 opens on it; Ch02 closes on #A3E0F4 and Ch03 opens on it;
   Ch03 closes on #48C2CC and the shore opens on it. display:none on a middle
   chapter would butt #3A5CC0 straight against #A3E0F4 — a hard seam.

   A collapsed chapter keeps its section box and its whole seven-stop gradient
   and simply squeezes it into a ~110px connector. Because every stop is a
   percentage, the ramp compresses rather than clipping, so the colour chain is
   continuous BY CONSTRUCTION — there is no join to fade, in any filter state,
   and no per-pair colour bookkeeping to keep in sync. Its content and its
   scenic layer go with it; the section is aria-hidden and holds nothing
   focusable while collapsed.
   ------------------------------------------------------------------ */
#ns-root .ns-chapter.ns-is-collapsed {
  height: 110px;
  min-height: 0;
  overflow: hidden;
}
#ns-root .ns-chapter.ns-is-collapsed > .ns-shell-inner,
#ns-root .ns-chapter.ns-is-collapsed > .ns-deco { display: none; }

#ns-root .ns-chapter-head { max-width: 760px; margin-bottom: clamp(30px, 3.4vw, 48px); }
#ns-root .ns-chapter-num {
  font: 700 clamp(44px, 5vw, 64px)/1 'Oswald', sans-serif;
  letter-spacing: .02em;
  margin-bottom: 6px;
}
#ns-root .ns-chapter-eyebrow {
  font: 700 11px/1.4 'Oswald', sans-serif;
  letter-spacing: .2em;
  text-transform: uppercase;
  margin-bottom: 12px;
}
#ns-root h2.ns-chapter-h2 {
  font: 700 clamp(30px, 3.6vw, 46px)/1.08 'Oswald', sans-serif;
  margin: 0;
  text-wrap: balance;
}
#ns-root .ns-chapter-lede {
  font: 400 clamp(14.5px, 1.3vw, 17px)/1.65 'Nunito Sans', sans-serif;
  margin: 16px 0 0;
  max-width: 640px;
  text-wrap: pretty;
}

/* Per-chapter type colours. The Pacific chapter is the only one set on a dark
   ground, which is why its numeral goes gold and its body copy goes pale. */
#ns-root .ns-chapter--atlantic .ns-chapter-num     { color: #B8431F; }
#ns-root .ns-chapter--atlantic .ns-chapter-eyebrow { color: #B8431F; }
#ns-root .ns-chapter--atlantic .ns-chapter-h2      { color: #20424F; }
#ns-root .ns-chapter--atlantic .ns-chapter-lede    { color: #2F5462; }

#ns-root .ns-chapter--pacific .ns-chapter-num     { color: #FFD97A; }
#ns-root .ns-chapter--pacific .ns-chapter-eyebrow { color: #FFD97A; }
#ns-root .ns-chapter--pacific .ns-chapter-h2      { color: #FCF6EC; }
#ns-root .ns-chapter--pacific .ns-chapter-lede    { color: #D6DCFB; }

#ns-root .ns-chapter--med .ns-chapter-num     { color: #8C3A17; }
#ns-root .ns-chapter--med .ns-chapter-eyebrow { color: #8C3A17; }
#ns-root .ns-chapter--med .ns-chapter-h2      { color: #0A4A52; }
#ns-root .ns-chapter--med .ns-chapter-lede    { color: #0F4A52; }

/* ==================================================================
   THE CARD REDESIGN (revision of Step 3/3b, before Step 6 and Step 7)
   ==================================================================
   Kevin's first browser look at the rebuilt page: the cards were far too
   text-heavy, and the type did not match the rest of the site. Neither could be
   fixed by adding photographs — 13 of the 19 voyages have no gallery and cannot
   get one, because real Dreamworld photography starts in 2013 and the 1994–2004
   legs are film-era. The six voyages that DO have galleries are already the six
   coverable legs.

   TYPE. Oswald came in with the Claude Design export and appears on no other
   page in the theme; the site is Nunito Sans + Caveat. It stays on the masthead,
   the chapter headings and the chip bar — that is where the export's identity
   lives and it is deliberate — but it comes off the cards entirely, so a card
   reads as this site's card. Every Oswald card rule was weight 700, and Nunito
   Sans is loaded at 400/600/700/800 (functions.php), so nothing here is a
   browser-synthesised weight.

   TEXT. The description line (voyage_line) is gone from the card. It was the
   longest thing on it and the least specific.

   WHAT REPLACED IT. Each card now carries a glyph of its own course, drawn from
   voyage_track by inc/voyage-glyph.php — geometry that until now was drawn
   nowhere but the one big chart at the top of the page. The card is a picture
   again, without a photograph.

   PROPORTION. The card did not simply get shorter by the height of the deleted
   paragraph. The year and the route now carry it, so both grew; the glyph took
   the space the paragraph left and more; and the crew line lost the button row
   it used to sit above. Sizes below are re-balanced around that, not inherited.
   ================================================================== */

/* ------------------------------------------------------------------
   LOG-CARDS
   Four to a row at 1240px. auto-fill rather than a fixed repeat(4,…) so the
   count degrades on its own as the column narrows — the export ships no media
   queries and neither does this step.
   ------------------------------------------------------------------ */
#ns-root .ns-logcards {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(255px, 1fr));
  gap: 18px;
}
#ns-root .ns-logcard {
  /* position: relative is load-bearing, not tidying — it is what the stretched
     title link's ::after resolves its inset:0 against. See THE STRETCHED LINK
     below. (.ns-plate already had it, for the tape.) */
  position: relative;
  display: flex;
  flex-direction: column;
  min-height: 204px;
  background: #FFFDF7;
  border: 1px solid rgba(58, 46, 34, .14);
  border-radius: 6px;
  padding: 16px 17px 15px;
  box-shadow: 0 16px 30px -22px rgba(20, 34, 52, .7);
  transition: transform .25s ease, box-shadow .25s ease;
}
/* focus-within, not just hover: a keyboard user tabbing onto the title or the
   glyph gets the same lift a mouse user gets, so the card that is about to
   respond to Enter is the card that looks active. */
#ns-root .ns-logcard:hover,
#ns-root .ns-logcard:focus-within {
  transform: translateY(-3px);
  box-shadow: 0 24px 40px -22px rgba(20, 34, 52, .75);
}
#ns-root .ns-logcard-top {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  margin-bottom: 7px;
}
/* 800, and 28px rather than Oswald's 26. Oswald is a condensed face: set at the
   same size in Nunito Sans the year reads smaller and lighter than it did, and
   the year is now one of the two things carrying the card. */
#ns-root .ns-logcard-year {
  font: 800 28px/1 'Nunito Sans', system-ui, sans-serif;
  letter-spacing: -.01em;
}
/* Tracking down from .12em to .07em for the same reason in reverse: letter-
   spacing that opens up a condensed face pulls a normal-width one apart. */
#ns-root .ns-logcard-pill {
  flex: 0 0 auto;
  font: 700 9.5px/1 'Nunito Sans', system-ui, sans-serif;
  letter-spacing: .07em;
  text-transform: uppercase;
  padding: 5px 8px;
  border-radius: 3px;
}
#ns-root .ns-logcard-route {
  font: 700 16px/1.3 'Nunito Sans', system-ui, sans-serif;
  margin: 0 0 11px;
  text-wrap: pretty;
}
#ns-root .ns-logcard-crew {
  font: 700 9.5px/1.5 'Nunito Sans', system-ui, sans-serif;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: #8A7358;
  /* margin-top:auto still pushes this to the foot of the card. The 13px bottom
     margin went with the button row it used to clear. */
  margin: auto 0 0;
  padding-top: 11px;
  border-top: 1px dashed #E0D2B8;
}
#ns-root .ns-logcard-crew span { color: #0B6E66; }

/* ------------------------------------------------------------------
   THE CARD'S ONE LINK
   The route is the card's title and its only anchor. Colour is inherited from
   the old .ns-logcard-route rule rather than from #ns-root's blanket rust link
   colour, which would make every card title shout.
   ------------------------------------------------------------------ */
#ns-root .ns-card-link {
  color: #20424F;
  text-decoration: none;
}
#ns-root .ns-logcard:hover .ns-card-link,
#ns-root .ns-plate:hover .ns-card-link,
#ns-root .ns-card-link:hover,
#ns-root .ns-card-link:focus-visible { color: #B8431F; }
#ns-root .ns-card-link:focus-visible { outline: 3px solid #B8431F; outline-offset: 3px; }

/* ------------------------------------------------------------------
   THE STRETCHED LINK
   How the whole panel becomes clickable without the card being an <a>.

   The card must contain a second control — the glyph button — and a <button>
   inside an <a> is invalid HTML and keyboard-hostile. So the title anchor stays
   an ordinary inline link and its ::after is expanded to cover the card. The
   glyph button is lifted above that overlay on z-index and stays independently
   clickable and focusable.

   Result: two tab stops per card, one real link target for a screen reader and
   for "copy link address", and a panel that is clickable edge to edge.

   ⚠ KNOWN TRADE-OFF, inherent to this pattern and not a bug in this rule: the
   overlay sits above the card's text, so the text cannot be selected by
   dragging across it. That is the same behaviour Bootstrap's .stretched-link
   documents. The alternative — raising the text above the overlay with
   `position:relative; z-index:1` — restores selection and costs the full-panel
   click, because a press on the year, route or crew line would then hit text
   instead of the link. Full-panel click was chosen; the swap is one declaration
   if that call changes.
   ------------------------------------------------------------------ */
#ns-root .ns-card-link::after {
  content: "";
  position: absolute;
  inset: 0;
  /* Above the card's background and text, below the glyph button's z-index 1. */
  z-index: 0;
}

/* ------------------------------------------------------------------
   THE ROUTE GLYPH
   A miniature of the voyage's own course, and the button that selects that
   course on the chart above. Server-rendered inline SVG (inc/voyage-glyph.php),
   projected with the same Mercator next-stops-chart.js uses, so the small
   picture and the big line are the same shape.

   Colour, weight and the transition are all here rather than in the SVG, which
   carries geometry only.
   ------------------------------------------------------------------ */
#ns-root .ns-glyph-btn {
  /* Full button reset — no chrome, no default padding, no UA background. */
  display: block;
  width: 100%;
  margin: 0 0 13px;
  padding: 0;
  background: none;
  border: 0;
  border-radius: 3px;
  cursor: pointer;
  color: #3A2E22;
  /* Above the stretched link's ::after, so pressing the glyph selects on the
     chart instead of following the card's link. */
  position: relative;
  z-index: 1;
  transition: color .25s ease;
}
/* Rust on any interaction with the CARD, not only with the glyph — the glyph is
   the card's picture as much as it is a control, and it should warm up with the
   panel it belongs to. Its own hover and focus are included so it still
   responds when the card-level rules do not apply. */
#ns-root .ns-logcard:hover .ns-glyph-btn,
#ns-root .ns-logcard:focus-within .ns-glyph-btn,
#ns-root .ns-plate:hover .ns-glyph-btn,
#ns-root .ns-plate:focus-within .ns-glyph-btn,
#ns-root .ns-glyph-btn:hover { color: #B8431F; }
#ns-root .ns-glyph-btn:focus-visible { outline: 3px solid #B8431F; outline-offset: 3px; }

/* height + width:100% with the SVG's default preserveAspectRatio (xMidYMid
   meet) is what makes distortion impossible: the glyph's own path is already
   fitted and centred inside its viewBox preserving aspect ratio, and `meet`
   then scales that viewBox uniformly and centres it in whatever box this rule
   gives it. So the drawing is undistorted at 1280, at 1440, at 1920, and at
   whatever widths Step 7 introduces — by construction, not by measurement. */
#ns-root .ns-glyph {
  display: block;
  width: 100%;
  height: 108px;
  overflow: visible;
}
#ns-root .ns-glyph-line {
  stroke: currentColor;
  stroke-width: 1.6;
  /* Deliberately dimmer than the end dots. The line is the shape; the dots are
     the two facts (where it started, where it finished). */
  opacity: .78;
}
#ns-root .ns-glyph-dot { fill: currentColor; }

/* Year + stop-count pill take the chapter's own accent: rust in the two warm
   chapters, blue in the Pacific night. */
#ns-root .ns-chapter--atlantic .ns-logcard-year,
#ns-root .ns-chapter--med .ns-logcard-year { color: #B8431F; }
#ns-root .ns-chapter--atlantic .ns-logcard-pill,
#ns-root .ns-chapter--med .ns-logcard-pill { color: #B8431F; background: rgba(184, 67, 31, .1); }
#ns-root .ns-chapter--pacific .ns-logcard-year { color: #3A5CC0; }
#ns-root .ns-chapter--pacific .ns-logcard-pill { color: #5F6AA8; background: rgba(95, 106, 168, .12); }

/* ------------------------------------------------------------------
   BUTTONS
   Was "shared by cards and the shore scene". After the card redesign the shore
   scene is the only caller left, and .ns-btn is UNCHANGED for that reason: it
   still styles the shore's two CTAs, so its Oswald stays. Cards simply stopped
   using the class — de-Oswalding .ns-btn here would silently restyle the
   closing scene. (A companion text-link rule lived here too, styling the
   log-card's "Log book" link; the card redesign removed its only caller and
   Step 6 deleted the rule.)
   ------------------------------------------------------------------ */
#ns-root .ns-actions {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 10px 14px;
  margin-top: auto;
}
#ns-root .ns-btn {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  font: 700 10.5px/1 'Oswald', sans-serif;
  letter-spacing: .1em;
  text-transform: uppercase;
  padding: 11px 14px;
  border-radius: 4px;
  border: 1px solid transparent;
  cursor: pointer;
  transition: gap .2s ease, background-color .2s ease, color .2s ease, border-color .2s ease;
}
#ns-root .ns-btn:hover { gap: 11px; }
#ns-root .ns-btn:focus-visible { outline: 3px solid #B8431F; outline-offset: 3px; }
/* --ink and --ghost were deleted here: the card redesign removed their only
   callers (--ink was the old log-card's "Show on the chart" button) and a
   theme-wide grep plus a posts/postmeta/options query found zero of either,
   against one each for --rust and --cream. Step 6's dead-CSS sweep is closed,
   so this file was their only remaining home. */
#ns-root .ns-btn--rust { background: #B8431F; color: #FCF6EC; border-color: #B8431F; }
#ns-root .ns-btn--rust:hover { color: #FCF6EC; background: #9E3818; }
#ns-root .ns-btn--cream { background: #FCF6EC; color: #3A2E22; border-color: #FCF6EC; }
#ns-root .ns-btn--cream:hover { color: #B8431F; }

/* ------------------------------------------------------------------
   PHOTO PLATES
   Three to a row at 1240px, taped and slightly rotated. The photo slot is
   left GENUINELY EMPTY when a trip has no gallery image — no striped
   placeholder, no stand-in artwork, per the standing design decision on this
   page. The empty slot keeps its caption line so the plate still explains
   itself rather than reading as a broken image.
   ------------------------------------------------------------------ */
#ns-root .ns-plates {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(340px, 1fr));
  gap: 26px 22px;
  margin-top: clamp(28px, 3vw, 42px);
}
#ns-root .ns-plate {
  position: relative;
  display: flex;
  flex-direction: column;
  min-height: 419px;
  background: #FFFDF7;
  border: 1px solid rgba(58, 46, 34, .14);
  padding: 13px 13px 15px;
  box-shadow: 0 20px 38px -22px rgba(10, 40, 50, .75);
  transform: rotate(-1.1deg);
  transition: transform .25s ease, box-shadow .25s ease;
}
#ns-root .ns-plates > .ns-plate:nth-child(even) { transform: rotate(.9deg); }
#ns-root .ns-plates > .ns-plate:nth-child(3n) { transform: rotate(-.5deg); }
#ns-root .ns-plate:hover,
#ns-root .ns-plates > .ns-plate:nth-child(even):hover,
#ns-root .ns-plates > .ns-plate:nth-child(3n):hover {
  transform: translateY(-5px) rotate(0deg);
  box-shadow: 0 30px 50px -22px rgba(10, 40, 50, .8);
}
#ns-root .ns-plate-tape {
  position: absolute;
  top: -11px;
  left: 50%;
  transform: translateX(-50%) rotate(-2.5deg);
  width: 96px;
  height: 24px;
  background: rgba(255, 255, 255, .62);
  box-shadow: 0 2px 6px rgba(10, 40, 50, .18);
  z-index: 2;
}
#ns-root .ns-plate-figure { margin: 0 0 12px; }
#ns-root .ns-plate-photo {
  display: block;
  width: 100%;
  height: 186px;
  object-fit: cover;
  border: 1px solid #DCCFB6;
  background: #F1E7D4;
}
/* The empty slot: the same frame, nothing inside it. */
#ns-root .ns-plate-photo--empty {
  height: 186px;
  border: 1px dashed #CFC0A4;
  background: #F6EFE1;
}
#ns-root .ns-plate-caption {
  display: block;
  font: 600 15px/1.3 'Caveat', cursive;
  color: #4A6076;
  margin-top: 9px;
  text-wrap: pretty;
}
#ns-root .ns-plate .ns-logcard-top { margin-top: 2px; }

/* The plate already has a picture. Its glyph is a supporting mark rather than
   the card's main image, so it runs at roughly two-thirds the log-card's height
   and sits alongside nothing else. Same SVG, same viewBox, same no-distortion
   guarantee — only the box it is given is smaller. */
#ns-root .ns-plate .ns-glyph { height: 72px; }
#ns-root .ns-plate .ns-glyph-btn { margin-bottom: 11px; }

/* Both cards keep their hover lift on focus-within, for the same reason. */
#ns-root .ns-plate:focus-within,
#ns-root .ns-plates > .ns-plate:nth-child(even):focus-within,
#ns-root .ns-plates > .ns-plate:nth-child(3n):focus-within {
  transform: translateY(-5px) rotate(0deg);
  box-shadow: 0 30px 50px -22px rgba(10, 40, 50, .8);
}

/* ------------------------------------------------------------------
   SHORE — the closing scene: sea running up into sand.
   The 165px → 168px jump is the waterline. It is deliberately a hard cut, not
   a blend; softening it turns a shoreline into a gradient smear.
   The 62% stop must land below 168px, hence the min-height floor.
   ------------------------------------------------------------------ */
#ns-root .ns-shore-scene {
  background: linear-gradient(#48C2CC 0px, #3FC7C0 62px, #57D7C2 112px, #8CE6CF 165px, #F3D89F 168px, #EFCB8A 62%, #F6E2BC 100%);
  min-height: 520px;
}
#ns-root .ns-shore-scene .ns-shell-inner {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  padding-top: clamp(210px, 20vw, 250px);
  padding-bottom: clamp(60px, 7vw, 96px);
}
#ns-root .ns-shore-eyebrow {
  font: 700 11px/1 'Oswald', sans-serif;
  letter-spacing: .22em;
  text-transform: uppercase;
  color: #8C3A17;
  margin-bottom: 16px;
}
#ns-root .ns-shore-display {
  font: 700 clamp(28px, 3.6vw, 46px)/1.16 'Oswald', sans-serif;
  color: #3A2E22;
  margin: 0;
  max-width: 900px;
  text-wrap: balance;
}
#ns-root .ns-shore-lede {
  font: 400 clamp(15px, 1.4vw, 18px)/1.65 'Nunito Sans', sans-serif;
  color: #4A3C2B;
  margin: 18px 0 0;
  max-width: 640px;
  text-wrap: pretty;
}
#ns-root .ns-shore-scene .ns-actions { justify-content: center; margin-top: 30px; }

/* ==================================================================
   SCENIC LAYERS (Step 3b)
   The export's four illustrated backdrops: daylight over the Atlantic, a night
   sky over the Pacific, tropical water in the Mediterranean, and the closing
   shore. Every position, size, colour and timing below is measured from the
   approved export and recorded in next-stops-v2-decorative-layers-REFERENCE.md.
   Do not tune these by eye.

   THREE DELIBERATE RE-EXPRESSIONS of measured pixels, all equal to the
   reference at its own measurement width:
     1. `right: 6vw` for the sun and the sailboat. The reference records
        86.4px, which is 6vw at 1440 — the export's content gutter, authored as
        a viewport unit. Held as px it would drift off the gutter everywhere
        else; as 6vw it computes to exactly 86.4px at 1440 and tracks the
        gutter at every other width.
     2. Pacific star field `height: 486px` for the reference's
        `top:0; bottom:142.625px`, and moon `height: 210px` for its
        `top:418.297px; bottom:0`. Both of those insets are arithmetic off the
        export's own section height (628.297px), not design values — and the
        section here is taller, because it carries real cards. Held as insets,
        preserveAspectRatio="none" would stretch every star and the satellite
        into ellipses and streaks. Anchored to their measured heights (486 =
        628.297 - 142.625, 210 = the moon viewBox exactly) they render at the
        export's own size: stars in the top of the sky, moon at the foot of it,
        with more deep blue between on a taller section.
   Everything else — every path, cx/cy/r/opacity, colour, duration and delay —
   is verbatim.

   DECORATION IS INERT. Every wrapper is aria-hidden in the markup and
   pointer-events:none here; nothing inside is focusable or hit-testable.
   ================================================================== */
#ns-root .ns-deco {
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
}
#ns-root .ns-deco svg { display: block; }
/* Content sits above its own section's decoration. The chapters and the shore
   are position:relative already; this is what puts type and cards in front. */
#ns-root .ns-chapter > .ns-shell-inner,
#ns-root .ns-shore-scene > .ns-shell-inner { position: relative; z-index: 1; }

/* ---- Chapter 01 · Atlantic (daylight) ---------------------------- */
/* A div, not an SVG: the two-ring halo is a box-shadow, which an SVG circle
   cannot do without two more elements. */
#ns-root .ns-deco-sun {
  position: absolute;
  top: 40px;
  right: 6vw;
  width: 124px;
  height: 124px;
  border-radius: 50%;
  background: radial-gradient(circle at 38% 34%, #FFE9A8, #F6C445 62%, #EFA92E);
  box-shadow: 0 0 0 18px rgba(246, 196, 69, .22),
              0 0 0 38px rgba(246, 196, 69, .11);
}
/* -2px, not 0: the strokes close the section on the next chapter's opening
   colour, and a subpixel gap there would read as a hairline seam. */
#ns-root .ns-deco-waves {
  position: absolute;
  left: 0;
  right: 0;
  bottom: -2px;
  height: 64px;
}

/* ---- Chapter 02 · Pacific (night) -------------------------------- */
#ns-root .ns-deco-stars {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 486px;
}
#ns-root .ns-deco-moon {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 210px;
}
#ns-root .ns-deco-stars svg,
#ns-root .ns-deco-moon svg { width: 100%; height: 100%; }

/* The moon body + halo, lifted out of that SVG into a div so it stays CIRCULAR.
   Its band's preserveAspectRatio="none" pins the vertical scale at 1 and lets the
   horizontal scale track container width, which is what the gull strokes want and
   the exact opposite of what a circle wants: the r=42 body measured 86.8x84 at
   1240, 100.8x84 at 1440 and 179.2x84 at 2560. Same construction as the sun above.

   Every measured value from the reference is preserved, just re-expressed:
     84px            = r 42, the SVG's TRUE (unstretched) diameter
     28px spread     = the r=70 halo: 84 + 2*28 = 140 = 84 * (70/42), ratio exact
     top 122px       = cy 164 - r 42, inside the band's own 210px
     right calc      = cx 965/1200 -> the centre sits 19.58333% of the width in from
                       the right edge, which is precisely what the stretched SVG did;
                       minus r 42 to convert centre -> right edge. vw (not %) so it
                       tracks the viewport exactly as the sun's `right: 6vw` does.
                       Verified against the pre-change render: centre 242.83px from
                       the right at 1240, 282 at 1440, 376 at 1920, 501.33 at 2560.
   Two alphas rather than one `opacity`: the SVG gave the body .9 and the halo .18
   independently, and an element opacity would multiply the halo down to .162. */
#ns-root .ns-deco-moonbody {
  position: absolute;
  top: 122px;
  right: calc(19.58333vw - 42px);
  width: 84px;
  height: 84px;
  border-radius: 50%;
  background: rgba(255, 217, 122, .9);
  box-shadow: 0 0 0 28px rgba(255, 217, 122, .18);
}

/* ---- Chapter 03 · Mediterranean (tropical) ----------------------- */
#ns-root .ns-deco-gulls {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 96px;
}
#ns-root .ns-deco-currents {
  position: absolute;
  inset: 0;
  opacity: .5;
}
#ns-root .ns-deco-gulls svg,
#ns-root .ns-deco-currents svg { width: 100%; height: 100%; }

/* The clip lives here rather than on .ns-chapter: a fish at translateX(112%)
   reaches twice the section width, and unclipped that is a document-wide
   horizontal scrollbar. Clipping the chapter itself would also cut the -2px
   wave overhang above. */
#ns-root .ns-deco-fish {
  position: absolute;
  inset: 0;
  overflow: hidden;
}
/* Each fish is a full-width wrapper carrying the drift, with the artwork at its
   left edge — so one translateX sweep carries it the whole way across at any
   viewport width, with no per-width tuning. */
#ns-root .ns-fish {
  position: absolute;
  left: 0;
  width: 100%;
  animation-name: ns-drift;
  animation-timing-function: linear;
  animation-iteration-count: infinite;
}
/* The negative delays are load-bearing: they are what scatters the three across
   the width on FIRST PAINT instead of releasing them from the left edge
   together. Keep them exactly. */
#ns-root .ns-fish--1 { top: 36%; animation-duration: 64s;  animation-delay: 0s; }
#ns-root .ns-fish--2 { top: 58%; animation-duration: 86s;  animation-delay: -24s; }
#ns-root .ns-fish--3 { top: 76%; animation-duration: 108s; animation-delay: -58s; }

@keyframes ns-drift {
  0%   { transform: translateX(-12%); }
  100% { transform: translateX(112%); }
}

/* ---- Shore · the closing scene ----------------------------------- */
/* The section had no positioning of its own — it does now, so its decoration
   has something to hang off. */
#ns-root .ns-shore-scene { position: relative; }
/* 168px is the waterline: it is the same figure as the hard #8CE6CF -> #F3D89F
   cut in the shore gradient above, so the strokes sit on the sea and stop at
   the sand. Changing one without the other breaks the shoreline. */
#ns-root .ns-deco-shorewaves {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 168px;
}
#ns-root .ns-deco-shorewaves svg { width: 100%; height: 100%; }
/* Anchored in px from the same section top the waterline is measured from, so
   the hull (bottom edge at 16 + 108 = 124px) stays above the 165px waterline at
   every viewport width — it cannot drift under the sea. */
#ns-root .ns-deco-boat {
  position: absolute;
  top: 16px;
  right: 6vw;
  width: 150px;
  height: 142px;
}
#ns-root .ns-deco-shoregulls {
  position: absolute;
  top: 34px;
  left: 72px;
  width: 96px;
  height: 52px;
  opacity: .9;
}

/* ------------------------------------------------------------------
   FOOTER — page-scoped. The theme footer is outside #ns-root (it comes from
   get_footer()), so this is the one block keyed off the body class instead.
   Scoped tight so the sitewide footer treatment is untouched everywhere else.
   ------------------------------------------------------------------ */
body.page-template-template-next-stops .site-footer {
  background-color: #8B5E3C;
  background-image: radial-gradient(rgba(255, 255, 255, .05) 1px, transparent 1px);
  background-size: 20px 20px;
  color: #F0E2CB;
}
body.page-template-template-next-stops .site-footer a { color: #F0E2CB; }
body.page-template-template-next-stops .site-footer a:hover { color: #E4B93B; }
body.page-template-template-next-stops .site-footer svg { color: #F0E2CB; }

@media (max-width: 860px) {
  /* Not responsive design work (Step 7) — a correction. Below 860px the theme
     header is position:relative and scrolls away (styles.css:1643-1646), so
     the only correct sticky offset is 0. Holding it at the header's 58px
     height would leave the bar floating in a gap. */
  #ns-root .ns-chipbar { top: 0; }
  /* The header is gone here, so this only has to clear the 67px chip bar. */
  #ns-root .ns-chart-anchor { scroll-margin-top: 74px; }
}

/* ==================================================================
   STEP 7 · MOBILE
   ==================================================================
   The Claude Design export ships ZERO media queries — it is clamp() and
   auto-fill at desktop width — so unlike every other step on this page there is
   no comp to follow here. Everything below is a decision, and each one carries
   the measurement it was taken from.

   720px is this page's established breakpoint (two blocks in next-stops.css
   already) and one of the theme's five global values (styles.css: 1024 / 860 /
   720 / 620 / 460). No new breakpoint is introduced. The 860px block above
   stays what it is — a correction to the sticky offset, not responsive work.

   WHAT IS ALREADY CORRECT AND IS DELIBERATELY NOT TOUCHED, all measured at 375
   and 414 before any change:
     - Horizontal overflow. scrollWidth === clientWidth on documentElement,
       body and #ns-root at 375, 414, 768, 1024 and 1440. The fish that reach
       x=474 and x=591 at 375 are contained by .ns-deco-fish's overflow:hidden
       exactly as Step 3b claims.
     - Card columns. .ns-logcards auto-fill minmax(255px) and .ns-plates
       minmax(340px) already collapse to one column on their own at 375 and
       414. A media query forcing 1fr would be a second source of truth for
       something the grid already gets right.
     - Stage height in portrait. See the note on .ns-stage below — the
       measurement argued against changing it.
   ------------------------------------------------------------------ */
@media (max-width: 720px) {

  /* ---- STAGE · short-viewport guard only --------------------------
     min-height 460px is NOT over-generous on a phone, which is what the
     measurements showed and what stopped this from being cut to ~340:

       base route band   103.8px at EVERY stage height from 220 to 460 — the
                         fit is width-limited at 375, so height buys the base
                         view nothing. That much was true.
       selection framing zoomFor's vertical term starts binding before the 5.5
                         cap below 400px: 0 voyages are height-limited at 400+,
                         3 at 380, 4 at 340, 6 at 260.
       the info card     natural height 276px as a bottom sheet against
                         max-height:64% — it needs 431px of stage to avoid
                         scrolling inside itself. 460 clears that by 7%.

     So portrait is unchanged. What IS broken is a SHORT viewport: a landscape
     phone is 375px tall and a 460px floor simply overflows it. min() takes the
     smaller, and is provably inert in portrait — at 667px tall, 78dvh is 520px,
     so min() returns 460 and nothing moves. At 375px tall it returns 292px,
     which still holds the whole 200px-tall wake that width projects.
     ---------------------------------------------------------------- */
  #ns-root .ns-stage { min-height: min(460px, 78dvh); }

  /* ---- CHIP BAR · one row, horizontally scrolled ------------------
     The bar is sticky, so its height is subtracted from an already short
     viewport for the whole page. Wrapped, it measured 127.5px at both 375 and
     414 — three rows (the label alone, then three chips, then Mediterranean),
     19% of a 667px phone. One scrolled row holds it at 67px.

     `top` stays 0. It is NOT the 58px mobile header height: below 860px the
     theme header is position:relative and scrolls away (styles.css:1643-1646),
     which is the correction the 860px block above already makes. Pinning the
     bar to 58px would leave it floating in a 58px gap on every scroll.
     ---------------------------------------------------------------- */
  #ns-root .ns-chipbar .ns-shell-inner {
    flex-wrap: nowrap;
    overflow-x: auto;
    overflow-y: hidden;
    overscroll-behavior-x: contain;
    -webkit-overflow-scrolling: touch;
    scroll-padding-inline: 16px;
    /* No scrollbar chrome: the bar is 67px tall and a gutter would eat a
       sixth of it. The affordance is carried by the shading below instead. */
    scrollbar-width: none;
    -ms-overflow-style: none;

    /* THE SCROLL AFFORDANCE. Two pairs of gradients: the first two are
       `local`, so they travel with the content and sit over the second two,
       which are `scroll` and stay pinned to the element. At rest the covers
       hide the shading; scroll right and the left cover slides away, exposing
       it. The point of doing it this way rather than with a plain ::after
       fade is that it COSTS NOTHING WHEN THERE IS NOTHING TO SCROLL — at 720px
       the row fits, and a permanent gradient stripe there would be a lie.

       Ordering is load-bearing: covers must be listed first to paint on top.
       The cover colour matches the bar's own rgba(252,246,236,.96). */
    background:
      linear-gradient(to right, rgba(252, 246, 236, .96) 42%, rgba(252, 246, 236, 0)) 0 0 / 34px 100% no-repeat local,
      linear-gradient(to left,  rgba(252, 246, 236, .96) 42%, rgba(252, 246, 236, 0)) 100% 0 / 34px 100% no-repeat local,
      linear-gradient(to right, rgba(58, 46, 34, .20), rgba(58, 46, 34, 0)) 0 0 / 22px 100% no-repeat scroll,
      linear-gradient(to left,  rgba(58, 46, 34, .20), rgba(58, 46, 34, 0)) 100% 0 / 22px 100% no-repeat scroll;
  }
  #ns-root .ns-chipbar .ns-shell-inner::-webkit-scrollbar { display: none; }

  /* Nothing in the row may shrink or wrap, or the "one row" is just a
     narrower version of the same three-row pile. */
  #ns-root .ns-chipbar-label,
  #ns-root .ns-chipbar .ns-filters,
  #ns-root .ns-chipbar .ns-chip { flex: 0 0 auto; }
  #ns-root .ns-chipbar .ns-filters { flex-wrap: nowrap; }
  /* margin-left:auto resolves to 0 once the row overflows, so it is doing
     nothing here; dropped so the tracking chip sits a normal gap after the era
     chips rather than at an accidental distance. */
  #ns-root .ns-chipbar .ns-live-filters { margin: 0; }

  /* ---- MASTHEAD · stats stop fitting by luck ----------------------
     At 375 the three stats measured 89.2 + 104.9 + 104.9 plus 44px of gaps =
     exactly 343px of 343px available. Zero slack. Step 5 made all six of these
     strings editable fields (ns_stat1_num .. ns_stat3_label), so the first
     longer label Simon types breaks a layout that only ever fit by arithmetic.

     auto-fit gives 2 columns at 375 and 414 (2+1) and 3 from ~500px up, and
     every cell can grow to as many lines as the text needs. text-align goes
     left with it: the right-alignment exists to set the stats against the lede
     on one desktop row, and there is no such row here. The label's 112px cap
     and auto left margin are part of that same right-aligned block and go too —
     they are what would clip a longer label soonest.
     ---------------------------------------------------------------- */
  #ns-root .ns-mast-stats {
    flex: 1 1 100%;
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
    gap: 20px 22px;
    padding-bottom: 0;
  }
  #ns-root .ns-stat { text-align: left; }
  #ns-root .ns-stat-label {
    max-width: none;
    margin-left: 0;
  }

  /* ---- THE INFO CARD'S CONTROLS · touch targets -------------------
     The card itself is next-stops.css's and its bottom-sheet rules are left
     alone; these are its two controls, which measured 30x30 and 41px tall.
     They sit here rather than there for the same reason the tracking chip's
     cream-ground colours do — this file is the one that gets to have opinions
     about how the page looks, and next-stops.css is treated as read-only.

     The close button keeps its 30px DISC and grows only its hit area, via a
     transparent pseudo-element: the visible affordance was designed at 30px
     and a 44px filled circle in the corner of a bottom sheet is a different,
     heavier thing. Absolutely-positioned pseudo-elements are not flex items,
     so the button's own centring of the glyph is untouched. */
  /* WHERE THE MOBILE LAYOUT FIGHTS THE APPROVED DESKTOP ONE, and the one place
     this step could not fully resolve in CSS.

     On desktop the info card is a 340px panel in the stage's top-right corner
     and the framed voyage sits in the open middle. next-stops.css turns it into
     a full-width bottom sheet below 720px, and zoomFor() centres a selection at
     (mw/2, mh/2) — so on a phone the sheet lands exactly on top of the thing the
     chart just zoomed to. Measured at 375 with Studland -> Cyprus selected:
     stage 460, route drawn y 173-287, sheet top y 158. The whole route, hidden.

     The correct fix is to bias the framing upward by the sheet's height when a
     card is open below 720px — one line in applyZoom(). That is a change to
     next-stops-chart.js beyond this step's "targetZoom() only" scope, so it is
     REPORTED AND DEFERRED, not smuggled in here.

     What CSS can do is give the route back some room. At 46% the sheet starts at
     y 240 and 67px of the 114px route (59%) stays visible above it, including
     the top of the wake. The cost is that the sheet now scrolls internally for
     more voyages than it did — it already did for all six gallery voyages
     (294px natural against the old 64% = 294px allowance, exactly at the line).
     The trade was taken deliberately: a card is readable when scrolled, and a
     framing you cannot see makes the whole tap pointless. The first ~200px of
     the card is the identity of what was tapped — era, year, route — which is
     what a reader needs before deciding to scroll.

     Here rather than in next-stops.css so that file stays untouched. */
  #ns-root .ns-card { max-height: 46%; }

  #ns-root .ns-card-close::after {
    content: "";
    position: absolute;
    top: 50%;
    left: 50%;
    width: 44px;
    height: 44px;
    transform: translate(-50%, -50%);
  }
  #ns-root .ns-card-btn { min-height: 44px; }

  /* (The shore scene's two CTAs were raised to 44px here in Step 7. That rule
     moved to a `pointer: coarse` query at the end of this file — it is a touch
     concern, not a width one. See the note there.) */

  /* ==================================================================
     THE FOUR SCENIC BACKDROPS
     ==================================================================
     All four survive; none is hidden. Two rules decided the treatment:

     1. A BAND WHOSE ARTWORK IS STROKES may keep preserveAspectRatio="none" and
        squash — that is what it is for, and the Step 3b reference says so.
     2. A BAND CARRYING SHAPES may not. At 375 a 1200-wide viewBox is scaled to
        0.31 horizontally, so anything round becomes a vertical sliver. This is
        the same failure Step 3b caught on the moon and fixed by pulling the
        body out into a div; below it is fixed the other way, by giving the
        wrapper the viewBox's own aspect-ratio so the scale is uniform.

     aspect-ratio, not a vw height: it resolves against the element's real
     width, so it stays exact where a vw expression would be off by the
     scrollbar. ------------------------------------------------------ */

  /* ---- Chapter 01 · Atlantic ---- */
  /* THE ONE REAL REFLOW DEFECT ON THE PAGE. The sun is 124px at right:6vw, so
     at 375 it occupies x 228.5-352.5 and y 40-164 within the section — and the
     h2 runs 133-198 there, because .ns-chapter-head's 760px cap does nothing at
     375 and "The Atlantic years" wraps across the full width. Confirmed in the
     render: the heading crosses the sun's lower limb.

     Smaller and higher, not hidden: 84px at top:14 ends at 98, clearing the
     h2's 133 by 35px, and it stays right of the 44px "01" numeral. The two
     halo rings scale with it — 18/38 * (84/124) = 12/26 — so the halo stays in
     the same proportion to the disc rather than swelling around a smaller sun. */
  #ns-root .ns-deco-sun {
    top: 14px;
    right: 5vw;
    width: 84px;
    height: 84px;
    box-shadow: 0 0 0 12px rgba(246, 196, 69, .22),
                0 0 0 26px rgba(246, 196, 69, .11);
  }
  /* Strokes, so the squash is legitimate — but free to fix, since the waves are
     anchored to the section's bottom edge and nothing depends on their height.
     1200x64 is 18.75:1; at 375 that is 20px, and the -2px seam overhang below
     is preserved because it is an offset, not a height. */
  #ns-root .ns-deco-waves {
    height: auto;
    aspect-ratio: 1200 / 64;
  }
  /* This band's SVG carries height="64" as an ATTRIBUTE, so the wrapper's auto
     height resolves from that content and the aspect-ratio above is ignored —
     measured: still 64px until this rule was added. The star and current bands
     do not need it; their SVGs are already height="100%". */
  #ns-root .ns-deco-waves svg { width: 100%; height: 100%; }

  /* ---- Chapter 02 · Pacific ---- */
  /* Nineteen circles and one line-art satellite in a 1200x620 viewBox stretched
     into 375x486: 0.31 horizontal against 0.78 vertical, a 2.5x squash. Every
     star renders as a vertical ellipse and the satellite's 26x22 body as a
     slit. At the viewBox's own ratio the field is 194px at 375 and every star
     is round — the same "stars in the top of the sky" the Step 3b note
     describes, just not stretched to reach further down a taller section. */
  #ns-root .ns-deco-stars {
    height: auto;
    aspect-ratio: 1200 / 620;
  }
  /* The moon band KEEPS its 210px and its squash, deliberately. Its artwork is
     two gull strokes, which tolerate it — and .ns-deco-moonbody is positioned
     top:122px INSIDE this band, so shrinking it to the viewBox ratio (66px at
     375) would drop the moon out of the chapter altogether. The band is a
     coordinate system here, not just a picture. */
  /* The halo is a 28px box-shadow spread, so the body needs 28px of clearance
     to stay whole. calc(19.58333vw - 42px) gives 31.4px at 375 (fine) but only
     20.7px at 320, which crops the halo at the viewport edge. max() holds the
     floor and is inert at 375 and above — it returns the calc there. */
  #ns-root .ns-deco-moonbody {
    right: max(30px, calc(19.58333vw - 42px));
  }

  /* ---- Chapter 03 · Mediterranean ---- */
  /* 1200x96 of strokes, but free like the Atlantic waves: nothing is anchored
     to the gulls. 30px at 375, undistorted. */
  #ns-root .ns-deco-gulls {
    height: auto;
    aspect-ratio: 1200 / 96;
  }
  /* THE WORST DISTORTION ON THE PAGE, and the least visible one. inset:0 puts a
     1200x900 viewBox into 375x3342 at 375 — 0.31 horizontal against 3.71
     vertical, a 11.9x aspect distortion that turns three horizontal current
     strokes into near-vertical zigzags at an effective opacity of .07-.11.

     Undistorted the band is 281px, which cannot fill a 3342px chapter, so this
     is a real trade: presence for shape. Shape wins — three legible ripples
     running behind the middle of the card stack read as water; a full-height
     smear reads as a rendering fault. Centred rather than pinned, so it sits
     behind the cards instead of under the chapter heading.

     (For scale: the approved 1440 render is itself 1.53x distorted here. The
     objection is to 11.9x, not to distortion as such.) */
  #ns-root .ns-deco-currents {
    inset: auto 0 auto 0;
    top: 50%;
    height: auto;
    aspect-ratio: 1200 / 900;
    transform: translateY(-50%);
  }

  /* ---- Shore ---- */
  /* .ns-deco-shorewaves keeps its 168px: that number IS the waterline, the same
     figure as the hard #8CE6CF -> #F3D89F cut in the shore gradient, and its
     artwork is strokes. Changing it would move the sea. */
  /* The boat is undistorted already (a real viewBox with the default
     preserveAspectRatio) — it is simply too big, at 150px across 40% of a 375px
     viewport. 108x102 holds 150:142 exactly. The SVG carries width/height
     ATTRIBUTES of 150/142, so the box alone would not resize it; CSS on the
     child overrides a presentation attribute, and `meet` does the rest without
     distortion. Still clear of the water: 16 + 102 = 118, above the 165px
     waterline, which is the invariant the original comment protects. */
  #ns-root .ns-deco-boat {
    width: 108px;
    height: 102px;
  }
  #ns-root .ns-deco-boat svg { width: 100%; height: 100%; }

  /* ---- The other half of undistorting a stroke band ----------------
     preserveAspectRatio="none" was doing one useful thing for these three: it
     held the vertical scale near 1, so a 3px wave stroke stayed 3px. Giving
     them the viewBox's own ratio scales BOTH axes to ~0.31 at 375, which turns
     every ripple into a 0.94px hairline — shape fixed, artwork lost.

     non-scaling-stroke pins each stroke to its authored width in screen pixels
     while the geometry scales. Same device next-stops-chart.css already uses to
     hold the vessel ring at 3px through the zoom group's counter-scale.

     Deliberately NOT applied to the star field: its circles are fills, not
     strokes, and the satellite's 2.4px outline held at full width inside an
     8x7px body would read as a blob rather than as line art. */
  #ns-root .ns-deco-waves svg *,
  #ns-root .ns-deco-gulls svg *,
  #ns-root .ns-deco-currents svg * { vector-effect: non-scaling-stroke; }
}

/* ------------------------------------------------------------------
   TOUCH TARGETS BY INPUT TYPE
   The shore scene's two CTAs are 34.5px as authored. That is a TOUCH concern,
   not a compliance one: WCAG 2.2 AA (SC 2.5.8) asks for 24x24, which 34.5
   already meets; 44x44 is AAA (2.5.5) and the iOS/Android platform guideline.
   So the fix is keyed to the input device, not to the viewport — a 375px-wide
   MOUSE window keeps the approved 34.5px, which the width-based rule this
   replaces got wrong, and every phone is a coarse pointer so nothing is lost.

   ⚠ KNOWN INCONSISTENCY, deliberate and not to be "fixed" here: Step 7 raised
   the chip bar's chips to 44px at EVERY width, which is why the desktop bar
   went 56px -> 67px. That is shipped, verified and approved. The page therefore
   has chips sized for touch everywhere and CTAs sized for touch only on touch
   devices. Re-scoping the chips would be a second change to an approved desktop
   render for no measured defect.
   ------------------------------------------------------------------ */
@media (pointer: coarse) {
  #ns-root .ns-btn { min-height: 44px; }
}

@media (prefers-reduced-motion: reduce) {
  #ns-root .ns-logcard,
  #ns-root .ns-plate,
  #ns-root .ns-btn,
  #ns-root .ns-glyph-btn,
  #ns-root .ns-chipbar .ns-chip { transition: none; }
  /* The lift goes, on hover AND on focus-within — a keyboard user with reduced
     motion asked for no movement just as plainly as a mouse user did. The
     COLOUR change stays in both cases: it is the feedback, not the motion. */
  #ns-root .ns-logcard:hover,
  #ns-root .ns-logcard:focus-within,
  #ns-root .ns-plate:hover,
  #ns-root .ns-plate:focus-within { transform: none; }
  #ns-root .ns-plates > .ns-plate:nth-child(even):hover,
  #ns-root .ns-plates > .ns-plate:nth-child(even):focus-within,
  #ns-root .ns-plates > .ns-plate:nth-child(3n):hover,
  #ns-root .ns-plates > .ns-plate:nth-child(3n):focus-within { transform: none; }
  /* The fish stop dead. `animation-play-state: paused` rather than the export's
     `animation: none` — the ONE deviation from the reference file in this step,
     and it is the difference between three fish stopped in the water and three
     fish stacked against the left edge. `animation: none` discards the negative
     delays along with the animation, which resets all three to translateX(0);
     pausing holds the exact frame those delays put them at. No motion occurs
     either way. Everything else added in Step 3b is static artwork. */
  #ns-root .ns-fish { animation-play-state: paused; }
}
