/* ==========================================================================
   themes/portal.css — Apex Holding
   PURPOSE: World-SPECIFIC layout and decoration for the portal (index.html)
   and the 404 page. Nothing shared lives here.

   OWNS
     1.  Portal page atmosphere + no-script furniture
     2.  Preloader (5.0)          — the mark drawing itself, the lift, the
                                    script-free hard expiry
     3.  Hero "The Summit" (5.1)  — sky, three-layer ridge stack, grain,
                                    copy column, descend cue
     4.  The Apex Constellation   — the signature set-piece (spec 4.5)
     5.  Threshold gallery (5.2)  — expansion mechanics + viewport-edge wash
     6.  About + stat strip (5.3)
     7.  Founder seal clearance (5.4)
     8.  Highlights band (5.5)
     9.  404 "Lost between worlds"
     10. Reduced-motion additions for everything above

   RULES OBSERVED
     - The --w-* semantic tokens are DEFINED in tokens.css and only CONSUMED
       here. This file never redeclares one. (constellation.js writes --w-*
       inline on <html> for the ambient world tint; that is a runtime value,
       not a declaration, and it is reverted on leave.)
     - Cross-world colour comes from the --world-*-{base,accent,text} lookup
       in tokens.css, never a raw hex.
     - Anything on a scroll or pointer path animates transform / opacity only.
     - Every hover-revealed string is also revealed on :focus-within, and is
       permanently visible wherever there is no hover (touch).
     - No rule here fights a shared rule; the three deliberate overrides of
       motion.css are each commented at the point of override.

   Loaded LAST, after motion.css.
   ========================================================================== */


/* --------------------------------------------------------------------------
   1. PAGE ATMOSPHERE + NO-SCRIPT FURNITURE
   -------------------------------------------------------------------------- */

/* A single low champagne bloom over the top of the document, so the page reads
   as one continuous night sky rather than a stack of bands. Painted as the
   body's own background-image rather than a pseudo-element: a negative
   z-index layer would be covered by the body's background box, and a
   positive one would sit over the content. Static, never animated. */
[data-world="portal"] body {
  background-image:
    radial-gradient(
      120% 100% at 50% 0%,
      color-mix(in srgb, var(--w-accent) 10%, transparent),
      transparent 64%
    );
  background-repeat: no-repeat;
  background-position: center top;
  background-size: 100% 120vh;
}

/* The two chrome slots hold only a <noscript> fallback. With scripting on
   they are replaced wholesale, so these rules only ever apply without it. */
.noscript-nav,
.noscript-footer {
  padding: var(--space-5) var(--gutter);
  border-block-end: 1px solid var(--w-line);
  background-color: var(--w-base-2);
}

.noscript-footer {
  margin-block-start: var(--space-10);
  border-block-end: 0;
  border-block-start: 1px solid var(--w-line);
}

.noscript-nav ul,
.noscript-footer ul {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-6);
  padding: 0;
  margin: 0;
  list-style: none;
}

.noscript-footer p {
  margin-block-end: var(--space-4);
  color: var(--w-text-dim);
}

.noscript-nav a,
.noscript-footer a {
  display: inline-flex;
  align-items: center;
  min-block-size: 2.75rem;
  font-family: var(--font-utility);
  font-size: var(--step--1);
  letter-spacing: var(--track-wide);
}


/* --------------------------------------------------------------------------
   2. PRELOADER (5.0)
   The Apex mark draws itself as three strokes over a slow horizon gradient
   while a hairline shimmer completes beneath it. On .is-done the whole stack
   lifts and shrinks toward where the header mark will be.

   *** IT CANNOT TRAP THE PAGE ***
   Three independent guarantees, any one of which is sufficient:
     a) html:not(.js) .preloader { display: none }        — motion.css
     b) @media (prefers-reduced-motion) .preloader { display: none } — motion.css
     c) the portal-preload-expire animation below retires it at 2.6s with no
        script involvement whatsoever. Even if core.js never loads, if the
        module 404s, or if a runtime error kills the boot, the preloader still
        goes away and the page underneath is fully usable.
   The portal module then removes the node outright once the lift has played,
   so nothing is left behind in the tree.
   -------------------------------------------------------------------------- */

.preloader {
  /* Guarantee (c). animation-fill-mode: forwards holds the retired state. */
  animation: portal-preload-expire 2600ms linear 1 forwards;
}

/* Once script has taken charge, hand the fade back to motion.css's .is-done
   transition — a running forwards animation would otherwise out-rank it. */
.preloader.is-done {
  animation: none;
}

@keyframes portal-preload-expire {
  0%,
  92% {
    opacity: 1;
    visibility: visible;
  }
  100% {
    opacity: 0;
    visibility: hidden;
  }
}

.preloader__sky {
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background-color: var(--w-base);
  background-image: url("../../img/portal/horizon-gradient.svg");
  background-repeat: no-repeat;
  background-position: center bottom;
  background-size: cover;
  opacity: 0.85;
  transform-origin: 50% 100%;
  animation: portal-preload-sky 6s var(--ease-in-out-cinema) infinite alternate;
}

/* Slow breathing sky. Transform and opacity only. */
@keyframes portal-preload-sky {
  from { opacity: 0.72; transform: scale(1); }
  to   { opacity: 0.95; transform: scale(1.06); }
}

.preloader__stack {
  position: relative;
  z-index: 1;
  display: grid;
  justify-items: center;
  gap: var(--space-6);
  transform-origin: 50% 50%;
  transition: transform var(--dur-world) var(--ease-in-out-cinema);
}

/* The lift: the mark travels toward the header mark's resting position at the
   top-left of the page, shrinking as it goes. The offsets are expressed in
   viewport units so they track any screen without measurement. */
.preloader.is-done .preloader__stack {
  transform:
    translate3d(
      calc(var(--gutter) + 1.1rem - 50vw),
      calc((var(--header-h) / 2) - 50svh),
      0
    )
    scale(0.42);
}

/* motion.css gives .preloader__mark a breathing pulse. The portal wants the
   stroke DRAW instead, so the pulse is switched off here and the animation
   moves onto the individual paths. This is the one intended override. */
.preloader__mark {
  inline-size: clamp(4.5rem, 14vw, 7rem);
  block-size: auto;
  aspect-ratio: 64 / 48;
  color: var(--w-accent);
  animation: none;
  overflow: visible;
}

.preloader__stroke {
  stroke-dasharray: var(--draw-length);
  stroke-dashoffset: var(--draw-length);
  animation: apex-draw 900ms var(--ease-out-soft) both;
}

/* Path lengths measured from the geometry in the markup, rounded up so the
   dash always fully covers its own stroke. */
.preloader__stroke--peak  { --draw-length: 66; animation-delay: 120ms; }
.preloader__stroke--ridge { --draw-length: 44; animation-delay: 460ms; }
.preloader__stroke--rule  { --draw-length: 48; animation-delay: 760ms; }

/* motion.css owns the rail's position, height, clipping and ground. The two
   values it cannot know are that the portal renders it as a <span> — which
   must be blockified before inline-size means anything — and the portal's
   slightly wider measure. */
.preloader__bar {
  display: block;
  inline-size: min(14rem, 44vw);
}

/* The shimmer that "completes". A second, slower sweep behind motion.css's
   own ::after gives the bar a sense of filling rather than merely ticking. */
.preloader__bar::before {
  content: "";
  position: absolute;
  inset-block: 0;
  inset-inline-start: 0;
  inline-size: 100%;
  background-image: linear-gradient(
    to right,
    var(--w-accent),
    var(--w-accent-soft)
  );
  transform: scaleX(0);
  transform-origin: left center;
  animation: portal-preload-fill 2200ms var(--ease-in-out-cinema) forwards;
}

@keyframes portal-preload-fill {
  from { transform: scaleX(0); }
  to   { transform: scaleX(1); }
}


/* --------------------------------------------------------------------------
   3. HERO — "The Summit" (5.1)
   -------------------------------------------------------------------------- */

.hero {
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: center;
  /* vh first as the floor for engines without small-viewport units. */
  min-block-size: 100vh;
  min-block-size: 100svh;
  padding-block: calc(var(--header-h) + var(--space-8)) var(--space-12);
  overflow: hidden;
  isolation: isolate;
}

/* --- 3.1 Sky ---------------------------------------------------------- */

.hero__sky {
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background-image: url("../../img/portal/horizon-gradient.svg");
  background-repeat: no-repeat;
  background-position: center bottom;
  background-size: cover;
}

/* --- 3.2 Ridge stack --------------------------------------------------
   Two independent motion systems drive this stack and they never collide:

     motion.js  writes an inline transform on each .hero__ridge <img>, from
                [data-parallax] on scroll. Hard capped at 12px by motion.css
                and again inside motion.js.
     the portal module writes --drift-x / --drift-y on the .hero__ridges
                CONTAINER, from pointer position. Hard capped at 5px here.

   Because the container moves all three layers together, pointer drift can
   never change the gap between two crests — only scroll parallax can, and the
   ~50-unit clearance invariant documented in ridge-mid.svg is sized for it.
   Nothing here uses transform for LAYOUT, so an inline transform overwriting
   a layer is harmless by construction.
   --------------------------------------------------------------------- */

.hero__ridges {
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  transform: translate3d(
    clamp(-5px, var(--drift-x, 0px), 5px),
    clamp(-5px, var(--drift-y, 0px), 5px),
    0
  );
}

/* *** DO NOT SIMPLIFY THESE SELECTORS ***
   base.css carries `img[width][height] { height: auto }` as its CLS safety
   net, at specificity (0,2,1). block-size IS height, so a plain
   `.hero__ridge--far` at (0,1,0) would silently lose and every band would
   collapse to its intrinsic height. One type selector plus two classes ties
   that specificity and wins on source order, which is the whole reason the
   modifier is repeated alongside the base class below.

   The width/height attributes stay on the markup: they are correct practice,
   and these layers cannot shift anything anyway — they are absolutely
   positioned and therefore out of flow. */
img.hero__ridge {
  position: absolute;
  inset-inline-start: -2%;
  inline-size: 104%;
  max-inline-size: none;
}

img.hero__ridge.hero__ridge--far {
  inset-block-end: 22%;
  block-size: clamp(9rem, 30vh, 22rem);
}

img.hero__ridge.hero__ridge--mid {
  inset-block-end: 10%;
  block-size: clamp(9rem, 28vh, 20rem);
}

img.hero__ridge.hero__ridge--near {
  inset-block-end: 0;
  block-size: clamp(8rem, 26vh, 18rem);
}

/* --- 3.3 Grain -------------------------------------------------------- */

.hero__grain {
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  background-image: url("../../img/portal/grain.svg");
  background-repeat: repeat;
  background-size: 200px 200px;
  opacity: 0.04;
  mix-blend-mode: overlay;
}

/* --- 3.4 Content ------------------------------------------------------ */

.hero__inner {
  position: relative;
  z-index: 3;
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.08fr);
  align-items: center;
  gap: clamp(var(--space-7), 5vw, var(--space-10));
  inline-size: 100%;
  max-inline-size: var(--maxw-wide);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

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

.hero__title {
  font-size: var(--step-6);
  line-height: var(--lh-tight);
  letter-spacing: var(--track-tight);
  text-wrap: balance;
}

.hero__lede {
  max-inline-size: 34ch;
  font-size: var(--step-1);
  line-height: var(--lh-body);
  color: var(--w-text-dim);
}

/* Matched to the specificity of the .hero__copy > * + * rhythm above, and
   written after it, so it wins without reaching for !important. */
.hero__copy > .hero__actions {
  margin-block-start: var(--space-7);
}

/* --- 3.5 Descend cue --------------------------------------------------
   Centred, so it never contests the bottom-left corner the founder seal
   occupies. Quiet by default; the glyph drifts, the label does not move. */

.hero__descend {
  position: absolute;
  inset-block-end: var(--space-6);
  inset-inline: 0;
  z-index: 4;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-2);
  inline-size: max-content;
  min-block-size: 2.75rem;
  margin-inline: auto;
  padding: var(--space-2) var(--space-5);
  color: var(--w-text-dim);
  text-decoration: none;
  transition: color var(--dur-micro) var(--ease-out-soft);
}

.hero__descend:hover,
.hero__descend:focus-visible {
  color: var(--w-accent-soft);
}

.hero__descend-label {
  font-family: var(--font-utility);
  font-size: var(--step--1);
  letter-spacing: var(--track-widest);
  text-transform: uppercase;
}

.hero__descend-glyph {
  inline-size: 0.9rem;
  block-size: auto;
  opacity: 0.8;
  animation: portal-descend 3.2s var(--ease-in-out-cinema) infinite;
}

@keyframes portal-descend {
  0%, 68%, 100% { transform: translate3d(0, 0, 0); opacity: 0.65; }
  34%           { transform: translate3d(0, 7px, 0); opacity: 1; }
}


/* --------------------------------------------------------------------------
   4. THE APEX CONSTELLATION — the signature set-piece
   Markup is built by assets/js/worlds/constellation.js. The mount below keeps
   a fixed aspect-ratio box whether or not that ever happens, so CLS is 0 in
   every path: no-script, script-pending, script-failed and script-running.
   -------------------------------------------------------------------------- */

.constellation {
  position: relative;
  inline-size: 100%;
  max-inline-size: 44rem;
  margin-inline: auto;
  aspect-ratio: 720 / 460;
}

/* --- 4.1 No-script fallback ------------------------------------------- */

.constellation__fallback {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-5);
  color: var(--w-accent);
}

.constellation__fallback img {
  inline-size: 100%;
  block-size: auto;
  opacity: 0.85;
}

/* base.css already strips the padding and markers from any classed list; the
   only thing left to neutralise anywhere in this file is the UA block margin. */
.constellation__fallback-list {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-2) var(--space-6);
  margin: 0;
}

.constellation__fallback-list a {
  display: inline-flex;
  align-items: center;
  min-block-size: 2.75rem;
  font-family: var(--font-utility);
  font-size: var(--step--1);
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
}

/* Scoped to .js AND to .is-live, which the module adds only once the live
   figure is actually in the tree. So the fallback survives no-script, a failed
   dynamic import and a thrown error alike — and there is no flash on the happy
   path either, because the figure is injected in a single operation. */
.js .constellation.is-live .constellation__fallback {
  display: none;
}

/* --- 4.2 The live figure ---------------------------------------------- */

.constellation__svg {
  position: absolute;
  inset: 0;
  inline-size: 100%;
  block-size: 100%;
  overflow: visible;
  color: var(--w-accent);
}

/* Loose field stars. Static, low value, no rhythm — depth, not decoration. */
.constellation__field circle {
  fill: var(--w-text-dim);
  opacity: 0.22;
}

/* --- Orbits. Elliptical, rotated, hairline. Brighten with their node. --- */
.constellation__orbit {
  fill: none;
  stroke: var(--w-text-dim);
  stroke-width: 0.6;
  stroke-opacity: 0.16;
  transition:
    stroke var(--dur-world) var(--ease-out-soft),
    stroke-opacity var(--dur-world) var(--ease-out-soft);
}

.constellation__orbit.is-active {
  stroke: var(--w-accent);
  stroke-opacity: 0.55;
}

/* --- The Apex mark at the centre --------------------------------------- */
.constellation__core {
  fill: none;
  stroke: var(--w-accent);
  stroke-linecap: round;
  stroke-linejoin: round;
}

.constellation__core-glow {
  fill: var(--w-glow);
  opacity: 0.5;
}

/* --- Nodes -------------------------------------------------------------
   Each node is a real SVG <a> — natively focusable, natively activated by
   Enter, and carrying data-portal-link so transitions.js wipes in the
   destination accent. The global :focus-visible ring in base.css is the
   focus indicator; the halo below is the design state, not a substitute.
   The Portfolio node is styled by exactly these rules and no others: it is
   visually equal to its neighbours, never emphasised. ------------------- */

.constellation__node {
  cursor: pointer;
  text-decoration: none;
}

/* Generous invisible target. 30 user units on a 720-unit figure is ≥44px of
   tap area at every size this figure is rendered at. */
.constellation__hit {
  fill: transparent;
  stroke: none;
}

/* --depth is written by the rAF loop, 0 (far) → 1 (near). Deliberately NOT
   transitioned: the value already arrives once per frame, and a transition
   would restart every frame and lag a whole duration behind the orbit. */
.constellation__body {
  opacity: calc(0.68 + var(--depth, 1) * 0.32);
}

.constellation__halo {
  fill: var(--w-glow);
  opacity: 0.28;
  transform-box: fill-box;
  transform-origin: center;
  transition:
    opacity var(--dur-micro) var(--ease-out-soft),
    transform var(--dur-micro) var(--ease-out-soft);
}

/* No fill transition: --w-accent is a registered @property already tweened by
   html.mood-shift over --dur-world. Transitioning the fill as well would chase
   that tween and arrive a full duration late. */
.constellation__dot {
  fill: var(--w-accent);
}

.constellation__name {
  fill: var(--w-text);
  font-family: var(--font-utility);
  font-size: 17px;
  letter-spacing: 0.06em;
  opacity: 0.72;
  transition: opacity var(--dur-micro) var(--ease-out-soft);
}

.constellation__tagline {
  fill: var(--w-accent-soft);
  font-family: var(--font-body);
  font-size: 15px;
  font-style: italic;
  opacity: 0;
  transition: opacity var(--dur-reveal) var(--ease-out-soft);
}

/* Engagement. Hover AND focus-within produce exactly the same state, so a
   keyboard visitor and a touch visitor see everything a pointer visitor
   sees — including the tagline. */
.constellation__node:hover .constellation__halo,
.constellation__node:focus-visible .constellation__halo,
.constellation__node.is-engaged .constellation__halo {
  opacity: 0.75;
  transform: scale(1.35);
}

.constellation__node:hover .constellation__name,
.constellation__node:focus-visible .constellation__name,
.constellation__node.is-engaged .constellation__name {
  opacity: 1;
}

.constellation__node:hover .constellation__tagline,
.constellation__node:focus-visible .constellation__tagline,
.constellation__node.is-engaged .constellation__tagline {
  opacity: 1;
}

/* Labels are drawn in SVG user units, so they shrink with the figure. Bump
   the user-unit size as the box narrows to hold a readable rendered size. */
@media (max-width: 52em) {
  .constellation__name { font-size: 21px; }
  .constellation__tagline { font-size: 18px; }
}

@media (max-width: 36em) {
  .constellation__name { font-size: 27px; }
  .constellation__tagline { font-size: 23px; }
}


/* --------------------------------------------------------------------------
   5. THRESHOLD GALLERY (5.2)
   Three panels. The Portfolio is deliberately absent — see index.html.

   NOTE ON ANIMATING flex-grow: the performance contract bans animated
   width/height on SCROLL paths, because those recompute every frame against
   scroll position. This is a discrete, user-initiated hover/focus toggle on
   at most one element at a time, exactly the category the accordion's
   0fr→1fr animation falls into in components.css. It is also the only
   technique that produces a true cinematic expansion without hard-coding
   panel widths. Everything else in this block is opacity or transform.
   -------------------------------------------------------------------------- */

.threshold {
  position: relative;
}

/* --- 5.1 Viewport-edge accent wash ------------------------------------
   The destination palette bleeds in from both gutters while a panel is
   engaged. Colour comes from the --world-* lookup in tokens.css. --------- */

.threshold__band {
  position: relative;
  display: flex;
  gap: 1px;
  inline-size: 100%;
  margin: 0;
  background-color: var(--w-line);
  border-block: 1px solid var(--w-line);
  isolation: isolate;
}

.threshold__band::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 4;
  pointer-events: none;
  opacity: 0;
  background-image: linear-gradient(
    to right,
    color-mix(in srgb, var(--wash-accent, transparent) 30%, transparent),
    transparent 26%,
    transparent 74%,
    color-mix(in srgb, var(--wash-accent, transparent) 30%, transparent)
  );
  transition: opacity var(--dur-world) var(--ease-out-soft);
}

.threshold__band:has(.threshold__panel:is(:hover, :focus-within))::after {
  opacity: 1;
}

.threshold__band:has([data-panel-world="greppo"]:is(:hover, :focus-within)) {
  --wash-accent: var(--world-greppo-accent);
}

.threshold__band:has([data-panel-world="arz-faraya"]:is(:hover, :focus-within)) {
  --wash-accent: var(--world-arz-faraya-accent);
}

.threshold__band:has([data-panel-world="zaarour"]:is(:hover, :focus-within)) {
  --wash-accent: var(--world-zaarour-accent);
}

/* --- 5.2 Panels -------------------------------------------------------- */

.threshold__panel {
  --panel-accent: var(--w-accent);
  --panel-text: var(--w-text);

  position: relative;
  display: flex;
  flex: 1 1 0;
  min-inline-size: 0;
  min-block-size: clamp(26rem, 66vh, 42rem);
  overflow: hidden;
  isolation: isolate;
  transition: flex-grow var(--dur-world) var(--ease-in-out-cinema);
}

.threshold__panel[data-panel-world="greppo"] {
  --panel-accent: var(--world-greppo-accent);
  --panel-text: var(--world-greppo-text);
}

.threshold__panel[data-panel-world="arz-faraya"] {
  --panel-accent: var(--world-arz-faraya-accent);
  --panel-text: var(--world-arz-faraya-text);
}

.threshold__panel[data-panel-world="zaarour"] {
  --panel-accent: var(--world-zaarour-accent);
  --panel-text: var(--world-zaarour-text);
}

/* The dimming veil. Opacity only — no filter on a hover path, and no
   desaturation cost: laying the page ground over the art both darkens and
   drains the colour in a single cheap composite. */
.threshold__panel::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  background-color: var(--w-base);
  opacity: 0.22;
  transition: opacity var(--dur-world) var(--ease-out-soft);
}

.threshold__art {
  position: absolute;
  inset: 0;
  z-index: 1;
  inline-size: 100%;
  block-size: 100%;
}

.threshold__body {
  position: relative;
  z-index: 3;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  gap: var(--space-3);
  inline-size: 100%;
  min-inline-size: 0;
  padding: var(--space-7) var(--space-6) var(--space-8);
  background-image: linear-gradient(
    to top,
    color-mix(in srgb, var(--w-base) 88%, transparent),
    color-mix(in srgb, var(--w-base) 55%, transparent) 42%,
    transparent 78%
  );
  color: var(--panel-text);
}

.threshold__eyebrow {
  font-family: var(--font-utility);
  font-size: var(--step--1);
  letter-spacing: var(--track-widest);
  text-transform: uppercase;
  color: var(--panel-accent);
}

.threshold__title {
  font-family: var(--font-display);
  font-size: var(--step-3);
  line-height: var(--lh-snug);
  letter-spacing: var(--track-tight);
  color: var(--panel-text);
  text-wrap: balance;
}

.threshold__teaser {
  max-inline-size: 34ch;
  font-size: var(--step-0);
  line-height: var(--lh-body);
  color: var(--w-text-dim);
}

/* --- 5.3 The Enter link, stretched over the whole panel ---------------- */

.threshold__cta {
  display: block;
  margin-block-start: var(--space-3);
}

/* Deliberately NOT positioned: its ::after must resolve against
   .threshold__body so it can stretch across the whole panel. Same pattern as
   .card__link in components.css. */
.threshold__enter {
  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
  min-block-size: 2.75rem;
  padding-block: var(--space-2);
  color: var(--panel-accent);
  font-family: var(--font-utility);
  font-size: var(--step--1);
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
  text-decoration: none;
  transition: color var(--dur-micro) var(--ease-out-soft);
}

/* The arrow steps forward; the whole panel is the target. */
.threshold__enter::before {
  content: "→";
  display: inline-block;
  transform: translateX(0);
  transition: transform var(--dur-micro) var(--ease-out-soft);
}

/* The whole panel is the target. .threshold__body is the positioned ancestor
   and spans the panel's full width, so stretching upward by a viewport height
   covers the art as well; .threshold__panel { overflow: hidden } clips the
   overshoot, for hit-testing as well as for paint. The focus ring stays on
   the link text itself, which is what :focus-visible outlines. */
.threshold__enter::after {
  content: "";
  position: absolute;
  z-index: 4;
  inset-inline: 0;
  inset-block: -100vh 0;
}

.threshold__panel:hover .threshold__enter,
.threshold__panel:focus-within .threshold__enter {
  color: var(--panel-accent);
}

.threshold__panel:hover .threshold__enter::before,
.threshold__panel:focus-within .threshold__enter::before {
  transform: translateX(6px);
}

/* --- 5.4 Expansion mechanics, pointer devices with room to expand ------ */

@media (min-width: 62em) {
  .threshold__teaser {
    opacity: 0;
    transform: translateY(10px);
    transition:
      opacity var(--dur-reveal) var(--ease-out-soft),
      transform var(--dur-reveal) var(--ease-out-soft);
  }

  .threshold__panel:hover .threshold__teaser,
  .threshold__panel:focus-within .threshold__teaser {
    opacity: 1;
    transform: none;
  }

  /* Siblings recede. The engaged panel is the more specific, later rule, so
     it always wins over the sibling rule without an !important. */
  .threshold__band:has(.threshold__panel:is(:hover, :focus-within)) .threshold__panel {
    flex-grow: 0.78;
  }

  .threshold__band:has(.threshold__panel:is(:hover, :focus-within)) .threshold__panel::before {
    opacity: 0.68;
  }

  .threshold__band:has(.threshold__panel:is(:hover, :focus-within))
    .threshold__panel:is(:hover, :focus-within) {
    flex-grow: 2.2;
  }

  .threshold__band:has(.threshold__panel:is(:hover, :focus-within))
    .threshold__panel:is(:hover, :focus-within)::before {
    opacity: 0.06;
  }

  .threshold__panel:hover .threshold__title,
  .threshold__panel:focus-within .threshold__title {
    font-size: var(--step-4);
  }
}

/* --- 5.5 Stacked, always-open. Touch and narrow viewports get the whole
   teaser permanently: nothing here is ever hover-only. ------------------ */

@media (max-width: 61.99em) {
  .threshold__band {
    flex-direction: column;
  }

  .threshold__panel {
    min-block-size: clamp(20rem, 52vh, 28rem);
  }

  .threshold__panel::before {
    opacity: 0.3;
  }
}


/* --------------------------------------------------------------------------
   6. ABOUT + STAT STRIP (5.3)
   -------------------------------------------------------------------------- */

.about__lead {
  max-inline-size: var(--maxw-prose);
}

.about__lead > * + * {
  margin-block-start: var(--space-4);
}

.about__title {
  font-size: var(--step-4);
}

.about__body {
  font-size: var(--step-1);
  line-height: var(--lh-body);
  color: var(--w-text-dim);
}

.stat-strip {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 13rem), 1fr));
  gap: var(--space-5) var(--space-8);
  margin: var(--space-9) 0 0;
}

/* The one non-numeric figure. Sized down a step so "MENA → Europe" sits on
   the same optical weight as the two counted values beside it. */
.stat__value--text {
  font-size: var(--step-3);
}

.about__note {
  margin-block-start: var(--space-7);
  max-inline-size: var(--maxw-prose);
  font-size: var(--step--1);
  line-height: var(--lh-body);
  color: var(--w-text-dim);
}


/* --------------------------------------------------------------------------
   7. FOUNDER SEAL CLEARANCE (5.4)
   chrome.js pins .founder-seal to the bottom-left on --z-sticky. Nothing on
   the portal may sit over it or clip it:
     - the descend cue is centred, not cornered;
     - the hero clips its own overflow, but the seal is position:fixed and
       therefore outside every one of this page's stacking/clipping contexts;
     - the only thing that must yield is the footer's legal row, which would
       otherwise run underneath the seal on narrow screens.
   -------------------------------------------------------------------------- */

@media (max-width: 48em) {
  [data-world="portal"] .site-footer__legal {
    padding-inline-start: calc(var(--gutter) + 3.5rem);
  }
}

/* The seal belongs to the portal alone. 404.html must declare
   data-world="portal" to inherit the palette, which is the only signal
   chrome.js reads, so the floating seal is suppressed here instead. The
   quiet "Founder" control chrome.js places in the footer is untouched. */
[data-page="not-found"] .founder-seal {
  display: none;
}


/* --------------------------------------------------------------------------
   8. HIGHLIGHTS BAND (5.5)
   -------------------------------------------------------------------------- */

.highlights__grid {
  --grid-min: 19rem;
  margin: 0;
}

.highlights__grid > li {
  display: flex;
}

.highlights__grid .card {
  inline-size: 100%;
}

.highlights .card__media {
  --card-media-ratio: 3 / 2;
}

.highlights .card__meta {
  align-items: center;
}

/* The card link is stretched by .card__link in components.css; here it just
   reads as the quiet action it is. */
.highlights .card__link {
  margin-block-start: auto;
  padding-block-start: var(--space-4);
  color: var(--w-accent-soft);
  font-family: var(--font-utility);
  font-size: var(--step--1);
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
}


/* --------------------------------------------------------------------------
   9. 404 — "Lost between worlds"
   -------------------------------------------------------------------------- */

.lost {
  --section-maxw: 56rem;
  display: flex;
  align-items: center;
  min-block-size: 100vh;
  min-block-size: 100svh;
}

.lost__inner {
  text-align: center;
}

/* One rhythm rule, then four overrides written at the SAME specificity and
   AFTER it, so each simply wins on order. Mixing a `> * + *` selector with
   bare class overrides is the classic way to lose this fight. */
.lost__inner > * + * {
  margin-block-start: var(--space-5);
}

.lost__figure {
  inline-size: min(100%, 34rem);
  block-size: auto;
  margin-inline: auto;
  color: var(--w-accent);
}

.lost__inner > .lost__figure {
  margin-block-end: var(--space-7);
}

/* The broken link in the figure: the segment that should reach the apex is
   dashed and drifting, the apex star itself is only an outline. */
.lost__gap {
  stroke: var(--w-accent);
  stroke-opacity: 0.4;
  animation: portal-lost-drift 5s linear infinite;
}

.lost__apex {
  stroke: var(--w-accent);
  animation: portal-lost-drift 5s linear infinite reverse;
}

@keyframes portal-lost-drift {
  to { stroke-dashoffset: -24; }
}

.lost__inner .eyebrow--ruled {
  justify-content: center;
}

.lost__title {
  font-size: var(--step-5);
  line-height: var(--lh-tight);
}

.lost__lede {
  max-inline-size: 44ch;
  margin-inline: auto;
  font-size: var(--step-1);
  color: var(--w-text-dim);
}

.lost__actions {
  justify-content: center;
}

.lost__inner > .lost__actions {
  margin-block-start: var(--space-8);
}

.lost__worlds {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 15rem), 1fr));
  gap: var(--space-3);
  margin: 0;
  text-align: start;
}

.lost__inner > .lost__worlds {
  margin-block-start: var(--space-9);
}

.lost__world {
  --lost-accent: var(--w-accent);

  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  block-size: 100%;
  min-block-size: 2.75rem;
  padding: var(--space-4) var(--space-5);
  border: 1px solid var(--w-line);
  border-inline-start: 2px solid var(--lost-accent);
  border-radius: var(--radius-sm);
  background-color: var(--w-surface);
  text-decoration: none;
  transition:
    background-color var(--dur-micro) var(--ease-out-soft),
    border-color var(--dur-micro) var(--ease-out-soft),
    transform var(--dur-micro) var(--ease-out-soft);
}

.lost__world[data-lost-world="greppo"]     { --lost-accent: var(--world-greppo-accent); }
.lost__world[data-lost-world="arz-faraya"] { --lost-accent: var(--world-arz-faraya-accent); }
.lost__world[data-lost-world="zaarour"]    { --lost-accent: var(--world-zaarour-accent); }
.lost__world[data-lost-world="portfolio"]  { --lost-accent: var(--world-portfolio-accent); }
.lost__world[data-lost-world="support"]    { --lost-accent: var(--world-support-accent); }

.lost__world:hover,
.lost__world:focus-visible {
  background-color: var(--w-surface-2);
  border-color: var(--lost-accent);
  transform: translateY(-2px);
}

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

.lost__world-essence {
  font-size: var(--step--1);
  line-height: var(--lh-body);
  color: var(--w-text-dim);
}

.lost__contact {
  font-size: var(--step--1);
  color: var(--w-text-dim);
}

.lost__inner > .lost__contact {
  margin-block-start: var(--space-8);
}


/* --------------------------------------------------------------------------
   10. NARROW VIEWPORTS
   -------------------------------------------------------------------------- */

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

  .hero {
    padding-block-end: var(--space-11);
  }

  .hero__lede {
    max-inline-size: 46ch;
  }

  .constellation {
    max-inline-size: 34rem;
  }
}

@media (max-width: 40em) {
  .hero {
    padding-block-start: calc(var(--header-h) + var(--space-6));
  }

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

  .hero__actions .btn {
    inline-size: 100%;
  }

  .threshold__body {
    padding: var(--space-6) var(--space-5) var(--space-7);
  }

  .lost__actions .btn {
    inline-size: 100%;
  }
}


/* ==========================================================================
   11. REDUCED MOTION
   motion.css already stops the shared layers. This block covers only what
   this file introduced. The result is the same portal, holding still: the
   preloader never renders at all, the ridges sit where they belong, the
   constellation freezes into a star map, and every panel is simply open.
   ========================================================================== */

@media (prefers-reduced-motion: reduce) {

  /* The preloader is display:none from motion.css. Kill its animations too,
     so nothing is scheduled for an element that will never be painted. */
  .preloader,
  .preloader__sky,
  .preloader__stack,
  .preloader__stroke,
  .preloader__bar::before {
    animation: none;
    transition: none;
  }

  .preloader__stroke {
    stroke-dashoffset: 0;
  }

  /* Ridges: no pointer drift, no scroll drift, no residue. */
  .hero__ridges {
    transform: none;
  }

  .hero__descend-glyph {
    animation: none;
    opacity: 1;
  }

  /* Constellation: a static figure. The orbits and nodes are positioned once
     by the module and never move again; hover and focus still retint, but as
     an instant token swap rather than a cross-fade. */
  .constellation__orbit,
  .constellation__halo,
  .constellation__name,
  .constellation__tagline {
    transition: none;
  }

  .constellation__node:hover .constellation__halo,
  .constellation__node:focus-visible .constellation__halo,
  .constellation__node.is-engaged .constellation__halo {
    transform: none;
    opacity: 0.75;
  }

  /* Threshold gallery: no expansion, no travel. Every teaser is open, so no
     information depends on a state change that no longer animates. */
  .threshold__band,
  .threshold__band::after,
  .threshold__panel,
  .threshold__panel::before,
  .threshold__teaser,
  .threshold__enter,
  .threshold__enter::before {
    transition: none;
  }

  .threshold__teaser {
    opacity: 1;
    transform: none;
  }

  .threshold__band:has(.threshold__panel:is(:hover, :focus-within)) .threshold__panel,
  .threshold__band:has(.threshold__panel:is(:hover, :focus-within))
    .threshold__panel:is(:hover, :focus-within) {
    flex-grow: 1;
  }

  .threshold__panel:hover .threshold__title,
  .threshold__panel:focus-within .threshold__title {
    font-size: var(--step-3);
  }

  .threshold__panel:hover .threshold__enter::before,
  .threshold__panel:focus-within .threshold__enter::before {
    transform: none;
  }

  /* 404 figure holds still; the break in the constellation is still legible
     because it is drawn as a dashed line, not as motion. */
  .lost__gap,
  .lost__apex {
    animation: none;
  }

  .lost__world {
    transition: none;
  }

  .lost__world:hover,
  .lost__world:focus-visible {
    transform: none;
  }
}
