/* World-map hero — worldwide-home masthead.
 * Soft dotted world map (canvas) behind an editorial masthead; markets light up;
 * a four-step journey sits in the bottom-right quadrant. Tokens only (see
 * tokens.css); teal (--im-accent-200) is the accent, reserved for the lights,
 * links, the headline underline and the journey's drawn path. Companion:
 * worldmap_hero.js.
 *
 * Layout: the hero is a full-screen COLUMN (min-height:100svh) — the copy block
 * grows to fill (flex:1), the steps are their own height and the client-logo
 * marquee is the last child, so it always sits at the bottom of the first screen
 * on every size, desktop and mobile, with no absolute positioning to go wrong.
 * The nav floats over the top of the hero (the hero starts at viewport top), so
 * the map is lifted to fill behind it. The copy sits lower than a masthead
 * usually does, on purpose: with the steps bottom-right, a high headline opens a
 * void in the lower-left bounded by content on three sides. Dropped, the same
 * space becomes a corner the marquee closes off, which reads as ground.
 *
 * Five tiers on two independent axes. WIDTH decides the composition:
 *   ≥981px   copy left, four steps bottom-right, marquee plinth.
 *   ≤980px   the same column, stacked, steps in a 2x2, copy CENTRED.
 *   ≤600px   phone metrics — tighter gutters, shorter type ramp.
 * HEIGHT decides what survives, and applies at every width:
 *   ≤760px   compact — tighter nav clearance and gutters.
 *   ≤600px   the full-screen claim goes; the steps stay, because they are
 *            content now and hiding them loses the journey at 400% zoom.
 * Keying the height tiers to width as well was the first cut's mistake: it left
 * out every zoomed-in desktop, and a 1440x900 laptop at 175% zoom is an 823x514
 * viewport that hits exactly the wall a landscape phone does.
 *
 * The single 980px cliff this replaced gave a 360px phone and a 768px tablet the
 * identical 22px gutter and 74px nav clearance, and let the copy's flex-grow pool
 * every leftover pixel into ONE void below the credential strip — 208px on a
 * phone, 432px on a tablet. Clearance now comes from --im-nav-h (86px, 64px on
 * phones) instead of a 74px guess that tucked the headline under the bar at 768. */

.wm-hero {
  position: relative;
  min-height: 100svh;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  overflow: hidden;
  background: var(--im-paper);
}
/* The canvases themselves live in assets/css/mvp/map-canvas.css, because the map
   is a block four other heroes host too — see parts/blocks/map_canvas.html. What
   stays here is only what is specific to THIS hero. */

/* the copy block — grows to fill so the marquee falls to the bottom. Centred in
   that space rather than pinned to the top, which is what puts the headline at
   the same optical height whatever the viewport is. */
.wm-hero__wrap {
  position: relative; z-index: 4; flex: 1 1 auto;
  width: 100%; max-width: var(--im-wrap-max); margin: 0 auto;
  padding: calc(var(--im-nav-h) + var(--im-space-10)) var(--im-space-12) var(--im-space-10);
  display: flex; flex-direction: column; justify-content: center; align-items: flex-start;
  pointer-events: none;
}
.wm-hero__wrap a, .wm-hero__wrap button { pointer-events: auto; }

/* left copy — the map fades gradually as it nears the words */
.wm-hero__copy { max-width: 560px; position: relative; }
.wm-hero__copy::before {
  content: ""; position: absolute; inset: -40px -80px -40px -48px; z-index: -1;
  background: radial-gradient(125% 125% at 16% 50%,
    rgba(252, 251, 247, .8) 24%, rgba(252, 251, 247, .5) 48%,
    rgba(252, 251, 247, .2) 70%, rgba(252, 251, 247, 0) 92%);
}
/* The floor is 42px, not 36px, and it is set by the tier BELOW it rather than by
   this one. The ramp has to be continuous through both breakpoints — a headline
   that steps 42→38 or 46→36 because the window moved one pixel reads as a bug
   however well each side is tuned alone — and the binding end is the tablet,
   which sets copy across the full width and wants 42px. Desktop confines the copy
   to a 560px column, so 42px is its floor too and 3.5vw only takes over at
   1200px. See the stacked tier for the other two segments. */
.wm-hero__title {
  font: 400 clamp(42px, 3.5vw, 50px)/1.08 var(--im-font-serif);
  letter-spacing: -.035em; margin: 0 0 18px; color: var(--im-ink);
}
.wm-hero__title .wm-l1 { white-space: nowrap; }
.wm-hero__title u { text-decoration: none; border-bottom: 3px solid var(--im-accent-200); padding-bottom: .02em; }
.wm-hero__lede {
  font: 400 17.5px/1.5 var(--im-font-display); color: var(--im-ink-soft); margin: 0 0 24px; max-width: 42ch;
}
.wm-hero__ctas { display: flex; gap: 12px; margin: 0 0 22px; flex-wrap: wrap; }
.wm-hero__creds { display: flex; flex-wrap: wrap; gap: 8px 20px; margin: 0; padding: 0; list-style: none; }
.wm-hero__creds li {
  font: 600 10.5px/1.3 var(--im-font-display); letter-spacing: .1em; text-transform: uppercase;
  color: var(--im-text-muted);
}

/* the four-step journey, bottom-right quadrant.
   This replaced a 274px full-width rail carrying a card that cycled six panes on
   a 2.8s timer. The height that block needed was derived from three stacked
   offsets and guarded by a manual browser check (scripts/check_hero_layout.py)
   because the card overhung its own container and could reach the copy on a
   short viewport. None of that arithmetic survives: the steps are flow content,
   four grid columns of text, so the block is exactly as tall as its tallest
   column and cannot collide with anything above it by construction.

   Right-aligned rather than full-width so the lit market band (roughly 30-38% of
   viewport height) stays clear — these sit at 65-83%. Capped at 760px because
   four columns of a 40-char note want ~180px each; wider and the notes go
   single-line and the row reads as a table. */
.wm-hero__steps {
  position: relative; z-index: 4; flex: 0 0 auto;
  width: 100%; max-width: var(--im-wrap-max); margin: 0 auto;
  padding: 0 var(--im-space-12) var(--im-space-12);
  display: flex; justify-content: flex-end;
}
.wm-steps {
  position: relative;
  display: grid; grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--im-space-6);
  width: min(100%, 760px); margin: 0; padding: 0; list-style: none;
}
/* the same local paper wash the copy column uses, at the strength the tablet
   tier uses there: enough that 12.5px sans holds against the dot field, soft
   enough that the map still reads through it */
.wm-steps::before {
  content: ""; position: absolute; inset: -26px -30px -22px -30px; z-index: -1;
  background: radial-gradient(112% 108% at 50% 46%,
    rgba(252, 251, 247, .74) 26%, rgba(252, 251, 247, .48) 60%,
    rgba(252, 251, 247, .16) 84%, rgba(252, 251, 247, 0));
}
/* The connector runs from the FIRST node to the LAST, not across the full row.
   It used to end at the right edge of the last column, which put 172px of line
   past the final node — a journey trailing off after its own last stop. It also
   made the node positions unintuitive: at 0 / 25.8 / 51.6 / 77.4% of the row,
   they are not the even thirds they look like, and timing the flair to thirds
   (which I did) lit each node progressively later than the line reached it, by
   0.21s, 0.43s and 0.63s down the row.

   Ending the fill at the last node fixes both at once: across a node-1-to-node-4
   span the four nodes ARE at 0, 1/3, 2/3 and 1, because the columns are equal
   and the gaps are equal. The sync stops being arithmetic that has to be
   maintained and becomes a property of the geometry.

   Three layers share this hairline and the order between them is load-bearing:
   grey track (0) UNDER the teal fill (1) UNDER the nodes (2).

   `.wm-step` must NOT carry a z-index. It did, briefly, to lift the nodes above
   the fill — but a z-index on the step makes it a stacking context, which takes
   its ::before down with it, and the grey track then painted OVER the teal bar
   and hid it completely. The line never turned teal at all; only the nodes did.
   The nodes are absolutely positioned in their own right, so they can be lifted
   directly and the step can stay a plain positioning parent. */
.wm-step { position: relative; padding-top: var(--im-space-5); }
.wm-step::before {
  content: ""; position: absolute; z-index: 0; top: 0; left: 0;
  right: calc(var(--im-space-6) * -1); height: 1px; background: var(--im-stone-300);
}
/* no segment after the last node: the track ends where the journey does */
.wm-step:last-child::before { display: none; }
.wm-step::after {
  content: ""; position: absolute; z-index: 2; top: -3.5px; left: 0; width: 8px; height: 8px;
  border-radius: 50%; background: var(--im-paper); border: 1.6px solid var(--im-stone-300);
}
/* the entry point is the only accent. It replaces the travelling marker: with
   nothing moving, the reader still needs one cue for where the sequence starts,
   and numbering four steps adds weight to a block meant to stay quiet. */
.wm-step:first-child::after { background: var(--im-accent-200); border-color: var(--im-accent-200); }

/* Progress fill — draws ONCE, then rests full. It is not the old rail's fill
   brought back: that one was JS-driven, looped forever on a 2.8s timer and had
   no pause control, which is the WCAG 2.2.2 problem this hero was carrying. A
   single draw on load is not "moving content presented in parallel" at all, so
   it needs no pause affordance, no timer and no JavaScript — `forwards` holds
   the end state and nothing runs after that. The duration lives in
   --wm-draw-dur beside the arrival times it has to agree with; it went 1.6 to
   2.8 to 4s as this was tuned, and at the short end it read as a loading bar
   clearing rather than as a path being walked. One-shot either way, so the 5s
   that would engage 2.2.2 on repeating motion is not the constraint.

   The drawn path is teal, the undrawn track stays stone. That extends the accent
   past the roles the header names (lights, links, headline underline) and it is
   deliberate: the first node was already accent-200 as the entry mark, so a path
   drawn in any other colour would arrive at its own start point and change
   colour. One path, one colour.

   scaleX on a 1px bar rather than an animated width or a gradient stop: it is
   compositor-only, and a custom property in a gradient stop would need @property
   registration to transition at all. */
/* LINEAR, and that is the whole point rather than a default left in place.
   cubic-bezier(.4, 0, .2, 1) was here first: it is the standard UI curve for an
   object travelling from A to B, so it accelerated through the middle and
   crawled at the end. Liquid running down a channel does not do that, it flows
   at a rate. Measured on the old curve the line crossed nodes 2 and 3 within
   0.39s of each other and then took a further 1.6s to reach node 4 — a race
   followed by a stall, which is what read as "not liquid". */
.wm-steps::after {
  content: ""; position: absolute; top: 0; left: 0; height: 1px; z-index: 1;
  /* stop at the last node: one column short of the row's right edge */
  right: calc((100% - 3 * var(--im-space-6)) / 4);
  background: var(--im-accent-200);
  transform: scaleX(0); transform-origin: left center;
  animation: wm-draw var(--wm-draw-dur) linear var(--wm-draw-start) forwards;
}
@keyframes wm-draw { to { transform: scaleX(1); } }

/* ONE delay per step, shared by everything that fires when the line arrives.
   The line and its flair went out of sync because they were timed separately
   against a span they did not actually cover; keeping the arrival time in a
   single custom property per step makes a repeat structurally impossible. The
   numbers are start + duration x (0, 1/3, 2/3, 1), which is exact now that the
   fill spans node 1 to node 4 and the draw is linear. */
.wm-steps { --wm-draw-start: .25s; --wm-draw-dur: 4s; }
.wm-step                { --wm-at: .25s; }   /* the origin, when the flow starts */
.wm-step:nth-child(2)   { --wm-at: 1.58s; }
.wm-step:nth-child(3)   { --wm-at: 2.92s; }
.wm-step:nth-child(4)   { --wm-at: 4.25s; }

/* the node: colour only, no pop. The .5s fill is deliberately far slower than
   the ~0.03s the line takes to cross an 8px dot, so the teal settles behind the
   flow rather than snapping on. Node 1 is already teal, so for it this is a
   no-op — it is the entry mark and is lit before anything moves. */
.wm-step::after { animation: wm-node-on .5s ease var(--wm-at) forwards; }
@keyframes wm-node-on {
  to { background: var(--im-accent-200); border-color: var(--im-accent-200); }
}

/* The flair is the content arriving, not an ornament on the marker. As the line
   reaches a node, that step's label and note lift and settle. An expanding ring
   lived here first and was dropped: it was app-notification language on a page
   whose whole register is editorial, and it decorated a dot rather than doing
   any work.

   Base opacity is .5, NOT 0. Step 4 does not light until 4.25s, and hiding real
   copy on a masthead for four seconds to buy an animation is a bad trade — a
   reader who arrives mid-sequence must find four legible steps, not two. At .5
   the text is readable throughout and the settle still reads as arrival.

   Nothing here is hidden from assistive technology or from a crawler: it is the
   same DOM either way, only opacity and transform move, and both end resolved. */
.wm-step__lab,
.wm-step__note {
  opacity: .5; transform: translateY(5px);
  animation: wm-step-in .65s cubic-bezier(.22, 1, .36, 1) var(--wm-at) forwards;
}
/* the note trails the label by a beat, so the step resolves top-down */
.wm-step__note { animation-delay: calc(var(--wm-at) + .09s); }
@keyframes wm-step-in { to { opacity: 1; transform: none; } }
@media (prefers-reduced-motion: reduce) {
  /* the same end state, arrived at instantly */
  .wm-steps::after { animation: none; transform: scaleX(1); }
  .wm-step::after {
    animation: none; background: var(--im-accent-200); border-color: var(--im-accent-200);
  }
  .wm-step__lab,
  .wm-step__note { animation: none; opacity: 1; transform: none; }
}
.wm-step__lab {
  display: block; margin-bottom: 6px;
  font: 600 11px/1.3 var(--im-font-display); letter-spacing: .1em; text-transform: uppercase;
  color: var(--im-ink);
}
.wm-step__note { display: block; font: 400 12.5px/1.45 var(--im-font-display); color: var(--im-ink-soft); }

/* client-logo marquee — the hero's bottom plinth (last flex child) */
.wm-hero__base { position: relative; z-index: 4; width: 100%; }

/* ── stacked tier (≤980px) ───────────────────────────────────────────────────
   The copy stacks, the steps go full width in two columns, and the marquee stays
   at the very bottom (still the last flex child of the full-screen column). The
   copy stays CENTRED — it is the same justify-content:center as the desktop
   tier, so the headline lands at 20-29% of viewport height on a phone and a
   tablet against a laptop's 20-27%, instead of at 7-9% with the slack dumped in
   one void underneath. The map scrim lifts too: the old .5→.72 paper wash left
   the map itself invisible, and the words are now meant to sit on it rather than
   above it.

   The steps used to collapse here to six progress dots plus one cycling card,
   which meant a phone showed a row of unlabelled dots and one sixth of the
   journey at a time. Two columns of four is both shorter and complete. */
@media (max-width: 980px) {
  .wm-hero__wrap {
    align-items: stretch;
    padding: calc(var(--im-nav-h) + var(--im-space-6)) var(--im-space-6) var(--im-space-5);
  }
  .wm-hero::after {
    content: ""; position: absolute; inset: 0; z-index: 3; pointer-events: none;
    background: linear-gradient(180deg, rgba(252, 251, 247, .22), rgba(252, 251, 247, .42));
  }
  .wm-hero__copy { max-width: none; }
  /* The same local wash the desktop tier uses, at about two thirds the strength.
     The full-strength version bleached the map out of the area it is meant to
     occupy; removing it entirely left the lede — 16.5px sans, --im-ink-soft, much
     lighter than the serif headline — competing with the dot field. So: soft
     enough that the map still reads through it, opaque enough under the lede. */
  .wm-hero__copy::before {
    inset: -20px -22px;
    background: radial-gradient(118% 92% at 28% 46%,
      rgba(252, 251, 247, .70) 22%, rgba(252, 251, 247, .46) 58%,
      rgba(252, 251, 247, .16) 82%, rgba(252, 251, 247, 0));
  }
  /* flat: this tier only governs 601-980px, and it is the segment both other
     tiers are pinned to — the phone ramp tops out here and the desktop floor
     starts here, so all three meet at 42px with no step at either breakpoint */
  .wm-hero__title { font-size: 42px; }
  .wm-hero__title .wm-l1 { white-space: normal; }
  /* the desktop measure has to be restated, not dropped: unbounded, the lede ran
     724px wide at 768 and 800px in phone landscape — 110 characters a line */
  .wm-hero__lede { max-width: 46ch; }
  .wm-hero__steps { padding: 0 var(--im-space-6) var(--im-space-6); max-width: none; }
  .wm-steps { width: 100%; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--im-space-5) var(--im-space-6); }
  /* Two columns means the connector would have to bridge a row gap to stay one
     line, which it cannot do honestly. Each step keeps its own node and its own
     short rule instead — the reading order carries the sequence here, not a
     drawn path. */
  /* every step keeps its own short rule here, including the last: the
     ends-at-the-final-node shaping is a property of the single-row layout and
     there is no continuous line to terminate on a 2x2 grid. */
  .wm-step::before { right: 0; }
  .wm-step:last-child::before { display: block; right: 0; }
  .wm-steps::before { inset: -18px -20px; }
  /* and the progress fill goes with it: one bar spanning the top of a two-row
     grid would draw across row one and stop, which reads as the journey ending
     at step two. The nodes still light in order and still ping, so the sequence
     and the flair both survive. */
  .wm-steps::after { display: none; }
}

/* ── phone tier (≤600px) ─────────────────────────────────────────────────────
   Narrower gutters and a type ramp that tracks the viewport rather than the
   tablet clamp. Split out of the 980px branch because a 360px phone and a 768px
   tablet were sharing one set of metrics. */
@media (max-width: 600px) {
  .wm-hero__wrap { padding-left: var(--im-space-5); padding-right: var(--im-space-5); }
  .wm-hero__title { font-size: clamp(32px, 9.2vw, 42px); }
  .wm-hero__lede { font-size: 16.5px; }
  .wm-hero__steps { padding-left: var(--im-space-5); padding-right: var(--im-space-5); }
  .wm-hero__creds { gap: 6px 14px; }
  .wm-step__note { font-size: 12px; }
}

/* ── short viewports ─────────────────────────────────────────────────────────
   This is a VERTICAL budget and the queries are height-only on purpose. Nav
   clearance + copy + steps + plinth needs about 620px, and a 1440x900 laptop at
   175% browser zoom is an 823x514 viewport — it hits exactly the wall a
   landscape phone does, so keying these to width as well (as the first cut did)
   left every zoomed-in desktop out of the fix.

   The budget dropped from ~705px because the block that used to sit here was a
   274px rail carrying a 188px card. Four columns of text are ~90px, so this tier
   now only has to tighten the gutters rather than shrink a card that could
   otherwise reach the copy. */
@media (max-height: 760px) {
  .wm-hero__wrap {
    padding-top: calc(var(--im-nav-h) + var(--im-space-5));
    padding-bottom: var(--im-space-5);
  }
  .wm-hero__steps { padding-bottom: var(--im-space-8); }
  .wm-step__note { font-size: 11.5px; }
}
/* Below this the hero drops its full-screen claim rather than running one and a
   half screens deep. The old rail was `display: none`d here, which was fair when
   it was decorative and aria-hidden; these steps are real content and hiding
   them takes them from assistive technology too, so they stay and the hero
   simply sizes to what it holds. */
@media (max-height: 600px) {
  .wm-hero { min-height: 0; }
  .wm-hero__steps { padding-bottom: var(--im-space-6); }
}
/* Short AND wide is the landscape phone, and there the two-column grid the
   stacked tier gives it costs ~84px of height for no reason: 844px of width
   holds four columns comfortably at 181px each. Keyed on both axes because the
   saving only exists when the width is there — a 360px portrait phone must stay
   at two. */
@media (max-height: 600px) and (min-width: 700px) {
  .wm-steps { grid-template-columns: repeat(4, minmax(0, 1fr)); }
  .wm-step__note { font-size: 11.5px; }
}
@media (max-height: 600px) and (max-width: 980px) {
  .wm-hero__wrap {
    padding: calc(var(--im-nav-h) + var(--im-space-4)) var(--im-space-6) var(--im-space-6);
  }
  .wm-hero__title { font-size: clamp(28px, 4.4vw, 36px); margin-bottom: var(--im-space-3); }
  .wm-hero__lede { margin-bottom: var(--im-space-4); }
}

/* The only prefers-reduced-motion block in this file sits with the progress fill
   above, next to the rule it answers, rather than in a catch-all down here. The
   map's parallax and roam are JS-side with their own REDUCED guard. */
