/* ═══════════════════════════════════════════════════════════════════════
   Hero version 2 - region, hub, network.

   Layered on top of css/site.css, which is loaded first and left untouched:
   index-v1.html and this page share the header, the type scale, the slide grid and
   every token. Only the centerpiece differs, so only the centerpiece is styled
   here. Anything added below that is not about the centerpiece belongs in
   site.css.

   The member-state markers deliberately reuse version 1's `.net-state` classes
   rather than defining their own: the selected state, the focus ring, the
   reticle, the ping and the responsive marker radii all already live in
   site.css, and js/hero-countries.js already knows how to address them.
   ═══════════════════════════════════════════════════════════════════ */

.hero {
  /* Straight off the GCC Center brand sheet: #094A75 deep blue, #4B7E99 steel,
     #9AC5D3 sky, #DAA520 gold. The centerpiece is the mark in motion, so it
     draws from the mark's own palette rather than from a colour of its own -
     which is also what tells this page apart from version 1, where the network
     is drawn in the GOIC blues from css/site.css. */
  --hs-node: #094A75;
  --hs-thread: rgba(75, 126, 153, 0.55);
  --hs-frame: rgba(9, 74, 117, 0.4);
  --hs-gold: #daa520;
  /* The gold glow, declared once and worn by every gold thing in the figure:
     the beads on the six lines, the innovation dots in the field, and the one
     gold ring. Two shadows rather than one - a tight bright halo for the lit
     edge and a wide faint one for the bloom around it - because a single blur
     at either radius reads as a smudge where the pair reads as a light.
     drop-shadow rather than an SVG filter on the group: the region is the
     element's own box, so eighteen beads are eighteen tiny filters instead of
     one covering the whole fan and recomputed on every frame of the scrub. */
  --hs-gold-glow: drop-shadow(0 0 1.5px color-mix(in srgb, #daa520 90%, transparent))
                  drop-shadow(0 0 5px color-mix(in srgb, #daa520 55%, transparent));
  --hs-gold-glow-soft: drop-shadow(0 0 3px color-mix(in srgb, #daa520 40%, transparent));
}
.hero[data-theme="dark"] {
  /* On the dark ground the deep blue disappears, so the sky tone carries the
     lines and the deep blue is what they are drawn against. */
  --hs-node: #9ac5d3;
  --hs-thread: rgba(154, 197, 211, 0.45);
  --hs-frame: rgba(154, 197, 211, 0.26);
}

/* ── the mount ──
   Same cell, box and clipping as .hero__build in site.css, so the field lands on
   exactly the center the version 1 mark occupies. Swapping the two pages must
   not move the centerpiece a pixel. */
.hero__spider,
.hero__spider > svg {
  grid-area: 2 / 2 / 5 / 3;
  align-self: center;
  justify-self: center;
  width: min(720px, 92vw, 78svh);
  aspect-ratio: 1;
}
.hero__spider {
  position: relative;
  z-index: 1;
  display: grid;
  place-items: center;
  /* Off by default. The six markers switch themselves back on - see below. */
  pointer-events: none;
  /* Two movements, one property. --hs-bob is the dangle, written per frame in
     px by setSway; --hs-act3 is the radar's own 0-1 curve, and the lift it
     drives is what keeps the radar's lower-left label and point out of act 3's
     headline (the reasoning is in js/hero-spider.js, where --hs-act3 is set).
     A share of the figure's own height rather than a fixed distance, so it
     tracks the box at every viewport - and it clips nothing: overflow is on the
     SVG inside, so the clip box travels with the drawing. */
  --hs-rise: 10%;
  --hs-settle: 0.06;
  translate: 0 calc(var(--hs-bob, 0px) - var(--hs-rise) * var(--hs-act3, 0));
  /* The lift alone cannot win at both ends on a 900px-tall laptop: enough of it
     to clear the headline pushes the top of the burst into the header. So the
     figure also settles back a little as the radar is drawn around it - six per
     cent, about the centre, which buys room top and bottom at once and reads as
     the field stepping back to show the whole profile. */
  scale: calc(1 - var(--hs-settle) * var(--hs-act3, 0));
  will-change: translate, scale;
}
.hero__spider > svg {
  display: block;
  /* The spokes stay inside the box by construction, but the node halos on the
     longest ones do not, and an unclipped box lets them wash over the
     headline. */
  overflow: hidden;
  will-change: transform;
}

/* ── the region ──
   pathLength="1" is set in the markup, so the dash pattern is in units of the
   whole outline and the draw-on is a plain 1-to-0 dashoffset regardless of how
   long the coastline actually is. */
.hs-coast {
  fill: none;
  stroke: var(--hs-thread);
  stroke-width: 1;
  stroke-dasharray: 1 1;
  stroke-linejoin: round;
}

/* The boundaries between the six, at more than the coastline's weight. There is
   no headroom underneath the coast to be subtle in: --hs-thread is already a
   0.45-alpha colour and the SVG paints at roughly 1:1 with CSS pixels, so a 0.6
   stroke at 0.55 opacity was a quarter-alpha sub-pixel line - in the DOM and
   invisible on screen - and even 1, matching the coast, read thin on the short
   lines. The point of these is that the six are six countries, so they are the
   one thing here allowed to be heavier than the outline. Same dash scheme, so
   they draw on with it. */
.hs-border {
  fill: none;
  stroke: var(--hs-thread);
  stroke-width: 1.6;
  stroke-dasharray: 1 1;
  stroke-linejoin: round;
  stroke-linecap: round;
}

/* ── the four-sided form ──
   Act 2's frame, the mark's blue square. Solid in the mark, a hairline here:
   filled, it would become the subject and put a plate behind the headline. */
.hs-frame { fill: none; stroke: var(--hs-frame); stroke-width: 1; }

/* ── the six member-state markers ──
   Bigger than version 1's, for this hero only. site.css sizes `.net-node` at
   9.5 units, which on a 600-unit box painted at roughly 1:1 is a 19px disc -
   enough for a single accent colour, not enough for a flag, which needs to
   resolve into bands and a device to be worth showing at all. Scoped under
   .hero__spider so index-v1.html's markers are untouched.

   The halos grow with the disc. R_STATE_BURST is 238 and the box half-side is
   300, so act 3's outermost marker plus its halo has to stay inside 300 - 40
   at the largest step leaves 22 units of clearance. */
.hero__spider .net-halo-out { r: 30px; }
.hero__spider .net-halo-in  { r: 19px; }
.hero__spider .net-node     { r: 15px; }

@media (max-width: 900px) {
  .hero__spider .net-halo-out { r: 35px; }
  .hero__spider .net-halo-in  { r: 23px; }
  .hero__spider .net-node     { r: 18px; }
}
@media (max-width: 620px), (max-height: 620px) {
  .hero__spider .net-halo-out { r: 40px; }
  .hero__spider .net-halo-in  { r: 27px; }
  .hero__spider .net-node     { r: 21px; }
}

/* The flag disc. The stroke is the state's accent, set per marker in the
   markup: on a dark ground a flag with a pale band bleeds into the backdrop
   without a rim, and the accent is what version 1's core dot used to carry, so
   §6.3's six matched weights survive the change intact. Rendered above the
   halos, so the ring reads as the edge of the chip rather than of the glow. */
.hero__spider .hs-flag-node {
  stroke-width: 1.6;
  stroke-opacity: 0.85;
}

/* ── spokes and nodes ──
   Round caps and a sub-pixel stroke: the reference art is drawn with a fine pen
   and a heavier line turns the network into a wheel. */
.hs-thread {
  stroke: var(--hs-thread);
  stroke-width: 1.15;
  stroke-linecap: round;
}
/* One colour per node, indirected through `--tip`, so the gold variant does not
   have to know anything about the node it is colouring.
   Every node is a solid disc. A third of them used to be drawn hollow, as a
   ring, and the mix was what kept the field from looking mechanical; the client
   asked for solid marks throughout, and the size range in js/hero-spider.js
   carries that variety on its own now. */
.hs-tip { fill: var(--tip, var(--hs-node)); }
/* The mark's gold dots: innovation and digital nodes on the six journeys. They
   carry the glow, which is what makes gold read as the accent rather than as a
   second node colour. */
.hs-tip--gold { --tip: var(--hs-gold); filter: var(--hs-gold-glow); }

/* A breath around each node, so the nodes read as lights rather than as dots.
   The fill is a gradient set in the markup - see the note in the SVG defs. */

/* ── the nucleus ──
   The terminal form of the build. It was a thick ring left open in the middle,
   and the opening carried the idea; on the client's instruction it is a solid
   mark now, and a smaller one, so the fill and the stroke trade places. */
.hs-core { fill: var(--hs-node); stroke: none; }
/* The marks travelling the six lines. A lit centre falling to the brand gold
   at the rim, rather than a flat fill: at this size a disc of one colour reads
   as a dot on a line, and the point of these is that something is moving along
   it. The glow is the same one every gold thing in the composition carries. */
.hs-pulse { fill: url(#hs-bead); filter: var(--hs-gold-glow); }

/* The only gold line in the composition, and the only place the brand's second
   colour appears now that the hexagon is gone. A hairline: §5 wants gold as an
   accent, and it now rings the mark at a distance rather than hugging it. */
.hs-gold-ring {
  fill: none;
  stroke: var(--hs-gold);
  stroke-width: 1;
  /* The ring gets the glow at half weight. It is a full circle rather than a
     3px dot, so the same shadow that reads as a lit bead reads as a haze on
     this. */
  filter: var(--hs-gold-glow-soft);
}

/* ── the maturity radar ──
   Act 3's assessment, drawn on the same tokens version 1's radar uses so the two
   pages measure in the same hand: a dashed grid in the hero's own rule colour,
   the measured profile in the mark's blue, the target in gold. Weights are set
   so the grid stays behind the network - it is what the field is measured on,
   not a second picture. */
.hs-radar-ring {
  fill: none;
  stroke: var(--hero-rule);
  stroke-width: 1;
  stroke-dasharray: 2 5;
  opacity: 0.55;
}
.hs-radar-ring--last { stroke-dasharray: none; opacity: 0.7; }
.hs-radar-axis { stroke: var(--hero-rule); stroke-width: 1; opacity: 0.5; }

.hs-radar-area {
  fill: color-mix(in srgb, var(--hs-node) 14%, transparent);
  fill-rule: evenodd;
  stroke: var(--hs-node);
  stroke-width: 1.6;
  stroke-linejoin: round;
  opacity: 0.9;
}
/* The gap is the point of the act, so the target is the one shape allowed to be
   gold besides the hairline at the rim - dashed, because it has not happened
   yet. */
.hs-radar-target {
  fill: none;
  stroke: var(--hs-gold);
  stroke-width: 1.5;
  stroke-dasharray: 6 5;
  stroke-linejoin: round;
}
/* Vertices of the measured profile, in the profile's own colour: gold here
   would collide with the gold innovation dots scattered through the field, and
   two different meanings in one colour is worse than a quieter vertex. */
.hs-radar-point { fill: var(--hs-node); }

/* The band between the two profiles - one path, evenodd, the same construction
   version 1 uses. This is the shape the legend's last row names, so it is drawn
   rather than left as empty space. */
.hs-radar-gap {
  fill-rule: evenodd;
  fill: color-mix(in srgb, var(--hs-gold) 13%, transparent);
  stroke: none;
}

/* ── the legend ──
   .hero__key, its layout and its fade all come from css/site.css untouched; the
   markup in this page is version 1's. Only the swatches are repainted, from
   the mark's palette, so the legend cannot describe version 1's colours while
   the radar is drawn in these.

   Its position is site.css's too. It used to be pushed down to clear the version
   badge this page parked in the same band; the badge is gone, so the band is
   empty here exactly as it is on version 1. */
.hero__key-mark--point::before  { background: var(--hs-node); }
.hero__key-mark--now::before    { background: var(--hs-node); }
.hero__key-mark--target::before { border-top-color: var(--hs-gold); }
.hero__key-mark--gap::before {
  background: color-mix(in srgb, var(--hs-gold) 26%, transparent);
}

/* font-size on SVG text resolves in user units, so this is 14 of the viewBox's
   600 and the labels scale with the graphic instead of drifting off their axes.
   Without the six names the profile is a decorative shape: the reader can see
   that something was measured but not what. */
.hs-radar-label {
  font-size: 14px;
  font-weight: 500;
  letter-spacing: 0.03em;
  fill: var(--hero-meta);
}

/* ── the live member states ──
   Interactive from the moment they arrive, in every formation - the same rule
   version 1 applies to a visible slide. `pointer-events` is inherited,
   so site.css's `.net-state--live { pointer-events: auto }` would otherwise
   punch straight through the mount's `none` from the first frame and leave six
   invisible hit targets sitting in the middle of the headline. */
.hs-state:not(.is-armed) { pointer-events: none; }
.hs-state { transition: opacity 0.3s var(--ease-out); }

/* Version 1 dims the unselected markers through `.layer--visual.is-focus`, a
   plane this page does not have. Same effect, sourced from the selection itself
   rather than from a class js/hero-countries.js has to be taught to set. */
.hero__spider:has(.is-active) .net-state:not(.is-active) { opacity: 0.28; }
.hero__spider:has(.is-active) .hs-dot { opacity: 0.3; }

/* The centerpiece carries the hero on a small screen, where the headline takes
   most of the box. Same escalation as .hero__build. */
@media (max-width: 900px), (max-height: 900px) {
  .hero__spider > svg { transform: scale(1.06); }
}
/* 2026-09-05: there used to be a second step here, scale(1.18) under 620px.
   It was the larger half of a measured act-3 collision and it is gone. The
   radar's outer level is 186 of the viewBox's 600, so the drawing reaches 31%
   of the box either side of centre; at 1.18 that is 37%, and on a 390x820
   phone the ring closed 68px past the top of the act-3 eyebrow and 34px into
   the headline's first line. 1.06 is what 620-900px already used and it is
   what these widths use now. */

/* Below this the box is small enough that 14 user units lands under 10px, and a
   ring of unreadable words around the field is worse than no words. The group's
   aria-label still names all six dimensions. */
@media (max-width: 760px), (max-height: 700px) {
  .hs-radar-label { display: none; }
  /* site.css turns the prose fallback on at 700px, for version 1's own
     breakpoint. These labels go at 760px, so the prose has to arrive with
     them - otherwise the six dimensions are named nowhere between the two. */
  .hero__key-axes { display: block; margin-top: 0.75rem; }
}

/* The lift, on a narrow screen.

   This block used to read `--hs-rise: 0px; --hs-settle: 0`, on the reasoning
   that with the labels gone there was nothing left to lift the figure clear of
   and that the headline sat under the box rather than across it. The second
   half of that was wrong, and it is what made act 3 look broken on a phone:
   at 390x820 the box is 92vw = 359px running 202-560 down the viewport, and
   slide 3's eyebrow starts at 444 - inside it, not below it. The labels were
   never the thing the rise was clearing. The headline was.

   So the rise grows here instead of vanishing. 16% of the box, measured
   against the top of the act-3 eyebrow with the figure at act 3 (--hs-act3: 1):
   360x740 grazes it by 18px, 390x820 clears by 10, 430x932 by 75, 600x900 by
   27, 760x1024 by 80. The graze at 360 is smaller than the 21px a 1440x900
   desktop already accepts, Arabic clears at every one of those widths, and the
   tightest still leaves 73px between the ring and the bottom of the header.

   Width only. A short viewport has no height to spend on a lift - see below -
   and 640px of height is where 16% starts pushing the ring under the bar. */
@media (max-width: 760px) and (min-height: 641px) {
  .hero__spider { --hs-rise: 16%; }
}

/* A short viewport is the case the old rule was actually right about: there is
   no room above the figure, so lifting it only trades the headline for the
   header. It stays where it is and the settle goes with it, exactly as before.
   Act 3 is cramped at this height whatever we do; this is the least bad of it. */
@media (max-height: 700px) {
  .hero__spider { --hs-rise: 0px; --hs-settle: 0; }
}

/* The legend, on a narrow screen.

   site.css parks .hero__key in the band under the header on the right, which
   is empty on a desktop. It is not empty here. The figure is 92vw wide and the
   lift above pulls it up into exactly that band: measured at 430x932 the key
   occupied x 193-409 / y 130-337 and the radar x 85-345 / y 242-502, so the
   four swatch rows and the axis prose were printed straight across the top
   right of the drawing. That is what the client saw on an iPhone on 2026-09-05.

   Nothing fits both. The figure is the centrepiece and the reader can read the
   profile without being told which dash is which, so on a phone the key keeps
   its head and loses its body: `تقييم النضج الرقمي` still names the graphic,
   at y 130-150, clear of a drawing that starts at 242.

   What goes with the body is the axis prose - the six dimension names that
   site.css and the block above turn on here as a fallback for the SVG labels,
   which are themselves hidden at this size. They are not lost: the radar group
   carries all six in its aria-label, which is where a reader who cannot see the
   drawing was always going to find them.

   800px rather than the 760px the labels go at. An iPad portrait is 768 wide,
   and there the ring passed straight through the last two rows of the list -
   the key's nearest corner sits 196px from the centre of a radar whose outer
   level is at 218. Narrowing the box instead was tried and does not settle it:
   13.5rem moves that corner out to 212, still inside the ring, because a
   narrower column wraps the rows and grows back down into it. 820x1180 and up
   clear the ring at the full 17rem, which is where this stops. */
/* ── the legend as a control ──
   The head is a <button> in this page's markup. Above 800px it is not a control
   and must not look like one: site.css draws .hero__key-head as a heading, and
   these first rules only undo what a UA stylesheet does to a button - its own
   font, its background, its padding, its centred text - so that the heading
   above the printed legend is the heading it always was. js/hero-key.js is what
   withholds aria-expanded at this width; this is the visual half of the same
   promise. */
.hero__key-head {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  width: 100%;
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  font-size: var(--fs-micro);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  text-align: start;
  /* site.css's .hero__key sets this on the whole block, and a heading has no
     business taking a click. The narrow-screen rules below hand it back. */
  pointer-events: none;
}
.hero__key-headline { flex: 1 1 auto; }
.hero__key-chev { display: none; }

@media (max-width: 800px) {
  /* ── collapsed ──
     One card rather than a heading with a list under it: at this width the
     legend sits over the drawing, and a plate with a ground of its own is the
     difference between a panel and a caption someone spilled on the artwork.

     The ground is the hero's own blue at 62% over a blur of what is behind it,
     so the figure still reads through it as depth rather than as clutter. The
     hairline is the gold the radar's target ring is drawn in, at 22% - present
     at a glance, not a box someone drew. The shadow is thrown down and short,
     which is what lifts it off the field. */
  .hero__key {
    /* Shut, the legend is a chip in the corner and nothing more. A labelled
       pill at 62vw reaches far enough inboard to sit on the top-right member
       state's halo - Saudi Arabia's, on an iPhone - and .hero__key is above the
       figure, so it covered the flag and swallowed the tap that opens its
       profile. The corner is the one part of this band no marker occupies: the
       flags ride the radar's ring, which is inset from the box, and the box is
       92vw. So the shut state is sized to clear the ring rather than to carry
       the words, and the words arrive with the panel.

       Width and radius are both stated at each end so the chip unfolds into the
       plate rather than snapping. */
    width: 2.75rem;
    border-radius: 999px;
    /* No padding on the card: the head carries it instead, so the button's hit
       area is the whole top of the plate rather than a target with an inert
       16px frame around it. .hero__key-inner carries the same inset below. */
    padding: 0;
    border: 1px solid color-mix(in srgb, var(--hs-gold) 22%, transparent);
    background: color-mix(in srgb, #141e26 62%, transparent);
    -webkit-backdrop-filter: blur(14px) saturate(140%);
    backdrop-filter: blur(14px) saturate(140%);
    box-shadow:
      inset 0 1px 0 color-mix(in srgb, #fff 7%, transparent),
      0 18px 40px -26px #000;
    /* The panel is a plate now, so it gets the corner treatment of one: it
       grows out of the collapsed pill rather than appearing at full size. */
    transition:
      width 0.45s var(--ease-out),
      border-radius 0.45s var(--ease-out),
      background 0.45s var(--ease-out),
      border-color 0.45s var(--ease-out);
  }
  .hero__key.is-open {
    width: min(16rem, 62vw);
    border-radius: 14px;
  }

  /* The name is still the button's accessible name while it is a chip - opacity
     rather than display, so a screen reader still reads "digital maturity
     assessment" off a control that shows only a chevron. */
  .hero__key:not(.is-open) .hero__key-headline {
    flex: 0 0 0;
    width: 0;
    overflow: hidden;
    white-space: nowrap;
    opacity: 0;
  }
  .hero__key-headline { transition: opacity 0.3s var(--ease-out) 0.15s; }

  /* The head is the control here, and its whole box is the target. The padding
     above is the card's, so the button reclaims it as hit area rather than
     adding its own - a 16px strip of card that does not respond to a tap is
     the difference between a control and a picture of one. */
  .hero__key-head {
    /* The card's own inset, moved onto the control. Chrome sizes a button by
       its content even as a block-level flex container, so the width is stated
       rather than left to fill - otherwise the Arabic head, whose line is
       shorter, would be a target two thirds the width of the plate it sits in
       while the English one filled it. */
    width: 100%;
    margin: 0;
    padding: 0.7rem 0.85rem;
    cursor: pointer;
    border-radius: inherit;
    -webkit-tap-highlight-color: transparent;
  }
  /* Shut, the control is the chip: a square of it, chevron centred, no inset of
     its own. 2.75rem square is 44px, which is the smallest target worth
     shipping to a thumb.

     Nothing geometric here eases. The head used to carry a 0.45s padding
     transition, but the height below and the headline's width collapse both cut
     instantly, so the inset was the one part of the box arriving late - a
     control that finishes changing shape after it has finished changing size.
     Removed 2026-09-11. The movement a reader actually sees is the headline's
     opacity fade, which is still there and still delayed to let the box settle
     first. */
  .hero__key:not(.is-open) .hero__key-head {
    justify-content: center;
    height: 2.75rem;
    padding: 0;
    gap: 0;
  }
  /* Armed only while act 3 is on screen. .hero__key is at opacity 0 outside it
     and an invisible button over act 2's headline is a trap. */
  .hero.is-key-on .hero__key-head { pointer-events: auto; }
  .hero__key-head:focus-visible {
    outline: 2px solid var(--hs-gold);
    outline-offset: 3px;
  }

  /* The affordance. A chevron drawn from two borders rather than an icon file,
     so it takes the legend's own colour and needs nothing fetched. It sits in a
     ring that is the same hairline as the card's, one step brighter, which is
     what reads as a button at 12px without printing the word "more". */
  .hero__key-chev {
    display: grid;
    place-items: center;
    flex: none;
    width: 1.35rem;
    height: 1.35rem;
    border-radius: 50%;
    border: 1px solid color-mix(in srgb, var(--hs-gold) 28%, transparent);
    background: color-mix(in srgb, var(--hs-gold) 8%, transparent);
    transition: transform 0.45s var(--ease-out),
                background 0.45s var(--ease-out);
  }
  .hero__key-chev::before {
    content: "";
    width: 0.36rem;
    height: 0.36rem;
    border-right: 1.5px solid var(--hs-gold);
    border-bottom: 1.5px solid var(--hs-gold);
    /* Down when shut. The glyph's own box is a square, so it is nudged up by a
       sixth of its height to sit on the ring's optical centre rather than its
       geometric one. */
    transform: translateY(-16%) rotate(45deg);
  }
  .hero__key:not(.is-open) .hero__key-chev {
    border-color: transparent;
    background: none;
  }
  .hero__key.is-open .hero__key-chev {
    transform: rotate(180deg);
    background: color-mix(in srgb, var(--hs-gold) 16%, transparent);
  }

  /* ── the disclosure ──
     grid-template-rows 0fr to 1fr, which animates to the content's own height
     without JS measuring it and without a max-height guess that is either too
     short in Arabic or leaves the transition idling in English. The child needs
     min-height 0 and overflow hidden or it refuses to be compressed. */
  .hero__key-body {
    display: grid;
    grid-template-rows: 0fr;
    transition: grid-template-rows 0.5s var(--ease-out),
                opacity 0.32s var(--ease-out);
    opacity: 0;
  }
  /* Side padding only. A bottom padding would survive the collapse - the 0fr
     row zeroes the content box and overflow clips what is in it, but padding is
     the box's own and stays - leaving 11px of empty plate under a shut pill.
     The ::after is content, so it goes away with everything else. */
  .hero__key-inner { min-height: 0; overflow: hidden; padding: 0 0.85rem; }
  .hero__key-inner::after { content: ""; display: block; height: 0.7rem; }
  .hero__key.is-open .hero__key-body {
    grid-template-rows: 1fr;
    opacity: 1;
    transition: grid-template-rows 0.5s var(--ease-out),
                opacity 0.4s var(--ease-out) 0.12s;
  }
  .hero__key-body ul { padding-top: 0.15rem; }
  .hero__key-axes {
    display: block;
    margin: 0.75rem 0 0;
    padding-top: 0.7rem;
    border-top: 1px solid color-mix(in srgb, var(--hs-gold) 14%, transparent);
    opacity: 0.78;
  }

  /* The rows arrive one after another rather than as a block. 55ms apart, which
     is a cascade at reading distance and a stutter past about 90. Only on the
     way in: closing, they go together, because a reader who is dismissing a
     panel is not being shown anything. */
  .hero__key.is-open .hero__key-body li {
    animation: key-row 0.42s var(--ease-out) both;
    animation-delay: calc(0.14s + var(--i, 0) * 0.055s);
  }
  .hero__key-body li:nth-child(1) { --i: 0; }
  .hero__key-body li:nth-child(2) { --i: 1; }
  .hero__key-body li:nth-child(3) { --i: 2; }
  .hero__key-body li:nth-child(4) { --i: 3; }
  @keyframes key-row {
    from { opacity: 0; transform: translateY(6px); }
    to   { opacity: 1; transform: none; }
  }

  /* Open, the plate stops pretending the drawing behind it is readable and
     commits: a denser ground and a brighter hairline. */
  .hero__key.is-open {
    background: color-mix(in srgb, #141e26 88%, transparent);
    border-color: color-mix(in srgb, var(--hs-gold) 34%, transparent);
  }

  /* And the figure steps back while it is open. Not hidden - the legend is
     about the shape, and a reader comparing the two needs both - but far enough
     that the type in front is the thing being read. The mount carries no
     JS-written opacity of its own; everything animated inside it is a child. */
  .hero:has(.hero__key.is-open) .hero__spider {
    opacity: 0.3;
    transition: opacity 0.45s var(--ease-out);
  }
}

/* Nothing above moves without being asked twice. */
@media (prefers-reduced-motion: reduce) {
  .hero__key,
  .hero__key-body,
  .hero__key-headline,
  .hero__key-head,
  .hero__key-chev { transition: none; }
  .hero__key.is-open .hero__key-body li { animation: none; }
}
/* ── the brand lockup ─────────────────────────────────────────────────────
   Version 1 draws a monogram inline and sets the Center's name in type beside
   it. This page carries the supplied artwork instead, so site.css's
   .bm-ring/.bm-nodes/.bm-hub/.bm-core and .brand__mark rules paint nothing
   here and there is no .brand__mark in the markup at all.

   One file, in both states, on the client's instruction. As of 2026-08-29 that
   file is assets/logo-for-header.svg - white wordmark, gold Arabic, gold
   tagline - and the contrast note that used to sit here is settled rather than
   carried: the previous single file was assets/logo-colored.svg, whose #1e6195
   wordmark on the hero's #141E26 measured about 2.4:1, under the 3:1 large
   text wants, and the client supplied artwork drawn for the dark bar instead.

   One file works on this page because the bar never turns light. It is
   transparent over the dark hero, and past the hero js/main-v2.js writes
   is-dark and it paints --blue-900 (see the dark bar section below). Before
   that decision the bar did resolve into a white plane, which is why this used
   to be a crossfade on the is-over-hero switch - white-and-gold lockup over the
   hero, blue-and-gold one over the solid bar - and why services.php and
   knowledge.php still carry that pair: their bars start white.

   So the constraint to keep in mind when touching the dark bar rules is that
   the header artwork now depends on them. A bar that goes light on this page
   would take the wordmark with it. */
.brand__logo {
  position: relative;
  display: block;
  flex: none;
  height: var(--brand-logo);
  /* The artwork's own box. As a ratio rather than a width so the element
     reserves its space before the SVG has decoded and the links beside it do
     not jump. */
  aspect-ratio: 1014.28 / 332.65;
  transition: height 0.5s var(--ease-out);
}
.brand__logo-img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  transition: opacity 0.45s var(--ease-out);
}
/* GOIC's own lockup, on the client's instruction 2026-10-01: last in the bar,
   so the nav sits between the Center's mark and this one. It is outside the
   home link on purpose. The Center's mark is to read 15% bigger, measured on
   the drawn artwork, not the boxes: logo-for-header.svg's ink fills 58.4% of
   its box height and logo-goic.svg's fills 91.6%, so equal-ratio boxes made
   GOIC look a third bigger. 0.584 / (1.15 * 0.916) = 0.554 of --brand-logo,
   which follows both bar states. Dark-ground version, white type. */
.brand__logo--goic {
  height: calc(var(--brand-logo) * 0.554);
  aspect-ratio: 300 / 100;
}
/* Up to 800 the burger bar has no room for it: measured 2026-10-01, GOIC ran
   ~100px off the edge at 375 and still 52-79px off at 561-600. It gives way
   rather than the Center's mark shrinking. At 768 it fit with 38px to spare,
   only 12px in Arabic, hence the margin up to 800. */
@media (max-width: 800px) {
  .brand__logo--goic { display: none; }
}
/* ...and moves into the menu panel instead. The panel is white, so this is the
   colour logo-goic.webp the footer uses, not the white-type SVG from the bar. */
.menu__goic {
  display: none;
  justify-self: start;
  margin-inline: var(--pad);
  width: auto;
  height: 48px;
}
@media (max-width: 800px) {
  .menu__goic { display: block; }
}
/* The services bar also carries the sign-in dropdown (.navdrop, 210px), so its
   actions are 464px against 254 on home and GOIC ran 122px off at 801 and
   still 40px off at 981. It fits from about 1070, so on this bar it stays in
   the menu for the whole burger range. */
@media (max-width: 1080px) {
  .nav:has(.navdrop) .brand__logo--goic { display: none; }
  body:has(.nav .navdrop) .menu__goic { display: block; }
}

/* With a mark at each end, the links are fluid and spread through whatever the
   two marks leave, and each mark gets a white hairline on its inner side.
   Logical sides, so the Arabic bar mirrors. Wide bar only: below 1081 the links
   are the burger and there is nothing between the marks to separate. */
@media (min-width: 1081px) {
  .nav { --brand-sep: clamp(1rem, 1.8vw, 1.75rem); }
  .nav__links {
    flex: 1 1 auto;
    justify-content: space-evenly;
    margin-inline-start: 0;
  }
  .brand { position: relative; padding-inline-end: var(--brand-sep); }
  .brand__logo--goic { margin-inline-start: var(--brand-sep); }
  .brand::after,
  .brand__logo--goic::before {
    content: '';
    position: absolute;
    top: 15%;
    bottom: 15%;
    width: 1px;
    background: rgba(255, 255, 255, 0.6);
  }
  .brand::after { inset-inline-end: 0; }
  .brand__logo--goic::before { inset-inline-start: calc(-1 * var(--brand-sep)); }
}

/* ── the header ────────────────────────────────────────────────────────────
   Version 1's bar is a single fixed height with a 34px monogram set flush
   against the name, which is a functional header rather than the front door of
   an institution. Everything below is version 2 only; css/site.css keeps the
   structure, the tokens and the behaviour, and this changes the proportions.

   Three ideas, in order of how much they do:

   1. The bar breathes over the hero and condenses past it. `is-over-hero` is
      already toggled for us, so the generous state costs nothing but a
      transition: 84px of bar and a 58px mark on the opening screen, 63px and
      44px once the reader is into the page and the bar is a utility again.

      Those were 71/44 and 53/34, which was too quiet for the front door. The
      mark is a diagonal swoosh with pale corners, so it reads smaller than its
      box - a square logotype at 44px and this one at 44px are not the same
      apparent size, and the box has to be bigger for the mark to land the same.
   2. The mark is given its own room and a hairline to stand against, instead
      of being crowded by the type. The rule is gold, which is the one place
      §5's accent belongs in a bar this quiet - a hairline carries no weight,
      and gold at type size on white would fail contrast anyway.
   3. The name and the parent organisation stop being one indistinct block:
      the Center's name holds the weight, GOIC sits under it as tracked
      uppercase micro-type, which is how a subsidiary line is normally set. */

.nav {
  /* The mark's gold, declared here as well as on .hero: the bar is outside the
     hero, so it does not inherit the hero's copy of the palette. */
  --hs-gold: #daa520;

  padding-block: 0.6rem;
  /* One writer for the lockup, so the bar's two heights are the artwork plus
     its padding and nothing has to be kept in sync by hand. Nothing is set in
     type beside the artwork any more, which freed about 180px along the bar -
     so the lockup takes it: 256px wide over the hero, where the monogram and
     its typeset name together were 382px. The bar is the front door, and at
     this size the mark inside the artwork is finally reading as a mark rather
     than as the dot on the left of a wordmark.
     Raised about a quarter on the client's instruction, from 74/98 to 92/124.
     Every breakpoint moves together - the ratio between the bar's two heights is
     what makes the shrink on scroll read as one movement rather than as the
     lockup jumping to an unrelated size. */
  --brand-logo: 92px;
  transition: background 0.45s var(--ease-out), border-color 0.45s var(--ease-out),
              backdrop-filter 0.45s var(--ease-out), transform 0.4s var(--ease-out),
              padding 0.5s var(--ease-out), box-shadow 0.45s var(--ease-out);
}
/* Solid, the bar is a plane the page slides under, so it gets a shadow rather
   than only a hairline - the hairline alone lets long body text appear to touch
   it. Barely there: 6% of a blue that is already dark. */
.nav:not(.is-over-hero) { box-shadow: 0 1px 24px rgba(9, 74, 117, 0.07); }
.nav.is-over-hero { padding-block: 0.8rem; }

/* The big lockup belongs to the reader coming back up the page. Changed on the
   client's instruction, 2026-09-01: the mark condenses on the way down and
   grows on the way up, anywhere on the page, hero or no hero.

   Two superseded rules sit behind that, both preserved here because the reason
   each was replaced is the reason this one is written the way it is.

   It began as `--brand-logo: 124px` on `.nav.is-over-hero`, and that class is
   true for the entire height of the hero minus the bar - roughly a screen and a
   half of scrolling. So the mark was at 124px the moment the reader came back up
   past that line, which is nowhere in particular and reads as the bar growing on
   its own halfway up the page.

   On 2026-08-29 the client pinned it to the top edge instead - `is-at-top`,
   grown only within a few pixels of zero. That fixed the arbitrary line by
   tying the size to a place. The complaint since is that the mark is then
   condensed for the whole page, and the reader who scrolls up to use the header
   gets the small one. So the size is tied to a movement rather than to a place:
   scrolling up is reaching for the header, and the header answers at full size.
   js/main-v2.js writes the class; the other bootstraps follow it.

   And on 2026-09-04 the client took it back to the hero's edge, wide header
   only: the mark changes size when the reader leaves the hero and at no other
   moment. Tying it to a movement had put a size change at whatever line the
   reader happened to reverse at, which is the same complaint as the original
   one wearing different clothes. Neither of the two rules above is wrong about
   what it was fixing; this one picks the boundary that is already on the page.
   Below 1081px the class is never set at all - see the ramps at the foot of
   this file, and the reasoning in js/main-v2.js.

   A second class rather than a narrower `is-over-hero`: that class also drives
   the transparent bar, the `::before` scrim, the padding and the dark-mode
   lockup crossfade. Narrowing it would make the bar go solid white while still
   sitting over the hero, which is a far bigger change than the one asked for.
   `is-at-top` carries the size and nothing else - the name is now narrower than
   what it does, since the top edge is only the floor case.
   padding-block stays on `is-over-hero` on purpose. The bar's breathing room is
   about the hero being behind it, and it is a 0.2rem difference nobody reads as
   a separate movement. Only the mark was the complaint.
   The 0.5s height transition on .brand__logo still applies, so the shrink and
   the grow are the same movement they always were. */
.nav.is-at-top { --brand-logo: 124px; }

/* The 124px mark only fits where the bar has the width for it. Measured on the
   live bar 2026-10-04, GOIC's mark at the far end ran off the right edge at
   laptop widths (GOIC team report, default zoom): at 1285px wide, 154px over
   with the CTA showing and 79px over at 124px without it. The CTA is only
   hidden over the hero on windows taller than 620px, so on a short window, and
   again once the reader is past the hero, it takes ~190px the bar does not have.
   Each pixel of --brand-logo costs the bar ~4.7px of width (both marks scale
   with it).

   So the top-edge size ramps with the window instead of being a flat 124px:
   92px up to 1300, 124px from 1700, and the CTA gives way below 1650, the
   width at which it fits beside the ramped marks with room for Arabic. Home bar
   only: the services and knowledge bars carry the sign-in dropdown and have
   their own width budget. The hero and the menu both still carry the same CTA. */
@media (min-width: 1081px) {
  .nav.is-at-top { --brand-logo: clamp(92px, 92px + (100vw - 1300px) * 0.08, 124px); }
}
@media (min-width: 1081px) and (max-width: 1649px) {
  .nav:not(:has(.navdrop)) .nav__cta { display: none; }
}

/* White type over a moving field is legible until the field moves something
   pale under it. A scrim from the top edge, weakest where the bar ends, holds
   the type without reading as a bar of its own - and it is only needed on the
   dark ground, where the type is white. */
.nav.is-over-hero::before { content: none; }
body[data-hero-theme="dark"] .nav.is-over-hero::before {
  content: '';
  position: absolute;
  inset: 0 0 auto 0;
  height: 260%;
  z-index: -1;
  pointer-events: none;
  background: linear-gradient(
    to bottom,
    rgba(20, 30, 38, 0.42) 0%,
    rgba(20, 30, 38, 0.22) 45%,
    rgba(20, 30, 38, 0) 100%);
}

/* ── the narrow bar, once the reader moves ───────────────────────────────
   `is-over-hero` holds for the whole hero track, which is four viewports here,
   so the transparent bar is transparent for a screen and a half of scrolling.
   On a wide display that is the point: the lockup has the empty band at the top
   of the art to itself and nothing ever climbs into it. On a phone there is no
   empty band. The headline is 2.4rem, the art is behind it, and the first flick
   of the thumb sends both straight under a bar that has no ground of its own -
   the wordmark ends up sitting on the word "One" and the reader cannot tell
   which of the two they are looking at.

   So below the burger breakpoint the bar takes a ground as soon as the page
   leaves its top edge. `is-scrolled` is written by js/main-v2.js.

   It was the only thing on the site that read the class until 2026-09-11, when
   the client reported the same collision on every other page - where a masthead
   title, not a hero headline, is what climbs under the bar. css/pages.css and
   css/services.css now carry their own version of this rule, uncapped, because
   a masthead sits directly under the bar at every width. The rule below stays
   the homepage's: `is-over-hero` is on none of those pages.

   The same navy the bar resolves into past the hero, so this is that bar
   arriving early rather than a third state - held just off opaque with a blur
   behind it, which keeps the art moving under the lockup instead of cutting the
   top of the screen off with a solid strip.

   The scrim goes with it. It exists to hold white type over moving art, and a
   plane does that better; leaving both on stacks a gradient over a fill and
   reads as a smudge under the bar. */
@media (max-width: 1080px) {
  .nav.is-over-hero.is-scrolled {
    background: color-mix(in srgb, var(--blue-900) 92%, transparent);
    backdrop-filter: blur(14px);
    border-bottom-color: rgba(255, 255, 255, 0.12);
  }
  body[data-hero-theme="dark"] .nav.is-over-hero.is-scrolled::before {
    content: none;
  }
}

/* ── the dark bar ────────────────────────────────────────────────────────
   Past the opening screen the bar goes navy rather than white. Both pages end
   up here from opposite directions - the homepage is transparent over its dark
   hero and would otherwise resolve into a white bar, the Services page is white
   over its dark masthead - and both are the same decision: the bar stays a dark
   plane the rest of the page slides under, so the reader never watches the
   header change sides twice.

   `is-dark` is written by js/main-v2.js past the hero and by js/services.js
   past the masthead. Every colour in the bar is already a token on .nav (see
   css/site.css), so this sets five values and a background, and the links, the
   language button, the burger and the brand all follow. */
.nav.is-dark {
  --nav-ink: #ffffff;
  --nav-ink-2: rgba(255, 255, 255, 0.84);
  --nav-ink-3: rgba(255, 255, 255, 0.62);
  --nav-line: rgba(255, 255, 255, 0.26);
  --nav-accent: #ffffff;

  background: var(--blue-900);
  border-bottom-color: rgba(255, 255, 255, 0.12);
  backdrop-filter: none;
}

/* The shadow under the solid bar is a blue lift meant for white on white. On
   navy it is invisible at best and a smudge at worst. */
.nav.is-dark { box-shadow: 0 1px 24px rgba(0, 0, 0, 0.18); }


/* Navy on navy is not a button. Same inversion the bar makes over the hero. */
.nav.is-dark .btn--primary {
  background: #ffffff;
  color: var(--blue-900);
}
.nav.is-dark .btn--primary:hover { background: var(--blue-100); }

/* The links lose a little size and gain a little air, so the right-hand half of
   the bar stays clearly secondary to the lockup. The rule under them opens from
   the middle rather than the left edge, which is the difference between a link
   that lights up and one that gets underlined. */
.nav__links { gap: clamp(0.85rem, 1.9vw, 1.75rem); }
.nav__links a {
  /* Fluid rather than one size. The links only exist above 1080px, and that is
     exactly where the bar is tightest - the lockup, five links and the CTA all
     on one line, and the lockup just got taller. So the type takes the room a
     wide display has and gives it back near the breakpoint, instead of one size
     that either wraps at 1081px or stays small on a 27in screen. */
  font-size: clamp(0.9rem, 0.27vw + 0.73rem, 1rem);
  letter-spacing: 0.012em;
  padding-block: 0.45rem;
  /* Never two lines. "Knowledge Hub" and "News & Updates" were both breaking on
     the services page around 1100px, where the CTA is in the bar and the links
     get squeezed - a two-line menu beside a one-line lockup, which is worse than
     a tight one. The row has the width for all five at this size; it was the
     shrink that was wrapping them, not a shortage of room. */
  white-space: nowrap;
}
.nav__links a::after { height: 1px; transform-origin: center; opacity: 0.85; }

/* Both controls to the same micro-type, so the right-hand end of the bar reads
   as one set rather than three sizes. */
.nav__actions { gap: 0.85rem; }
.lang,
.nav__login {
  padding: 0.46rem 0.78rem;
  font-size: var(--fs-micro);
  letter-spacing: 0.04em;
  border-color: color-mix(in srgb, var(--nav-line) 70%, transparent);
}
.nav__cta > .btn {
  padding-block: 0.62rem;
  font-size: 0.82rem;
  letter-spacing: 0.015em;
}

@media (max-width: 860px) {
  /* The lockup gives back the height it took over the hero, but no more: it is
     the only brand element in the bar, so there is nothing else to shrink and
     nothing for it to hand over to. 62px still reads the artwork's tagline;
     below that the tagline turns into texture, which is the cost of showing the
     full lockup rather than the mark on its own.

     Fluid rather than one step, and this is what forced it. One breakpoint at
     860px has to serve every width down to 320, and the lockup is 3.05:1 - so a
     height that fits at 860 is 281px of width at 375, which left 345px of usable
     bar carrying a 281px mark plus a 125px actions group. The burger went off
     the right edge of the screen and could not be reached at all. The old fixed
     74px overflowed by 2px for the same reason; raising it turned a hairline
     into 57px.

     The two ends are measured, not guessed: 92px at 860 where there is room,
     64px at 375 where the mark, the language button and the burger together
     just fit inside the padding with about 29px to spare. */
  .nav { --brand-logo: clamp(58px, 42.5px + 4.12vw, 78px); }
  /* DEAD as of 2026-09-04, and kept on purpose. `is-at-top` is now only set
     above 1081px (the width at which css/site.css swaps the nav links for the
     burger), so nothing below 860 can carry it and this ramp cannot match. It
     stays because it is the measured tall end for these widths, and lowering
     the gate in the bootstraps would need it back the same day. If the gate is
     ever left where it is for good, delete this line and the one in the 374
     block below rather than letting them rot. */
  .nav.is-at-top { --brand-logo: clamp(64px, 42.4px + 5.77vw, 92px); }
}

/* Below 375px both ramps above sit on their floor, and the floor is too tall.
   The comment above says 375 is where the mark, the language button and the
   burger 'just fit' - which is exactly true, and it means everything narrower
   did not. At 320 the top-edge lockup is 64px of height, so 195px of width, and
   195 + 110 of actions + 16 of gap + 40 of padding is 361px in a 320px bar: the
   burger sat 37px off the right edge, unreachable, and the menu with it. The
   document did not scroll, so nothing looked wrong - the button was simply not
   on the screen. Same bug the 860px block was written to fix, one screen size
   further down.

   One ramp for both states here, not two. Above 375 the bar can afford to
   breathe at the top edge and condense past it; below it there is no spare
   width to spend on that, and a lockup that grew on scroll would be the thing
   pushing the burger off again. The ends are the same arithmetic as above:
   48px at 320 with about 2px of slack, meeting the outer ramp's 64px at 375. */
@media (max-width: 374px) {
  .nav, .nav.is-at-top { --brand-logo: clamp(48px, 29.1vw - 45px, 64px); }
}

/* ── the pause at the end ─────────────────────────────────────────────────
   Scroll the hero stays pinned for after the last act has finished. Without it
   the network resolves into the mark on the very last pixel of the track and the
   sections are already climbing over it, so the formation the whole hero was
   building towards is the one thing the reader never gets to sit with. Four
   fifths of a screen is long enough to read the radar and its legend and short
   enough that nobody thinks the page has jammed.

   The track grows by the same amount, so the pause is added scroll rather than
   scrub taken away from the three acts - they keep a full viewport each.
   js/scrollstage.js reads both numbers from here. */
.hero {
  --hero-hold: 0.8;

  /* ── the slow zone through act 1 ──────────────────────────────────────
     Off. Set to 0 by the client 2026-08-29: the extra scroll comes out and the
     track is pixel-linear from the first pixel to the last.

     What it was, because the machinery is still here and still works. The zone
     was scroll the hero crawled through rather than advanced on, spent by
     js/scrollstage.js as a stretch of constant sub-nominal speed ending at
     `--hero-dwell-at`, with the track grown by the same amount so the three
     acts kept a full viewport each of real progress. It had three forms in one
     day: half a screen of frozen track at the end of act 1, a landing for
     hovering the six member states, which are the only touchable things in the
     hero; then a raised-cosine dip in speed, because the pop-and-lock of the
     hard stop was the complaint; then a flat constant-speed zone, because a
     cosine runs at nominal at both its edges and the top of the track is one of
     them, so the first quarter of the map always went down in the first 160px
     however the dip was placed - fast, then slow, never straight.

     At 1.2 it made act 1 1.9 screens of scroll. At 0 act 1 is 0.77 of a screen,
     which is the acts' own share of the track and nothing added: 0.257 of a
     scrub that is now 3 screens exactly. The draw itself does not change shape
     either way - js/hero-spider.js has drawn the coastline linearly from 0 to
     the end of act 1 since the morning - so this is length only.

     `--hero-dwell` is still the one dial. Turn it up and act 1 lengthens
     linearly: 0.8 gives 1.5 screens, 1.2 gives 1.9, 1.6 gives 2.3. At 0 the
     solve in js/scrollstage.js is skipped outright and `warp` is the identity,
     so nothing is being computed and discarded.

     `--hero-dwell-at` is inert while the dwell is 0 and is left declared rather
     than deleted: it is where the zone would end, 0.257 is the end of act 1 -
     the frame js/entrance.js hands the headline to slide 2 on, and the frame
     js/hero-spider.js finishes the coastline on - and rediscovering that number
     is the expensive part of turning the zone back on. js/scrollstage.js reads
     both from here. */
  --hero-dwell: 0;
  --hero-dwell-at: 0.257;

  height: calc(
    100svh * (var(--hero-acts) + 1 + var(--hero-dwell) + var(--hero-hold))
  );
}
/* The short-viewport breakpoint in site.css already shortens the acts because a
   tall track is a marathon on a landscape phone. The pause is the same problem,
   so it shrinks with them. */
@media (max-height: 620px) {
  .hero { --hero-hold: 0.45; }
}
