/* ==========================================================================
   themes/zaarour.css — Zaarour Gate
   PURPOSE: the world-specific layout and decoration for zaarour-gate/index.html.
   Everything here is prefixed .zg- and exists only on this page.

   THIS FILE NEVER REDEFINES A --w-* SEMANTIC TOKEN. tokens.css owns those; the
   [data-world="zaarour"] block there supplies alpine dawn, sun gold and glacier.
   Everything below consumes those tokens and adds structure on top.

   ==========================================================================
   JS CONTRACT — the custom properties assets/js/worlds/zaarour.js writes

     --zg-gate     0 → 1   on [data-zg-gate]. 0 = gate shut, 1 = fully parted.
                           Drives the two leaf translations, the seam light and
                           the bloom. DEFAULT IS 1, so with scripting off, with
                           reduced motion, or if the module fails to load, the
                           gate simply stands open. Nothing is ever locked shut.
     --zg-reveal   0 → 1   on [data-zg-gate]. Resolves the hero content. Its
                           floor is 0.72 opacity, never 0 — the headline is
                           legible at AA from the very first frame.
     --aurora-dur          per aurora band, cycle length
     --aurora-delay        per aurora band, NEGATIVE so bands start mid-cycle
                           and never look synchronised
     --pulse-dur           per location-map spot, pulse length
     --pulse-delay         per location-map spot, phase offset

   State classes the module toggles:  .is-live  (hero on screen — will-change)

   ==========================================================================
   MOTION DISCIPLINE
   Every animated property in this file is transform or opacity. The scroll
   path touches nothing else. Section 14 is the complete reduced-motion and
   scripting-off override: the gate is already open, the track collapses to a
   single screen, and every loop stops.
   ========================================================================== */


/* --------------------------------------------------------------------------
   1. PAGE GROUND
   A dawn that has not quite arrived: cold ground, one warm bloom low on the
   horizon. background-image only — base.css keeps ownership of the colour.
   -------------------------------------------------------------------------- */

[data-world="zaarour"] body {
  background-image:
    radial-gradient(
      120% 62% at 50% 0%,
      color-mix(in srgb, var(--w-base-2) 85%, transparent),
      transparent 70%
    ),
    radial-gradient(
      80% 40% at 50% 30%,
      color-mix(in srgb, var(--w-accent) 5%, transparent),
      transparent 65%
    );
  background-attachment: scroll;
  background-repeat: no-repeat;
}


/* --------------------------------------------------------------------------
   2. THE HERO TRACK
   The section is a tall scroll TRACK. The stage inside it is sticky, so the
   viewer stands still while the scroll distance is spent parting the gate.
   The track is the only element on the page taller than one screen, and it
   collapses to exactly one screen when motion is unavailable (section 13).
   -------------------------------------------------------------------------- */

.zg-hero {
  --zg-gate: 1;
  --zg-reveal: 1;

  position: relative;
  min-block-size: 210svh;
  background-color: var(--w-base);
}

.zg-hero__stage {
  position: sticky;
  inset-block-start: 0;
  display: grid;
  place-items: center;
  block-size: 100svh;
  overflow: hidden;
  isolation: isolate;
}


/* --------------------------------------------------------------------------
   3. SKY — dawn wash and slow aurora drift
   Three bands on the apex-aurora keyframe from motion.css, each given its own
   duration and a negative delay by the world module so they never beat in
   time with one another. Defaults live here so the sky still breathes if the
   module never runs.
   -------------------------------------------------------------------------- */

.zg-sky {
  position: absolute;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  pointer-events: none;
  background-image: linear-gradient(
    to bottom,
    var(--w-base) 0%,
    var(--w-base-2) 46%,
    color-mix(in srgb, var(--w-base-2) 70%, var(--w-base)) 100%
  );
}

/* The low band of warmth the gate is silhouetted against. */
.zg-sky__dawn {
  position: absolute;
  inset-inline: -10%;
  inset-block-end: 8%;
  block-size: 58%;
  background-image: radial-gradient(
    60% 100% at 50% 100%,
    color-mix(in srgb, var(--w-accent) 30%, transparent),
    color-mix(in srgb, var(--w-accent) 8%, transparent) 45%,
    transparent 72%
  );
}

.zg-sky__aurora {
  position: absolute;
  inset-inline: -20%;
  block-size: 46%;
  opacity: 0.35;
  will-change: transform;
  animation: apex-aurora var(--aurora-dur, 38s) var(--ease-in-out-cinema) var(--aurora-delay, 0s) infinite;
}

.zg-sky__aurora--a {
  inset-block-start: 4%;
  background-image: linear-gradient(
    100deg,
    transparent 8%,
    color-mix(in srgb, var(--w-accent-2) 34%, transparent) 40%,
    transparent 78%
  );
}

.zg-sky__aurora--b {
  inset-block-start: 18%;
  block-size: 38%;
  background-image: linear-gradient(
    76deg,
    transparent 12%,
    color-mix(in srgb, var(--w-focus) 26%, transparent) 52%,
    transparent 84%
  );
}

.zg-sky__aurora--c {
  inset-block-start: 30%;
  block-size: 34%;
  background-image: linear-gradient(
    118deg,
    transparent 16%,
    color-mix(in srgb, var(--w-accent) 22%, transparent) 48%,
    transparent 80%
  );
}


/* --------------------------------------------------------------------------
   4. RIDGELINE
   Two hand-authored silhouettes for depth. preserveAspectRatio="none" in the
   markup lets them stretch to any viewport without a media query.
   -------------------------------------------------------------------------- */

.zg-ridge {
  position: absolute;
  inset-inline: 0;
  inset-block-end: 0;
  z-index: 1;
  inline-size: 100%;
  block-size: clamp(9rem, 34svh, 20rem);
  pointer-events: none;
}


/* --------------------------------------------------------------------------
   5. THE GATE
   Two tapered monoliths standing on the ridge. Each leaf translates outward
   by its own width as --zg-gate runs 0 → 1; the seam light widens behind
   them. Transform and opacity only, both driven from one inherited variable,
   so the whole set-piece costs three composited layers and no layout.
   -------------------------------------------------------------------------- */

.zg-gate {
  position: absolute;
  /* Centred explicitly rather than by the stage's place-items, because the
     alignment of an absolutely positioned grid child is the one corner of
     grid layout engines still disagree about. The gate itself never carries
     any other transform — only its leaves do. */
  inset-inline-start: 50%;
  inset-block-end: clamp(4rem, 13svh, 9rem);
  z-index: 2;
  display: grid;
  grid-template-columns: 1fr 1fr;
  inline-size: min(62rem, 86vw);
  block-size: min(60svh, 32rem);
  transform: translateX(-50%);
  pointer-events: none;
}

.zg-gate__leaf {
  position: relative;
  z-index: 2;
  inline-size: 100%;
  block-size: 100%;
}

.zg-gate__leaf--left {
  grid-column: 1;
  transform: translate3d(calc(var(--zg-gate, 1) * -102%), 0, 0);
}

.zg-gate__leaf--right {
  grid-column: 2;
  transform: translate3d(calc(var(--zg-gate, 1) * 102%), 0, 0);
}

/* will-change is present only while the module is actually driving the hero,
   and is dropped the moment the hero leaves the viewport. */
.zg-hero.is-live .zg-gate__leaf {
  will-change: transform;
}

/* The blade of light in the seam. Scales from a hairline to the full opening
   rather than animating width, so it stays on the compositor. */
.zg-gate__light {
  position: absolute;
  inset-block: 6%;
  inset-inline-start: 50%;
  z-index: 1;
  inline-size: 40%;
  background-image: linear-gradient(
    to right,
    transparent 0%,
    color-mix(in srgb, var(--w-accent-soft) 30%, transparent) 26%,
    var(--w-accent-soft) 50%,
    color-mix(in srgb, var(--w-accent-soft) 30%, transparent) 74%,
    transparent 100%
  );
  opacity: calc(0.28 + var(--zg-gate, 1) * 0.62);
  transform: translateX(-50%) scaleX(calc(0.05 + var(--zg-gate, 1) * 0.95));
}

/* The bloom that spills past the leaves once they are clear of one another. */
.zg-gate__bloom {
  position: absolute;
  inset-block: -30%;
  inset-inline: -20%;
  z-index: 0;
  background-image: radial-gradient(
    46% 54% at 50% 56%,
    color-mix(in srgb, var(--w-accent) 44%, transparent),
    color-mix(in srgb, var(--w-accent) 12%, transparent) 42%,
    transparent 70%
  );
  opacity: calc(0.12 + var(--zg-gate, 1) * 0.68);
  transform: scale(calc(0.72 + var(--zg-gate, 1) * 0.28));
}


/* --------------------------------------------------------------------------
   6. HERO CONTENT
   Resolves with the gate. The opacity floor is 0.72 — at the darkest frame
   the headline still measures ~8:1 and the subline ~4.9:1 on this ground, so
   there is no moment at which the page is showing text that cannot be read.
   -------------------------------------------------------------------------- */

.zg-hero__content {
  position: relative;
  z-index: 3;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-5);
  inline-size: 100%;
  max-inline-size: var(--maxw-content);
  padding: var(--header-h) var(--gutter) 0;
  text-align: center;
  opacity: calc(0.72 + var(--zg-reveal, 1) * 0.28);
  transform: translate3d(0, calc((1 - var(--zg-reveal, 1)) * 14px), 0);
}

.zg-hero__title {
  max-inline-size: 18ch;
  font-size: var(--step-6);
  text-shadow: 0 2px 32px color-mix(in srgb, var(--w-base) 80%, transparent);
}

.zg-hero__sub {
  max-inline-size: 42ch;
  color: var(--w-text-dim);
  font-size: var(--step-1);
}

/* Centred button row. Used by the hero and again by the close. */
.zg-actions {
  justify-content: center;
  margin-block-start: var(--space-2);
}

/* Scroll cue. Decorative and aria-hidden; it fades out exactly as fast as the
   gate opens, and it is absent entirely when there is nothing to scroll for. */
.zg-hero__cue {
  position: absolute;
  inset-inline-start: 50%;
  inset-block-end: var(--space-6);
  z-index: 3;
  margin: 0;
  transform: translateX(-50%);
  color: var(--w-text-dim);
  font-family: var(--font-utility);
  font-size: var(--step--1);
  letter-spacing: var(--track-widest);
  text-transform: uppercase;
  opacity: calc(1 - var(--zg-gate, 1));
}

html:not(.js) .zg-hero__cue {
  display: none;
}


/* --------------------------------------------------------------------------
   7. VISION PILLARS
   Four light-panels. Each carries a top light-edge and a vertical wash that
   arrive with the reveal — scoped to .js so that without scripting the panels
   are simply, fully lit.
   -------------------------------------------------------------------------- */

.zg-pillars {
  --grid-min: 15rem;
}

.zg-pillar {
  position: relative;
  overflow: hidden;
  isolation: isolate;
}

/* The light-edge along the top of the panel. */
.zg-pillar::before {
  content: "";
  position: absolute;
  inset-block-start: 0;
  inset-inline: 0;
  z-index: 1;
  block-size: 1px;
  background-image: linear-gradient(
    to right,
    transparent,
    var(--w-accent) 50%,
    transparent
  );
  transform: scaleX(1);
  transform-origin: center;
}

/* The wash falling from that edge. */
.zg-pillar::after {
  content: "";
  position: absolute;
  inset-block-start: 0;
  inset-inline: 0;
  z-index: -1;
  block-size: 60%;
  background-image: linear-gradient(
    to bottom,
    color-mix(in srgb, var(--w-accent) 14%, transparent),
    transparent
  );
  opacity: 1;
}

.js .zg-pillar::before,
.js .zg-pillar::after {
  transition:
    opacity var(--dur-reveal) var(--ease-out-soft),
    transform var(--dur-reveal) var(--ease-out-soft);
}

.js .zg-pillar::before {
  transform: scaleX(0);
}

.js .zg-pillar::after {
  opacity: 0;
}

.js .zg-pillar.is-in::before {
  transform: scaleX(1);
}

.js .zg-pillar.is-in::after {
  opacity: 1;
}

.zg-pillar__index {
  font-family: var(--font-utility);
  font-size: var(--step--1);
  letter-spacing: var(--track-widest);
  color: var(--w-accent-soft);
}

.zg-pillar__note {
  margin-block-start: auto;
  padding-block-start: var(--space-4);
  border-block-start: 1px solid var(--w-line);
  color: var(--w-text-dim);
  font-size: var(--step--1);
}


/* --------------------------------------------------------------------------
   8. THE LOCATION MAP
   Hand-authored artwork in the "Why Zaarour" section. The frame's aspect ratio
   is 12 / 7, EXACTLY the ratio of the SVG viewBox, so with the default
   preserveAspectRatio the drawing fills the box edge to edge with no letterbox
   — which in turn means a chip placed at 72% / 50% in this layer lands on the
   feature drawn at 864 / 350 in the viewBox, at every viewport width. Changing
   either ratio without the other would drift the chips off their marks.
   -------------------------------------------------------------------------- */

.zg-map {
  /* Fourth child of the .zg-why grid, spanned across every column so it takes
     a full row of its own beneath the prose and the render. */
  grid-column: 1 / -1;
  margin-block-start: var(--space-4);
}

/* The ruled edge is an OVERLAID pseudo-element, not a border, and the frame
   carries no padding: a real border would eat into the content box under the
   global border-box sizing, the SVG would no longer be exactly 12 / 7, and
   every chip would drift a fraction off the feature it names. An absolutely
   positioned ring costs no layout, and it sits above the artwork — which an
   inset shadow would not, since the SVG paints over its parent's background. */
.zg-map__frame {
  position: relative;
  aspect-ratio: 12 / 7;
  border-radius: var(--radius-md);
  background-color: var(--w-base-2);
}

.zg-map__frame::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  border: 1px solid var(--w-line);
  border-radius: inherit;
  pointer-events: none;
}

.zg-map__art {
  display: block;
  inline-size: 100%;
  block-size: 100%;
  border-radius: var(--radius-md);
}

/* The layer is inert; only the chips inside it take the pointer, so the map
   never swallows a drag or a scroll. */
.zg-map__marks {
  position: absolute;
  inset: 0;
  /* Above the ruled edge, so a chip near the frame's border is never crossed
     by the hairline and its focus ring is never cut by it. */
  z-index: 2;
  pointer-events: none;
}

.zg-map__spot,
.zg-map__note {
  position: absolute;
  inset-inline-start: var(--zg-x, 50%);
  inset-block-start: var(--zg-y, 50%);
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin: 0;
  /* Pinned by the DOT, not by the centre of the chip: the label grows to the
     right and the mark itself stays exactly on its feature. */
  transform: translate(-0.375rem, -50%);
}

.zg-map__spot {
  min-block-size: 44px;
  padding-inline-end: var(--space-2);
  pointer-events: auto;
  text-decoration: none;
}

.zg-map__dot {
  position: relative;
  flex: none;
  inline-size: 0.75rem;
  block-size: 0.75rem;
  border-radius: 50%;
  background-color: var(--w-accent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--w-base) 70%, transparent);
  transition: transform var(--dur-micro) var(--ease-out-soft);
}

/* The soft pulse. Scale and opacity only; zaarour.js gives each ring its own
   duration and phase through [data-zg-hotspot] so they never blink together. */
.zg-map__dot::after {
  content: "";
  position: absolute;
  inset: -0.5rem;
  border: 1px solid var(--w-accent);
  border-radius: 50%;
  opacity: 0;
  animation: apex-pulse-ring var(--pulse-dur, 5.4s) var(--ease-out-soft) var(--pulse-delay, 0s) infinite;
}

/* The site's own mark reads a step louder than the context around it. */
.zg-map__spot--site .zg-map__dot {
  inline-size: 0.9375rem;
  block-size: 0.9375rem;
  box-shadow:
    0 0 0 3px color-mix(in srgb, var(--w-base) 70%, transparent),
    0 0 18px 2px var(--w-glow);
}

/* The static annotations carry no link, so they carry no dot either — a
   hairline tick keeps them visibly a different kind of mark. */
.zg-map__tick {
  flex: none;
  inline-size: 0.75rem;
  block-size: 1px;
  background-color: var(--w-accent-soft);
  opacity: 0.7;
}

.zg-map__label {
  padding: var(--space-1) var(--space-3);
  border: 1px solid var(--w-line);
  border-radius: var(--radius-pill);
  background-color: color-mix(in srgb, var(--w-base) 82%, transparent);
  color: var(--w-text-dim);
  font-family: var(--font-utility);
  font-size: var(--step--1);
  letter-spacing: var(--track-wide);
  white-space: nowrap;
}

.js .zg-map__label {
  transition:
    background-color var(--dur-micro) var(--ease-out-soft),
    border-color var(--dur-micro) var(--ease-out-soft),
    color var(--dur-micro) var(--ease-out-soft);
}

/* Hover and focus are given the SAME treatment, and neither reveals a single
   word that was not already on screen — the emphasis is the whole effect. */
.zg-map__spot:hover .zg-map__label,
.zg-map__spot:focus-visible .zg-map__label {
  border-color: var(--w-accent);
  background-color: color-mix(in srgb, var(--w-base) 94%, transparent);
  color: var(--w-text);
}

.zg-map__spot:hover .zg-map__dot,
.zg-map__spot:focus-visible .zg-map__dot {
  transform: scale(1.25);
}

.zg-map__spot:focus-visible {
  outline: 2px solid var(--w-focus);
  outline-offset: 4px;
  border-radius: var(--radius-pill);
}

/* --- The key beneath the map ------------------------------------------- *
   The complete text equivalent. Every mark on the drawing has an entry here,
   and every entry is legible whether or not the map above ever rendered. */

.zg-map__key {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
  gap: var(--space-5) var(--space-6);
  margin-block: var(--space-6) 0;
}

.zg-map__entry {
  padding-inline-start: var(--space-4);
  border-inline-start: 2px solid var(--w-line);
}

/* A spot link lands here, and the entry it landed on says so. */
.zg-map__entry:target {
  border-inline-start-color: var(--w-accent);
}

.zg-map__entry:target .zg-map__def {
  color: var(--w-text);
}

.zg-map__term {
  font-family: var(--font-utility);
  font-size: var(--step--1);
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
  color: var(--w-accent-soft);
}

.zg-map__def {
  margin: var(--space-2) 0 0;
  color: var(--w-text-dim);
  font-size: var(--step--1);
}


/* --------------------------------------------------------------------------
   8b. THE RENDER FRAMES
   The four photographic slots on this page all carry the client's own
   architectural renders, and each frame in the markup is set to that file's
   exact pixel ratio. object-fit:cover would still crop on any sub-pixel
   disagreement between the declared ratio and the rasterised box, so on this
   page the renders are CONTAINED instead: the ratios already match, so nothing
   letterboxes, and no rounding anywhere can shave an edge off a drawing. The
   masterplan is the reason this rule exists — a cropped masterplan is not a
   masterplan. Two attributes plus a class out-specify the components.css rule,
   and this file loads after it in any case.
   -------------------------------------------------------------------------- */

[data-world="zaarour"] .media-frame > img {
  object-fit: contain;
  object-position: center;
}

.zg-plan__caption {
  margin-block-start: var(--space-5);
  color: var(--w-text-dim);
  font-size: var(--step--1);
}


/* --------------------------------------------------------------------------
   9. THE JOURNEY — phase ribbon
   Each phase carries its own hairline, so the ribbon stays a ribbon at every
   breakpoint instead of breaking into fragments when the grid wraps.
   -------------------------------------------------------------------------- */

.zg-phases {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: var(--space-6) var(--space-5);
  margin: 0;
}

.zg-phase {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding-block-start: var(--space-6);
  border-block-start: 1px solid var(--w-line);
}

/* The marker straddling the hairline. */
.zg-phase::before {
  content: "";
  position: absolute;
  inset-block-start: 0;
  inset-inline-start: 0;
  inline-size: 0.5rem;
  block-size: 0.5rem;
  border-radius: 50%;
  background-color: var(--w-line);
  transform: translateY(-50%);
}

.zg-phase__name {
  font-family: var(--font-display);
  font-size: var(--step-2);
  line-height: var(--lh-snug);
  color: var(--w-text-dim);
}

.zg-phase__state {
  font-family: var(--font-utility);
  font-size: var(--step--1);
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
  color: var(--w-text-dim);
}

/* --- The lit phase ----------------------------------------------------- */

.zg-phase--current {
  border-block-start-color: var(--w-accent);
}

.zg-phase--current::before {
  inline-size: 0.75rem;
  block-size: 0.75rem;
  background-color: var(--w-accent);
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--w-accent) 22%, transparent);
}

.zg-phase--current .zg-phase__name {
  color: var(--w-text);
}

.zg-phase--current .zg-phase__state {
  color: var(--w-accent-soft);
}

.zg-journey__line {
  margin-block-start: var(--space-8);
  font-family: var(--font-display);
  font-size: var(--step-2);
  line-height: var(--lh-snug);
  color: var(--w-text);
}


/* --------------------------------------------------------------------------
   10. WHY ZAAROUR
   Verified geography only. The figure column is a plain measured list, not a
   stat wall — nothing here is being sold, it is being stated.
   -------------------------------------------------------------------------- */

.zg-why {
  display: grid;
  grid-template-columns: 1.1fr 0.9fr;
  gap: var(--space-9);
  align-items: start;
}

.zg-facts {
  display: grid;
  gap: var(--space-5);
  margin: 0;
}

.zg-fact {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  padding-block-end: var(--space-5);
  border-block-end: 1px solid var(--w-line);
}

.zg-fact:last-child {
  padding-block-end: 0;
  border-block-end: 0;
}

.zg-fact__value {
  font-family: var(--font-display);
  font-size: var(--step-3);
  line-height: var(--lh-tight);
  color: var(--w-accent);
}

.zg-fact__label {
  font-family: var(--font-utility);
  font-size: var(--step--1);
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
  color: var(--w-text-dim);
}


/* --------------------------------------------------------------------------
   11. REGISTER INTEREST
   A real form on a build with no server. Everything about that is said out
   loud in the markup rather than implied by a hopeful submit button.
   -------------------------------------------------------------------------- */

.zg-register {
  background-color: var(--w-base-2);
  border-block: 1px solid var(--w-line);
}

.zg-register__grid {
  display: grid;
  grid-template-columns: 0.85fr 1.15fr;
  gap: var(--space-9);
  align-items: start;
}

.zg-form {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
  padding: var(--space-7);
  border: 1px solid var(--w-line);
  border-radius: var(--radius-md);
  background-color: var(--w-surface);
}

.zg-form__row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-5);
}

.zg-form__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-4);
  align-items: center;
}

/* The live region. It is ALWAYS rendered and always reserves one line: a
   role="status" region that is display:none while empty is unreliably
   announced when it appears, and reserving the line also means a message
   landing costs zero layout shift. */
.zg-form__status {
  min-block-size: 1.6em;
  margin: 0;
  color: var(--w-accent-soft);
  font-family: var(--font-utility);
  font-size: var(--step--1);
  line-height: var(--lh-body);
}

/* Success reads as resolved, not alarming, so it steps to the plain text
   colour while a problem keeps the same lifted accent the field errors use.
   Both are announced identically — colour is never the only carrier of the
   difference, the wording always says which it is. */
.zg-form__status[data-state="sent"] {
  color: var(--w-text);
}

/* Small dim notes: the honest asides about how this build actually behaves. */
.zg-note {
  color: var(--w-text-dim);
  font-size: var(--step--1);
}

.zg-register__aside > * + * {
  margin-block-start: var(--space-5);
}


/* --------------------------------------------------------------------------
   12. THE PROMISE
   One line over the open gate. The artwork here is static in every state —
   the gate has already been opened by this point in the page.
   -------------------------------------------------------------------------- */

.zg-promise {
  position: relative;
  overflow: hidden;
  --section-space: var(--space-12);
}

.zg-promise__art {
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
}

.zg-promise__sky {
  position: absolute;
  inset: 0;
  background-image: radial-gradient(
    58% 76% at 50% 96%,
    color-mix(in srgb, var(--w-accent) 26%, transparent),
    color-mix(in srgb, var(--w-accent) 6%, transparent) 44%,
    transparent 74%
  );
}

.zg-promise__ridge {
  position: absolute;
  inset-inline: 0;
  inset-block-end: 0;
  inline-size: 100%;
  block-size: clamp(6rem, 22svh, 12rem);
}

.zg-promise .section__inner {
  position: relative;
  z-index: 1;
  text-align: center;
}

.zg-promise__line {
  max-inline-size: 20ch;
  margin-inline: auto;
  margin-block-start: var(--space-6);
  font-size: var(--step-5);
  line-height: var(--lh-snug);
}


/* --------------------------------------------------------------------------
   13. NARROW VIEWPORTS
   -------------------------------------------------------------------------- */

@media (max-width: 62em) {
  .zg-why,
  .zg-register__grid {
    grid-template-columns: 1fr;
    gap: var(--space-8);
  }

  .zg-phases {
    grid-template-columns: repeat(2, 1fr);
  }
}

@media (max-width: 40em) {
  .zg-hero {
    /* Less track on a phone: the same choreography, spent over less thumb. */
    min-block-size: 180svh;
  }

  /* The stage is exactly one screen tall and clips, so the hero content has to
     fit inside one screen on the shortest phone in circulation — after the
     header clearance and with the two large buttons stacked full width. One
     step down on the headline and a tighter rhythm is what buys that room. */
  .zg-hero__content {
    gap: var(--space-4);
  }

  .zg-hero__title {
    font-size: var(--step-5);
  }

  .zg-hero__sub {
    font-size: var(--step-0);
  }

  .zg-gate {
    inline-size: 92vw;
    block-size: min(50svh, 22rem);
  }

  .zg-phases {
    grid-template-columns: 1fr;
    gap: var(--space-5);
  }

  .zg-form {
    padding: var(--space-5);
  }

  .zg-form__row {
    grid-template-columns: 1fr;
  }

  .zg-map__key {
    gap: var(--space-5);
  }
}

/* The map chips are the one thing on this page that cannot simply reflow: the
   labels are pinned to points on a drawing, and below roughly 46em they would
   run into one another. So the LABEL becomes screen-reader-only rather than
   display:none — the link keeps its full accessible name, the mark stays on
   its feature, and the key beneath the map, which was always the real text
   equivalent, carries every word for sighted visitors too. */
@media (max-width: 46em) {
  .zg-map__label {
    position: absolute;
    inline-size: 1px;
    block-size: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    border: 0;
    clip-path: inset(50%);
    white-space: nowrap;
  }

  .zg-map__spot {
    justify-content: center;
    min-inline-size: 44px;
    min-block-size: 44px;
    padding-inline-end: 0;
    transform: translate(-50%, -50%);
  }

  /* The two static annotations are label and nothing else — with the label
     hidden a lone tick mark would be decoration that means nothing, so they
     step off the drawing altogether. Both keep their entries in the key
     below, so no word of them is lost at this width. */
  .zg-map__note {
    display: none;
  }
}


/* --------------------------------------------------------------------------
   14. REDUCED MOTION AND SCRIPTING OFF
   Both resolve to the same honest picture: THE GATE IS ALREADY OPEN. The
   track collapses to one screen so no scroll distance is spent on motion
   that is not going to happen, the aurora and the hotspot pulses stop, and
   the content is simply there.

   No !important anywhere. --zg-gate and --zg-reveal already default to 1, and
   the module refuses to install its loop under reduced motion, so nothing
   inline ever contradicts these rules.
   -------------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  .zg-hero {
    min-block-size: 100svh;
  }

  .zg-hero__stage {
    position: static;
    block-size: auto;
    min-block-size: 100svh;
  }

  .zg-sky__aurora {
    animation: none;
    will-change: auto;
    opacity: 0.32;
  }

  .zg-map__dot::after {
    animation: none;
    opacity: 0.4;
    transform: none;
  }

  .zg-map__dot {
    transition: none;
  }

  /* The emphasis stays, the movement goes. */
  .zg-map__spot:hover .zg-map__dot,
  .zg-map__spot:focus-visible .zg-map__dot {
    transform: none;
  }

  .zg-hero.is-live .zg-gate__leaf {
    will-change: auto;
  }

  .js .zg-pillar::before {
    transform: scaleX(1);
  }

  .js .zg-pillar::after {
    opacity: 1;
  }
}

html:not(.js) .zg-hero {
  min-block-size: 100svh;
}

html:not(.js) .zg-hero__stage {
  position: static;
  block-size: auto;
  min-block-size: 100svh;
}
