/* ============================================================================
   beacon.css — the deck, and the constellation that navigates it.
   ----------------------------------------------------------------------------
   Founder direction 2026-08-14: fade between sections rather than scroll up
   from the bottom; make it quicker; and make it visible that there is
   something to move through.

   Two mechanisms, and the split is deliberate.

   THE DECK IS NATIVE SCROLL-SNAP. One flick moves one beat because the browser
   says so. Nothing here listens to the wheel — intercepting it to drive a deck
   is scroll hijacking, which the base audit ruled out for this audience and
   which reads as a broken page on the trackpads district IT runs.

   THE BEACON IS REAL ANCHORS. A <nav> of links to #beat-n. It answers "how many
   beats are here and which am I on" before the reader touches anything, and it
   lets them jump rather than travel. With JavaScript off, every link still
   works and only the lit state is missing.

   Measured before: 2.2 screens of scroll per beat, ~510 wheel notches to read
   the site. A panel is now exactly one viewport, so it is one gesture per beat.
   ========================================================================= */

/* ── THE SNAP CONTAINER ────────────────────────────────────────────────────
   On the ROOT, because that is the scroller for a normal document. The class
   is added by the shell only on pages that have beats, so the reference papers
   — which are for reading and scrolling freely — are untouched. */
/* ⛔ AND NOT BELOW THE CANVAS BREAKPOINT. canvas.css already abandons the
   deck at 700px — the held photograph becomes a banner and the panels become
   ordinary stacked type, because "a held image plus overlaid type is a bad
   trade: the image is small, the type is large, and the two fight". The
   SNAPPING was never taken off with it, so the page kept behaving like a deck
   over a layout that is not one: on load it snapped straight past the banner
   to the first panel and the reader never saw the photograph.
   A deck mechanic on a document layout is the wrong half of two designs. Below
   the breakpoint this scrolls like the document it is. */
/* ⛔ THE SNAP CONTAINER IS RETIRED. `scroll-snap-type: y mandatory` was here,
   and Lenis now owns the scroll position site-wide (assets/smooth.js). Two
   owners of one number is a judder that cannot be tuned away, so one of them
   had to go and the programmatic engine is the one that can also drive
   timelines.

   ⛔ THE CLASS STAYS, AND THAT IS THE WHOLE POINT OF DOING THIS SURGICALLY.
   `html.beats` gates FIVE things in shell.js and only this one was snap: it
   also emits beacon.css, the navigator markup, beacon.js and reveal.js.
   Stripping the class — as a global "remove the snap hooks" sweep would —
   deletes the beat navigator and the directional arrivals from all 11 deck
   pages and leaves them as plain documents. What was asked for was smooth
   scrolling, not the loss of the navigator.

   scroll-padding-block-start stays: it is not a snap property. It keeps an
   anchor jump clear of the fixed chrome, which matters more now that
   smooth.js routes those jumps through lenis.scrollTo. */
html.beats {
  scroll-padding-block-start: 0;
}
/* ⛔ SAME SPECIFICITY, SO THIS MUST COME AFTER — it did not, the first time,
   and the override silently lost to the rule above it while reading as though
   it worked. Measured at 390px: computed scroll-snap-type was still
   "y mandatory". */
/* ⛔ FREE SCROLL MEANS CONTENT PASSES UNDER THE FIXED CHROME — AT EVERY WIDTH.
   The wordmark and the menu are transparent and sit at z-index 90. Under
   mandatory snap a panel was always bounded to the viewport, so nothing ever
   arrived behind them.

   ⚠ THIS RULE WAS SCOPED TO max-width: 700px, AND THAT SCOPE EXPIRED THE MOMENT
   SNAP WAS RETIRED. It was written when only mobile scrolled freely; desktop
   was held by the snap and could not reach the chrome. Retiring snap site-wide
   gave every viewport the same behaviour and left the guard on one of them.
   Caught by looking: at scrollY 1.55x the viewport on /journey, "CetusEd" was
   struck straight through the lead paragraph. Sampled after: 1 of 5 scroll
   positions on /journey, 0 of 5 on /clocks and /measure — intermittent by
   nature, because with free scrolling ANY content can be under the chrome at
   SOME offset, which is exactly why it needs a scrim rather than a layout rule.

   A scrim, not a solid bar: the photography still shows through the top of it.
   z-index 87 keeps it under the beacon (88) and the two corners (90). */
html.beats body::after {
  content: ""; position: fixed; z-index: 87; pointer-events: none;
  inset-inline: 0; inset-block-end: 0; height: 7.5rem;
  background: linear-gradient(to top,
    color-mix(in srgb, var(--sky) 92%, transparent) 0%,
    color-mix(in srgb, var(--sky) 74%, transparent) 42%,
    transparent 100%);
}
/* ⛔ MANDATORY IS SAFE ONLY WHILE A PANEL FITS. If a beat ever grows past the
   viewport, mandatory snapping can make its lower half unreachable. That is why
   .canvas-panel is min-height rather than height — an overgrown panel stays
   scrollable — and why _test-beats.cjs measures every panel against the
   viewport at three sizes and fails the build rather than trusting this note. */

/* ⛔ THE PER-PANEL SNAP POINTS ARE RETIRED with the container above.
   `scroll-snap-align: start` and `scroll-snap-stop: always` were on
   .canvas-panel, and `scroll-snap-align: start` on .site-foot.
   A beat is still exactly one viewport tall and _test-beats still proves it at
   three widths — that was never a snap requirement, it is what makes a beat
   readable without scrolling inside it. */

/* ── THE BEACON ────────────────────────────────────────────────────────────
   Bottom centre: out of the two corners the wordmark and the rail already own,
   and on the axis the reader's eye returns to between beats. */
.beacon {
  position: fixed;
  inset-block-end: clamp(1rem, 2.6vh, 2rem);
  inset-inline-start: 50%;
  translate: -50% 0;
  z-index: 88;                    /* under the rail's 90, over the canvas */
  display: flex;
  align-items: center;
  gap: clamp(.55rem, 1.4vw, .9rem);
  padding: .5rem .85rem;
  border-radius: 999px;
  /* No card. The same rule the rail follows: legibility from a shadow that
     follows the shape, never from a box drawn behind it. */
  filter: drop-shadow(0 1px 3px rgba(0,0,0,.9)) drop-shadow(0 0 12px rgba(0,0,0,.65));
}

.beacon-dot {
  position: relative;
  display: grid;
  place-items: center;
  inline-size: 1.5rem;
  block-size: 1.5rem;             /* a real 24px target, not a 6px dot */
  border-radius: 50%;
  text-decoration: none;
  color: var(--star);
}
/* The visible mark is small; the hit area is not. */
.beacon-dot::after {
  content: '';
  inline-size: .42rem;
  block-size: .42rem;
  border-radius: 50%;
  background: color-mix(in srgb, var(--star) 42%, transparent);
  transition: background var(--t-hover, 180ms) var(--e-out, ease),
              scale var(--t-hover, 180ms) var(--e-out, ease),
              box-shadow var(--t-hover, 180ms) var(--e-out, ease);
}
.beacon-dot:hover::after,
.beacon-dot:focus-visible::after { background: var(--star); scale: 1.25; }
.beacon-dot:focus-visible { outline: var(--hair-strong) solid var(--waterline); outline-offset: 2px; }

/* THE LIT BEAT. Same treatment as a star on the landing map — a bright core
   with a faint halo — so the navigator reads as part of the same figure rather
   than as a carousel widget borrowed from somewhere else. */
.beacon-dot.is-on::after {
  background: var(--star);
  scale: 1.5;
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--star) 16%, transparent),
              0 0 10px 2px color-mix(in srgb, var(--star) 34%, transparent);
}

/* The label appears on hover or focus, above the dot, so the navigator can be
   used deliberately rather than by counting positions. */
.beacon-label {
  position: absolute;
  inset-block-end: 145%;
  inset-inline-start: 50%;
  translate: -50% 0;
  white-space: nowrap;
  font-family: var(--f-data);
  font-size: var(--m-tick);
  letter-spacing: .04em;
  color: var(--star);
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--t-hover, 180ms) var(--e-out, ease);
}
.beacon-dot:hover .beacon-label,
.beacon-dot:focus-visible .beacon-label { opacity: 1; }

/* ── NARROW ────────────────────────────────────────────────────────────────
   The rail sits bottom-right and the wordmark bottom-left; on a phone the
   beacon has to clear both, so it moves up rather than shrinking into them. */
@media (max-width: 700px) {
  /* ⛔ THE GAP AND PADDING MUST BE FLUID, BECAUSE THE DOT COUNT IS NOT FIXED (CP-R1605qh-S).
     Moving the beacon to the top at narrow widths is deliberate. What was not deliberate is that
     it did not FIT: measured at 390x844 on /journey the pill rendered 398px wide starting at
     x=-4 — 8px wider than the viewport, hanging off both edges. That is the "row of dots across
     the top" in the founder's iPhone screenshot.
     The cause is a FIXED gap against a VARIABLE beat count. /journey carries 13 dots; every other
     page carries 5-7, which is why this page and only this page overflowed — a defect that hides
     on 20 of 21 pages. Scaling gap and padding with the viewport fits the worst case now and any
     longer page later, which a hand-tuned value for 13 would not. */
  .beacon {
    inset-block-end: auto;
    inset-block-start: clamp(.75rem, 2vh, 1.25rem);
    gap: clamp(.14rem, 1.05vw, .35rem);
    padding: .35rem clamp(.3rem, 1.5vw, .6rem);
    max-width: calc(100vw - 1.25rem);
  }

  /* ⛔ THE SCRIM DID NOT FOLLOW THE BEACON WHEN THE BEACON MOVED (CP-R1605qr-S).
     The rule above deliberately relocates the beacon to the TOP at narrow widths. The scrim that
     exists precisely so fixed chrome does not strike through prose - `html.beats body::after`,
     twenty lines up - is anchored `inset-block-end: 0`. It stayed at the BOTTOM. So on a phone the
     beacon sits over live text with nothing behind it, which is the founder's "dots painting over
     the body text", and it is not a new defect class: it is the SAME shape as the dead
     max-width:700px footer breakpoint, one rule moving while its partner keeps the old scope.
     Measured on the LIVE site at 390x844 on /journey, scrollY 600: 18 of 39 dots had body text
     directly beneath them, and the dot row was painted straight across the headline "every hand it
     passes through".
     ⭐ NOTE WHAT THIS IS NOT. It is not a reversal of the surfaceless-chrome decision, and it
     needed no design ruling. That decision already prescribes A SCRIM RATHER THAN A CARD for
     exactly this situation and the site already ships one; it was simply in the place the beacon
     used to be. Same mechanism, same tokens, same 7.5rem depth, mirrored to where the chrome
     actually is. The photography still shows through it.
     ⚠ THE BOTTOM SCRIM STAYS AT THIS WIDTH TOO - the wordmark and the menu are still down
     there, and the same screenshot shows them struck through the prose. Two pieces of chrome, two
     edges, one rule each. */
  html.beats body::before {
    content: ""; position: fixed; z-index: 87; pointer-events: none;
    inset-inline: 0; inset-block-start: 0; height: 7.5rem;
    background: linear-gradient(to bottom,
      color-mix(in srgb, var(--sky) 92%, transparent) 0%,
      color-mix(in srgb, var(--sky) 74%, transparent) 42%,
      transparent 100%);
  }
  .beacon-label { inset-block-end: auto; inset-block-start: 145%; }
}

/* ── REDUCED MOTION ────────────────────────────────────────────────────────
   ⛔ SNAP STAYS ON. It is not motion — it is where a scroll comes to rest, and
   turning it off would hand the readers most likely to need a short, decisive
   page the 2.2-screens-per-beat version instead. What is suppressed is the
   scale change on the dots, which is movement. */
@media (prefers-reduced-motion: reduce) {
  .beacon-dot::after { transition: background 1ms; }
  .beacon-dot:hover::after,
  .beacon-dot:focus-visible::after,
  .beacon-dot.is-on::after { scale: 1; }
}

/* ── PRINT ─────────────────────────────────────────────────────────────────
   A navigator for a deck means nothing on paper. */
@media print { .beacon { display: none; } }

/* THE FOOTER CLEARS THE DOTS. This lives HERE, not in layout.css, because the
   beacon is the reason it is needed and beacon.css is loaded only on pages
   that have beats. In layout.css it was global, which put dead space under
   the footer of every page WITHOUT dots - including /contact, where it read
   as a formatting fault rather than as clearance. */
.site-foot { padding-bottom: 4.5rem; }
