/* ============================================================================
   MUGGER CITY — MOTION LAYER
   Phase 03 · interaction, motion & experience
   ----------------------------------------------------------------------------
   Load order:  tokens.css → style.css → premium.css → motion.css

   Six interactions, each earning its place:
     1  hero descent      — the surface gives way as you go under
     2  river field map   — the survey line draws itself, bend by bend
     3  specimen reveal   — the mugger is uncovered, not inserted
     4  monsoon rise      — the water comes up as you pass through
     5  field records     — one record at a time, with continuity
     6  river progress    — where you are in the journey

   Every one of them is progressive enhancement. With JS off, CSS unsupported,
   or reduced motion on, the page is the finished Phase 02 editorial site.
   The end of this file turns all of it off for prefers-reduced-motion.
   ========================================================================== */


/* ============================================================================
   1 · HERO — DELIBERATELY UNTOUCHED
   ----------------------------------------------------------------------------
   Phase 03 added a scroll-linked "descent" here: the photograph scaled and
   drifted, a second scrim closed over the frame, and the copy faded out.

   It has been removed in Phase 04. The hero sliding cards are a locked
   component and that was a new visual effect applied to them. It also broke
   something concrete: the original cards run a slow Ken Burns zoom
   (@keyframes heroKen) on .hero-cx__img, and setting `transform` on the same
   element overrode that animation outright. Removing the descent restores it.

   The other five Phase 03 interactions — river map, specimen reveal, monsoon
   rise, field records, river progress — are unaffected and remain in place.
   ========================================================================== */


/* ============================================================================
   2 · RIVER FIELD MAP
   The signature interaction. The line is traced the way a cartographer draws
   it, and each field point arrives only once the water has reached it.
   --draw is written by motion.js (0 → 1 across the section's scroll range).
   ========================================================================== */

.rv-map {
  --draw: 1;               /* fully drawn by default — JS takes over if able */
  margin: 0;
  align-self: center;
}

.rv-map__svg {
  display: block;
  width: 100%;
  height: auto;
  overflow: visible;
}

/* the dry bed: always present, so the map is legible before anything moves */
.rv-map__bed {
  fill: none;
  stroke: var(--color-rule);
  stroke-width: 1.5;
  stroke-dasharray: 3 5;
  stroke-linecap: round;
}

/* the water itself */
.rv-map__line {
  fill: none;
  stroke: var(--color-accent-text);
  stroke-width: 2.25;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.js-motion .rv-map__line {
  stroke-dasharray: var(--len, 1600);
  stroke-dashoffset: calc(var(--len, 1600) * (1 - var(--draw)));
}

/* field points — a survey pin, its label, and a hairline tying them together */
.rv-pt circle {
  fill: var(--color-bg-paper);
  stroke: var(--color-accent-text);
  stroke-width: 2;
}

.rv-pt text {
  font-family: var(--font-data);
  font-size: 11px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  fill: var(--color-text-muted);
}

/* Each point waits its turn — it arrives when the traced line reaches it.
   Which points are due is decided in motion.js rather than with calc() on
   custom properties: unregistered properties are substituted as tokens, and
   nesting them inside clamp() proved unreliable across the reveal range.

   The hidden state is scoped to .is-driven, which motion.js only adds from
   inside a frame that actually ran. If requestAnimationFrame is throttled —
   background tab, reduced-power mode, an inert renderer — the labels are
   simply there. Place names are content; they must never be withheld by an
   animation that failed to start. */
.js-motion .rv-map.is-driven .rv-pt {
  opacity: 0;
  transition: opacity var(--duration-medium) var(--ease-exit);
}
.js-motion .rv-map.is-driven .rv-pt.is-on { opacity: 1; }

/* SVG text scales with the viewBox, so at phone widths an 11-unit label
   renders at about 6px — unreadable. The whole annotation layer is scaled up
   on small screens to hold a legible size against the shrinking chart. */
/* The map shares its row with the headline on the homepage, so it renders
   narrower than its 640-unit viewBox well before the phone breakpoints. Step
   the annotation layer up once on the way down. */
@media (max-width: 1150px) {
  .rv-pt text { font-size: 13px; letter-spacing: 0.11em; }
  .rv-pt circle { r: 6; stroke-width: 2.25; }
  .rv-map__line { stroke-width: 2.75; }
  .rv-map__bed { stroke-width: 1.9; stroke-dasharray: 4 6; }
}
@media (max-width: 900px) {
  .rv-pt text { font-size: 17px; letter-spacing: 0.08em; }
  .rv-pt circle { r: 7; stroke-width: 2.5; }
  .rv-map__line { stroke-width: 3.25; }
  .rv-map__bed { stroke-width: 2.25; stroke-dasharray: 5 7; }
}
@media (max-width: 560px) {
  .rv-pt text { font-size: 21px; letter-spacing: 0.06em; }
  .rv-pt circle { r: 9; stroke-width: 3; }
  .rv-map__line { stroke-width: 4; }
  .rv-map__bed { stroke-width: 2.75; stroke-dasharray: 6 8; }
}

.rv-map__cap {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
  margin-top: var(--space-4);
  padding-top: var(--space-3);
  border-top: var(--rule);
  font-family: var(--font-data);
  font-size: 11px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--color-text-faint);
}

.rv-map__scale b {
  font-family: var(--font-display);
  font-size: 1.7rem;
  font-weight: 400;
  letter-spacing: 0;
  color: var(--color-text-primary);
  margin-right: 0.15em;
}

/* the photograph beside the map, on the Phase 02 plate treatment */
.rv-plate {
  width: 100%;
  height: 100%;
  min-height: 320px;
  object-fit: cover;
  border-radius: var(--radius-sm);
  border: var(--rule);
  filter: var(--img-treatment);
}


/* ============================================================================
   3 · SPECIMEN REVEAL
   The mugger should read as uncovered rather than placed. A single vertical
   wipe on the plate — the water drawing back — then the vitals arrive in
   sequence. clip-path animates on the compositor; no layout is touched.
   ========================================================================== */




/* ============================================================================
   4 · MONSOON RISE
   The one section where the environment itself should change. No particles,
   no DOM rain, no video: the photograph drifts, the water line comes up the
   frame, and the whole thing darkens as you pass through it.
   --rise is written by motion.js (0 → 1 across the section).
   ========================================================================== */

.px-monsoon { --rise: 0; }

.js-motion .px-monsoon__bg {
  will-change: transform;
  transform: scale(calc(1.04 + var(--rise) * 0.05)) translate3d(0, calc(var(--rise) * -3vh), 0);
}

/* the river coming up the frame — a single gradient element, GPU-composited */
.js-motion .px-monsoon__veil::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(0deg,
              rgba(3, 14, 8, 0.92) 0%,
              rgba(5, 28, 18, 0.55) 45%,
              rgba(5, 28, 18, 0) 100%);
  transform: translate3d(0, calc((1 - var(--rise)) * 60%), 0);
  opacity: calc(0.35 + var(--rise) * 0.65);
  pointer-events: none;
}


/* ============================================================================
   5 · FIELD RECORDS
   The brief's ask: one record active at a time, with continuity — not a row of
   floating cards. The rail already scrolls; motion.js marks whichever record
   is nearest the reading position and reports it in the counter.
   ========================================================================== */

.js-motion .px-card {
  transition: opacity var(--duration-medium) var(--ease-standard),
              border-color var(--duration-normal) var(--ease-standard);
}

/* recessive, not hidden — you can always read ahead */
.js-motion .px-deck__rail.is-tracking .px-card { opacity: 0.55; }
.js-motion .px-deck__rail.is-tracking .px-card.is-active {
  opacity: 1;
  border-top-color: var(--color-accent-text);
}

/* position readout — replaces the generic "drag, swipe" nudge once you move */
.px-deck__count {
  font-family: var(--font-data);
  font-size: 11px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--color-text-faint);
  white-space: nowrap;
}
.px-deck__count b {
  font-weight: 500;
  color: var(--color-accent-text);
}


/* ============================================================================
   6 · RIVER PROGRESS
   A scroll indicator that belongs to this site: a thin length of river down the
   edge of the screen that fills as you travel. Decorative and inert — the real
   position information is the page itself.
   ========================================================================== */

.rv-progress {
  position: fixed;
  top: 0;
  left: 0;
  width: 2px;
  height: 100vh;
  z-index: 40;
  background: var(--color-rule);
  pointer-events: none;
  opacity: 0;
  transition: opacity var(--duration-slow) var(--ease-standard);
}
.js-motion .rv-progress.is-on { opacity: 1; }

.rv-progress__fill {
  position: absolute;
  inset: 0 0 auto 0;
  height: 100%;
  background: linear-gradient(180deg, var(--color-accent) 0%, var(--color-accent-text) 100%);
  transform: scaleY(var(--p, 0));
  transform-origin: top;
}

@media (max-width: 768px) {
  .rv-progress { display: none; }   /* the phone has its own scrollbar */
}


/* ============================================================================
   RESTRAINT — quiet the ambient loops the page inherited
   The dossier hint arrow pulsed forever next to a static heading. Ambient
   motion should be rare and slow; a 1.8s loop in the corner of the eye is not
   atmosphere, it is a tic. It now nudges only while the rail can be scrolled.
   ========================================================================== */

.px-deck__hint::after { animation: none; }
.js-motion .px-deck__hint.is-idle::after {
  animation: pxNudge 2.6s var(--ease-drift) infinite;
}


/* ============================================================================
   REDUCED MOTION
   Everything above is switched off, not merely shortened. The page returns to
   the Phase 02 static composition with every element in its final state.
   tokens.css already zeroes durations globally; this restores end states so
   nothing is left mid-animation or invisible.
   ========================================================================== */

@media (prefers-reduced-motion: reduce) {
  /* The hero is not listed here: motion.css no longer touches it, and its own
     reduced-motion handling lives with the component in premium.css. */
  .js-motion .px-monsoon__bg {
    transform: none !important;
    opacity: 1 !important;
  }
  .js-motion .px-monsoon__veil::after { transform: none !important; opacity: 0.6 !important; }

  .js-motion .rv-map__line { stroke-dasharray: none !important; stroke-dashoffset: 0 !important; }
  .js-motion .rv-pt { opacity: 1 !important; }


  .js-motion .px-deck__rail.is-tracking .px-card { opacity: 1 !important; }
  .rv-progress { display: none !important; }
  .px-deck__hint::after { animation: none !important; }
}

/* ============================================================================
   CARD FOCUS — click a card, it comes to the centre over a blurred page
   ----------------------------------------------------------------------------
   Applies to the informational cards across the site: field records, the day
   on the river, the 12 facts, field sources, rescue steps, status cards.
   Deliberately NOT applied to anything that navigates (products, next-chapter
   cards) or that already has its own behaviour (the myth accordion, the
   specimen notes on the animal plate).

   The opened card is a clone. The original stays in the layout, so nothing
   reflows and the page behind is exactly where the reader left it.
   ========================================================================== */

.cxp-backdrop {
  position: fixed;
  inset: 0;
  z-index: 300;
  background: rgba(3, 14, 8, 0.52);
  -webkit-backdrop-filter: blur(14px) saturate(0.92);
  backdrop-filter: blur(14px) saturate(0.92);
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--duration-medium) var(--ease-standard);
}
.cxp-backdrop.is-on { opacity: 1; pointer-events: auto; }

.cxp-stage {
  position: fixed;
  inset: 0;
  z-index: 301;
  display: grid;
  place-items: center;
  padding: clamp(16px, 5vh, 56px) clamp(16px, 5vw, 56px);
  pointer-events: none;
}

.cxp-clone {
  pointer-events: auto;
  width: min(620px, 92vw);
  max-height: 86vh;
  overflow-y: auto;
  margin: 0;
  border-radius: var(--radius-sm);
  box-shadow: 0 30px 90px -20px rgba(2, 12, 7, 0.7);
  transform-origin: center center;
  will-change: transform;
  /* the clone keeps its source component's classes, some of which declare a
     transition at higher specificity — the movement is driven by the Web
     Animations API instead, so no transition is declared here at all */
  /* neutralise layout roles the card had in its own container */
  position: relative;
  flex: none;
  scroll-snap-align: none;
  cursor: zoom-out;
}
/* the deck's records carry a heavy top rule — keep it, drop the drag cursor */
.cxp-clone.px-card { padding: var(--space-6); }
.cxp-clone.px-clock__cell,
.cxp-clone.rs-step,
.cxp-clone.px-source,
.cxp-clone.fn-fact { padding: var(--space-6); }

/* the source card, while its clone is out */
.cxp-source-hidden { visibility: hidden; }

/* an affordance that the cards can be opened at all */
.cxp-able { cursor: zoom-in; }
.cxp-able:focus-visible { outline: var(--focus-ring); outline-offset: var(--focus-offset); }

.cxp-hint {
  position: fixed;
  left: 50%;
  bottom: clamp(14px, 3vh, 30px);
  transform: translateX(-50%);
  z-index: 302;
  font-family: var(--font-data);
  font-size: 10.5px;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: rgba(242, 237, 223, 0.72);
  pointer-events: none;
  opacity: 0;
  transition: opacity var(--duration-medium) var(--ease-standard) 0.25s;
}
.cxp-hint.is-on { opacity: 1; }

@media (prefers-reduced-motion: reduce) {
  .cxp-clone { transition: none !important; }
  .cxp-backdrop { transition: none !important; }
}
