/* ============================================================
   GALAXY HERO — overrides for the home page (index.html)
   Loaded after style.css; only touches the hero and intro panel.
   ============================================================ */

/* The page opens on the galaxy alone. */
#hero {
  min-height: 100vh;
  position: relative;
}

/* The canvas is fixed so the galaxy stays put while the page scrolls
   over it — the camera flies into the disc rather than the disc
   scrolling away. */
#hero .hero-bg {
  position: fixed;
  inset: 0;
  z-index: 0;
  /* Cold near-black, lifting very slightly toward the galaxy's plane,
     with a vignette at the corners. Not flat black — the faint
     gradient is what stops the field banding. */
  background:
    radial-gradient(ellipse 85% 65% at 50% 50%,
      #0A0D16 0%,
      #05070D 55%,
      #030409 100%);
}
/* The home page, WebGL2: THE CANVAS STAYS BEHIND THE SHEET. The name's
   stars leave the hero and run down the page as a river, but they
   never leave the sky: the profile's sheet is CUT OPEN along their way
   (.sheet::before, --sheet-cut) and the sky shows through the cut. A
   first version raised the canvas over the sheet and masked the sky
   out in the shader; the user asked for the stars to stay in the sky
   with only their background open — and a real hole in the sheet also
   scrolls with the page for free, where a mask on a fixed canvas
   trailed it by a frame.
   The night gradient is on a fixed layer of its own (below), so the
   canvas is transparent where the sky is empty. */
:root:not([data-sky]):not([data-no-webgl]) #hero .hero-bg {
  background: none;
  pointer-events: none;
}
:root:not([data-sky]):not([data-no-webgl]) #hero::before {
  content: '';
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  /* The first layer is the soft halo behind the name. It used to come
     from style.css's `.hero-bg::after` (a blurred, breathing disc); it
     is switched off below and painted here, still, at the same size
     and strengths. It is fixed to the window, so the river's crossings
     and the mini galaxy's window pass over it as the page scrolls. */
  background:
    radial-gradient(circle min(37vh, 37vw) at 50% 50%,
      rgba(0, 212, 255, 0.10) 0%,
      rgba(108, 99, 255, 0.08) 42%,
      rgba(108, 99, 255, 0.03) 64%,
      transparent 100%),
    radial-gradient(ellipse 85% 65% at 50% 50%,
      #0A0D16 0%,
      #05070D 55%,
      #030409 100%);
}
:root:not([data-sky]):not([data-no-webgl]) #hero .hero-bg::after { display: none; }

/* No CSS vignette or grain over the galaxy: the composite pass in
   galaxy.js has its own. A fixed full-screen layer used to sit here
   (a radial vignette with SVG grain on ::after), but it never reset
   the `opacity: 0.025` it inherits from style.css, so it darkened the
   corners by 1.4 % at most — invisible — while being blended over the
   whole canvas on every frame. */
#hero .noise-overlay { display: none; }

/* ============================================================
   THE NAME — "ALI KOAIK", written in full by the stars
   ============================================================

   NOTE ON NAMING: these classes are prefixed `galaxy-`/`gname-`
   rather than the obvious `hero-name`/`name-*`. style.css already
   defines .hero-name for the other page, and that rule sets
   `-webkit-text-fill-color: transparent` over an accent GRADIENT plus
   `animation: fadeInUp ... both`. Inherited here it painted the word
   purple-to-cyan and, because `both` holds the animation's final
   opacity:1, pinned it visible from the first frame — overriding the
   opacity these rules animate. Keeping the namespaces separate is
   what stops that, so do not rename these back.

   The geometry note now lives on .gname-full itself, next to the
   numbers it constrains. */
.galaxy-name {
  /* The name's font size: everything round it is hung from this. */
  --S: clamp(2.8rem, min(17vmin, 19vw), 11rem);
  position: fixed;
  inset: 0;
  z-index: 2;
  pointer-events: none;
}

/* The name is positioned from the SAME anchor the star sampler uses,
   rather than by flex flow: the sampler centres the capitals on
   NAME_CENTER_Y, and no arrangement of line-heights and margins can
   be trusted to land on that exact line.

   --cap-shift is measured at runtime by alignNameToStars() in
   galaxy.js. translateY(-50%) centres the text BOX, which carries the
   font's full ascent and descent and therefore sits below the middle
   of the capitals; the shift corrects that residual against the real
   star centroid, so it is right for whatever font actually loads.

   CRITICAL: the geometry here is mirrored in galaxy.js — NAME_FONT,
   NAME_SIZE_VMIN, NAME_LETTER_SPACING and NAME_CENTER_Y. The star
   fill is sampled from a canvas rasterisation of the same text at the
   same size and position, so if these drift apart the stars fill a
   word that is not where the outline is drawn. Change both together.

   ONE rule for the whole name, not one per word: "Ali" and "Koaik"
   previously had separate weights, sizes and treatments, which left
   Koaik outlined and Ali solid — two typographic ideas fighting
   inside a single signature. */
.gname-full {
  position: absolute;
  left: 50%;
  top: 52%;                 /* = NAME_CENTER_Y in galaxy.js */
  margin: 0;
  white-space: nowrap;
  display: block;
  transform: translate(-50%, calc(-50% - var(--name-scroll, 0px)));

  /* THE NAME IS REAL TEXT (see "THE NAME IS REAL TEXT" in galaxy.js):
     the page's own heading face, heavy and tight, white with a soft
     light round it. The stars are sampled FROM this box, character by
     character, so any change of face, size or tracking here is
     followed by them — nothing to mirror in the JS. --name-text is
     written by galaxy.js every frame (the stars gather and the text
     resolves; the reader scrolls and it thins into stars): no
     transition on it. */
  font-family: 'Space Grotesk', system-ui, sans-serif;
  font-weight: 700;
  font-size: var(--S);
  letter-spacing: -0.035em;
  line-height: 1;
  /* Room for the glow inside the box: the entrance's mask clips to it. */
  padding: 0.6em 0.7em;
  /* The letters themselves are NOT painted: the name is written by
     stars (the 2D canvas over this box). What this element gives is
     the soft light behind them, in the letters' shape — a shadow of
     transparent text — and the box the stars are sampled from. */
  color: transparent;
  text-shadow:
    0 0 0.05em rgba(var(--accent-rgb, 156, 196, 255), 0.11),
    0 0 0.40em rgba(var(--accent-rgb, 156, 196, 255), 0.08);
  background: none;
  animation: none;

  opacity: calc(var(--name-text, 0) * (1 - var(--leave, 0)));
  will-change: opacity;
  /* The entrance: uncovered left to right as the stars land
     (--name-wipe, galaxy.js; 2 = all of it, also the default). */
  -webkit-mask-image: linear-gradient(90deg, #000 calc((var(--name-wipe, 2) - 0.20) * 100%), transparent calc((var(--name-wipe, 2) + 0.03) * 100%));
          mask-image: linear-gradient(90deg, #000 calc((var(--name-wipe, 2) - 0.20) * 100%), transparent calc((var(--name-wipe, 2) + 0.03) * 100%));
}

/* The sparks: a 2D canvas at device resolution over the name (placed
   and sized by buildSparks in galaxy.js). */
.gname-sparks {
  position: absolute;
  pointer-events: none;
  transform: translateY(calc(-1 * var(--name-scroll, 0px)));
  opacity: calc(1 - var(--leave, 0));
}
:root[data-no-webgl] .gname-sparks { display: none; }

/* ── Under the name ──
   "Ali Koaik" alone is a title card; this block is what makes it a
   portfolio. It is set like a title sequence, not like a web
   template: TYPE AND HAIRLINES ONLY. No pills, no icon buttons, no
   gradients, no glass, no glow — the user asked for elegant, and
   those are what made the earlier version read as machine-made.
   What carries it instead is weight (light), tracking (wide, on the
   small capitals), one accent (gold, used twice) and space.

   ONE box in normal flow (.gname-below), hung from the same anchor
   as the name and pushed down by a multiple of its size, so it rides
   the star centroid through resize; its children just stack. It
   shares --name-koaik with the name (the stars finish writing it,
   then the framing resolves under it), scrolls up with it
   (--name-scroll, galaxy.js) and fades on the leave (--leave). No
   transitions on any of those: they are written every frame. */
.gname-below {
  position: absolute;
  left: 50%;
  top: 52%;                 /* = NAME_CENTER_Y, as above */
  width: min(90vw, 44rem);
  transform: translate(-50%,
    calc(var(--cap-shift, 0px) + 0.50 * var(--S) - var(--name-scroll, 0px)));
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  opacity: calc(var(--name-koaik, 0) * (1 - var(--leave, 0)));
  will-change: opacity;
  font-family: 'Space Grotesk', system-ui, sans-serif;
  color: #DDE2EC;
}
/* A short gold rule: the one ornament. */
.gname-rule {
  width: 34px; height: 1px;
  margin-bottom: 1.35rem;
  background: var(--accent);
  opacity: 0.85;
}
/* What he is: small capitals, widely tracked. */
.gname-role {
  margin: 0;
  font-size: clamp(0.66rem, 1.25vmin, 0.76rem);
  font-weight: 500;
  letter-spacing: 0.42em;
  text-indent: 0.42em;          /* tracking adds space after the last letter; this recentres */
  text-transform: uppercase;
  color: #C9CFDB;
}
/* What he works on: one light line, the largest thing here. */
.gname-focus {
  margin: 0.8rem 0 0;
  max-width: 30em;
  font-size: clamp(1.1rem, 2.5vmin, 1.5rem);
  font-weight: 300;
  line-height: 1.35;
  letter-spacing: 0.005em;
  color: #F4F6FA;
  text-wrap: balance;
}
/* Where, and that he is available: plain words, a dash between. */
.gname-meta {
  margin: 1.5rem 0 0;
  font-size: 0.82rem;
  font-weight: 400;
  letter-spacing: 0.03em;
  color: #8892A6;
}
.gname-meta span + span::before {
  content: '';
  display: inline-block;
  width: 18px; height: 1px;
  margin: 0 0.85em;
  background: currentColor;
  opacity: 0.6;
  vertical-align: 0.3em;
}

/* Where to find him: three round glass buttons in the top-right
   corner, level with the bar, each with its logo. They belong to the
   name screen: they scroll away with it (--name-scroll), come in once
   the name is written and fade on the leave. */
.gname-social {
  position: absolute;
  top: 16px;
  right: clamp(14px, 2.4vw, 32px);
  display: flex;
  gap: 10px;
  pointer-events: auto;
  transform: translateY(calc(-1 * var(--name-scroll, 0px)));
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.6s ease, visibility 0s linear 0.6s;
}
:root.is-written .gname-social {
  opacity: calc(1 - var(--leave, 0));
  visibility: visible;
  transition-delay: 0s;
}
.gname-icon {
  display: grid;
  place-items: center;
  width: 46px; height: 46px;
  border: 1px solid rgba(255, 255, 255, 0.14);
  border-radius: 50%;
  color: #D5DBE8;
  background:
    linear-gradient(180deg, rgba(255, 255, 255, 0.13), rgba(255, 255, 255, 0.03) 46%, rgba(255, 255, 255, 0.06)),
    rgba(150, 175, 225, 0.05);
  -webkit-backdrop-filter: blur(14px) saturate(170%);
  backdrop-filter: blur(14px) saturate(170%);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.28),
    inset 0 -1px 0 rgba(var(--accent-rgb), 0.14),
    0 8px 24px rgba(0, 0, 0, 0.30);
  transition: color 0.3s ease, border-color 0.3s ease, background-color 0.3s ease,
              translate 0.35s cubic-bezier(0.2, 0.8, 0.2, 1);
}
.gname-icon svg { width: 21px; height: 21px; }
/* The résumé: the same glass, stretched to a pill to hold its word. */
.gname-cv {
  display: inline-flex;
  align-items: center;
  gap: 0.6em;
  width: auto;
  padding: 0 1.25em 0 1.05em;
  border-radius: 999px;
  font-family: 'Space Grotesk', system-ui, sans-serif;
  font-size: 0.82rem;
  font-weight: 500;
  letter-spacing: 0.08em;
  text-decoration: none;
  white-space: nowrap;
}
.gname-cv svg { width: 18px; height: 18px; }
.gname-icon:hover, .gname-icon:focus-visible {
  color: #FFFFFF;
  border-color: rgba(var(--accent-rgb), 0.6);
  background-color: rgba(var(--accent-rgb), 0.18);
  translate: 0 -2px;
}
.gname-icon:focus-visible { outline: 1px solid var(--accent-dim); outline-offset: 3px; }
/* The bar takes the whole top on a narrow screen: the three go under it. */
@media (max-width: 1100px) {
  .gname-social { top: 76px; gap: 8px; }
  .gname-icon { width: 42px; height: 42px; }
  .gname-cv { width: auto; }
}

.gname-arrow {
  display: inline-block;
  margin-left: 0.3em;
  letter-spacing: 0;
  font-family: var(--font-mono, 'JetBrains Mono', monospace);
  transition: translate 0.35s cubic-bezier(0.2, 0.8, 0.2, 1);
}

/* The rotating line from the film, last and quiet. Two lines are
   reserved so the swap never moves the block; only the inner span
   fades and lifts. */
.gname-quote {
  margin: 1.5rem 0 0;
  width: min(86vw, 40ch);
  min-height: calc(2 * 1.6em);
  display: flex;
  align-items: flex-start;
  justify-content: center;
  font-size: 0.84rem;
  font-weight: 300;
  font-style: italic;
  line-height: 1.6;
  letter-spacing: 0.02em;
  color: rgba(150, 160, 180, 0.8);
}
.gname-quote-text {
  opacity: 0;
  transform: translateY(6px);
  transition: opacity 0.9s ease, transform 0.9s ease;
}
.gname-quote.is-visible .gname-quote-text {
  opacity: 1;
  transform: translateY(0);
}
/* A short window has no room for the epigraph above the cue. */
@media (max-height: 700px) { .gname-quote { display: none; } }

/* ── The greeting, above the name ── */
.gname-hello {
  position: absolute;
  left: 50%;
  top: 52%;                 /* = NAME_CENTER_Y */
  margin: 0;
  transform: translate(-50%,
    calc(-50% + var(--cap-shift, 0px) - 0.62 * var(--S) - var(--name-scroll, 0px)));
  font-family: var(--font-mono, 'JetBrains Mono', monospace);
  font-size: clamp(0.78rem, 1.7vmin, 1rem);
  letter-spacing: 0.32em;
  text-indent: 0.32em;
  text-transform: uppercase;
  white-space: nowrap;
  color: var(--accent);
  opacity: calc(var(--name-koaik, 0) * (1 - var(--leave, 0)));
}

/* ── The cue: there is more below ── */
.gname-cue {
  position: fixed;
  left: 50%;
  bottom: 4vh;
  z-index: 2;
  transform: translateX(-50%);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  color: #9AA3B8;
  font-family: var(--font-mono, 'JetBrains Mono', monospace);
  font-size: 0.66rem;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  text-decoration: none;
  /* Gone within a fifth of a screen of scrolling. */
  opacity: calc(var(--name-koaik, 0) * max(0, 1 - var(--name-scroll-n, 0) * 5) * (1 - var(--leave, 0)));
  pointer-events: auto;
}
:root:not(.is-written) .gname-cue,
:root.is-launched .gname-cue { visibility: hidden; }
.gname-cue:hover, .gname-cue:focus-visible { color: #E6E9F2; }
.gname-cue-chevron {
  width: 9px; height: 9px;
  border-right: 1px solid currentColor;
  border-bottom: 1px solid currentColor;
  transform: rotate(45deg);
  animation: cueBob 2.2s ease-in-out infinite;
}
@keyframes cueBob {
  0%, 100% { transform: translateY(0) rotate(45deg); opacity: 0.6; }
  50%      { transform: translateY(5px) rotate(45deg); opacity: 1; }
}

@media (max-width: 640px) {
  .gname-below { width: 90vw; }
  .gname-rule { margin-bottom: 1.1rem; }
  .gname-role { letter-spacing: 0.32em; text-indent: 0.32em; }
  .gname-quote { margin-top: 1.7rem; font-size: 0.8rem; }
}

/* Reduced motion: the burst never plays, so the name is simply
   present rather than assembling. */
@media (prefers-reduced-motion: reduce) {
  .gname-full,
  .gname-below,
  .gname-hello,
  .gname-cue { opacity: 1; }
  .gname-quote-text { opacity: 1; transform: none; }
  .gname-arrow, .gname-social, .gname-icon { transition: none; }
}

/* ── No WebGL: the stars can never arrive ──
   Set by galaxy.js when a WebGL2 context cannot be created. The name
   is normally invisible because the star fill is the letterform; with
   no renderer there is no fill, so the whole hero would be a blank
   screen. Here the name is painted conventionally instead — a gradient
   fill in the site's accent colours — and the reveal tokens are
   forced on, since the burst that would have driven them never runs. */
:root[data-no-webgl] .gname-full { opacity: 1; color: #F5F8FF; }

:root[data-no-webgl] .gname-below,
:root[data-no-webgl] .gname-hello,
:root[data-no-webgl] .gname-cue {
  opacity: 1;
}
/* No WebGL2: nothing publishes --name-scroll, so the name block is
   placed in the page at the name screen instead of fixed, and simply
   scrolls away like the rest of the page. The name screen is the
   first screen now (the #intro / #burst spacers are gone), so top 0.
   The mini galaxy cannot be drawn either: its box is removed. */
:root[data-no-webgl] .galaxy-name { position: absolute; top: 0; bottom: auto; height: 100vh; }
:root[data-no-webgl] .launch-mark { display: none; }

/* ============================================================
   START THE TRIP — the orbit mark
   ============================================================
   The way on, at the end of the profile (#build). After a ring
   ship, a shuttle and two TARSes, an orbit diagram; since item 32
   the mark is an empty box (#miniGalaxy) where the sheet is cut
   open onto the mini galaxy (galaxy.js), and the words under it are
   a text link. Type and hairlines, the same language as the name
   screen; no figure, no box.
   It is a real link to system.html, and the BUTTON is still: nothing
   on it turns or grows (a take-off that grew the mark toward the
   reader was removed at the user's request — do not animate this
   button). Hover only changes colour and underlines the words. The
   press dives the whole page into the galaxy (galaxy.js, LAUNCH;
   .is-diving below) — the user's own request, 2026-10-20. */
.orbit-btn {
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  gap: 1.6rem;
  padding: 0.6rem 1.2rem;
  color: #B4BCCC;
  text-decoration: none;
  -webkit-tap-highlight-color: transparent;
}
/* THE MINI GALAXY's place. The box is empty and square: galaxy.js
   measures it, cuts a round WINDOW of that size in the sheet (the
   river ends in it) and gathers the name's stars behind it, in the
   sky. Nothing in the link animates. */
.launch-mark { display: block; width: clamp(200px, 34vmin, 300px); aspect-ratio: 1; }
.orbit-label {
  padding: 0.35em 0;
  font-size: 0.74rem;
  font-weight: 500;
  letter-spacing: 0.26em;
  text-transform: uppercase;
  white-space: nowrap;
  color: var(--accent);
  background: linear-gradient(currentColor, currentColor) 0 100% / 0 1px no-repeat;
  transition: background-size 0.45s cubic-bezier(0.2, 0.8, 0.2, 1);
}
.orbit-btn:hover .launch-mark, .orbit-btn:focus-visible .launch-mark { color: rgba(var(--accent-rgb), 0.75); }
.orbit-btn:hover .orbit-label, .orbit-btn:focus-visible .orbit-label { background-size: 100% 1px; }
.orbit-btn:focus-visible { outline: 1px solid var(--accent-dim); outline-offset: 8px; }

/* ============================================================
   THE PROFILE — below the name, a normal page
   ============================================================
   In the page flow after the name screen: scrolling past it moves the
   name up (galaxy.js) and brings this in. It exists once the name is
   written (.is-written). (--leave, which faded it on the old press, is
   no longer written by anything: its factors here are 1.)

   A top bar, then four sections in a column (profile.js fills them):
   #about (the bio, four numbers, the ways to reach him, the portrait
   frame), #education (the career, a timeline), #stack, #build. They
   sit on THE SHEET (below), which slides up over the fixed sky.

   A section comes in ONCE, as a whole: profile.js adds .is-in when it
   enters the view and its .pf-rise parts lift in, --d apart. */
.profile {
  position: relative;
  z-index: 3;
  color: #E6E9F2;
  font-family: 'Space Grotesk', system-ui, sans-serif;
  opacity: calc(1 - var(--leave, 0));
}
/* THE SHEET: one container from "Becoming" to the end of the page,
   with a background of its own — opaque ink, not the sky. The sky's
   canvas is fixed, so the sheet SLIDES UP OVER IT as the page scrolls:
   rounded top corners, a hairline of light along the top edge and a
   shadow thrown up onto the stars make it read as a surface in front.
   Nothing here is animated; the scroll is the motion.

   THE RIVER. The ink is on ::before so that it can be CUT: galaxy.js
   writes --sheet-cut, a clip-path that is the whole box minus the way
   the stars take (a channel from a notch in the top edge, down the
   margins and across between the sections, into a round window for
   the mini galaxy). Through the cut the fixed sky shows — the stars
   are still in it, behind the sheet. The cut is part of the page, so
   it scrolls with it exactly. .sheet-edge (an SVG under the ink, the
   same outline stroked) puts a hairline of light and a soft shadow
   on the inside of the cut: only the inner half of a stroke centred
   on the edge is not covered by the ink. Without the variable (no
   WebGL2, no script) the sheet is whole. */
.sheet {
  position: relative;
  padding: 0 clamp(16px, 4vw, 48px);
  border-radius: clamp(24px, 3.4vw, 44px) clamp(24px, 3.4vw, 44px) 0 0;
  box-shadow: 0 -28px 80px rgba(0, 0, 0, 0.6);
}
.sheet::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -1;
  border-radius: inherit;
  background: linear-gradient(180deg, #12151C 0, #0C0E13 70vh);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1);
  clip-path: var(--sheet-cut, none);
  pointer-events: none;
}
.sheet-edge {
  position: absolute;
  inset: 0;
  z-index: -2;
  width: 100%;
  height: 100%;
  overflow: hidden;
  border-radius: inherit;
  pointer-events: none;
}
.sheet-edge path { fill: none; stroke-linejoin: round; }
.sheet-edge-rim { stroke: rgba(255, 255, 255, 0.17); stroke-width: 2; }
.sheet-edge-shade--near { stroke: rgba(0, 0, 0, 0.30); stroke-width: 10; }
.sheet-edge-shade--far { stroke: rgba(0, 0, 0, 0.16); stroke-width: 30; }
:root:not(.is-written) .profile,
:root.is-launched .profile { display: none; }
/* THE DIVE (galaxy.js, LAUNCH): the press on the mini galaxy hands
   the screen to the sky. The page goes first, quickly — under it the
   sky is whole, the galaxy already turning faster — then the sky's
   backdrop, while the sky itself falls into the galaxy (that part is
   the canvas, and ends in .dive-void's black). */
:root.is-diving .profile { opacity: 0; pointer-events: none; transition: opacity 0.4s cubic-bezier(0.4, 0, 0.7, 1); }
:root.is-diving #hero::before { opacity: 0; transition: opacity 0.9s ease-in-out 0.25s; }
.pf-sec {
  --pf-ease: cubic-bezier(0.2, 0.8, 0.2, 1);
  max-width: 1120px;
  margin: 0 auto;
  padding: clamp(72px, 13vh, 150px) 0;
}
.pf-sec:focus { outline: none; }   /* where a jump from the bar puts focus (profile.js goTo): a place, not a control */
/* Every section is headed alike (.ab-label, .ab-title below): a small
   numbered label, "03 — Stack", then a very large title. */
.pf-head { margin-bottom: clamp(32px, 6vh, 60px); }
.pf-rise {
  opacity: 0;
  translate: 0 22px;
  transition: opacity 0.7s ease, translate 0.85s var(--pf-ease);
  transition-delay: calc(var(--d, 0) * 110ms);
}
.pf-sec.is-in .pf-rise { opacity: 1; translate: 0 0; }
.pf-rise:nth-child(2) { --d: 1; }
.pf-rise:nth-child(3) { --d: 2; }
.pf-rise:nth-child(4) { --d: 3; }
.pf-rise:nth-child(5) { --d: 4; }
.pf-rise:nth-child(6) { --d: 5; }

/* ── The top bar ──
   PLAIN: a small dark pill with a hairline border and a light blur,
   links in quiet type. The liquid-glass build (sheen, gradient rim,
   lit insets, gold drop) was dropped at the user's request for
   something simpler. The marker under the current link is a faint
   lighter pill; its left and width are set on a change of section
   only — not per frame — which is why they may have a transition. */
.site-nav {
  position: fixed;
  top: 16px;
  left: 50%;
  z-index: 5;
  display: flex;
  align-items: center;
  max-width: calc(100vw - 20px);
  padding: 5px;
  border: 1px solid rgba(255, 255, 255, 0.09);
  border-radius: 999px;
  background: rgba(16, 18, 24, 0.78);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.35);
  transform: translate(-50%, -12px);
  opacity: 0;
  visibility: hidden;
  transition: transform 0.35s ease, opacity 0.3s ease, visibility 0s linear 0.35s;
}
:root.nav-on .site-nav {
  transform: translate(-50%, 0);
  opacity: 1;
  visibility: visible;
  transition-delay: 0s;
}
/* The links scroll sideways inside the bar when six do not fit (a
   phone); no scrollbar is shown, profile.js keeps the current link
   in the middle. */
.site-nav-links {
  position: relative;
  display: flex;
  gap: 2px;
  min-width: 0;
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scrollbar-width: none;
  border-radius: 999px;
}
.site-nav-links::-webkit-scrollbar { display: none; }
/* Between the sections of this page and the pages of their own. */
.site-nav-sep {
  flex: 0 0 auto;
  align-self: center;
  width: 1px; height: 16px;
  margin: 0 6px;
  background: rgba(255, 255, 255, 0.14);
}
.site-nav-links a {
  position: relative;
  flex: 0 0 auto;
  padding: 9px 16px;
  border-radius: 999px;
  font-size: 0.875rem;
  font-weight: 500;
  line-height: 1.25;
  white-space: nowrap;
  color: #9AA3B5;
  transition: color 0.2s ease;
}
.site-nav-links a:hover, .site-nav-links a.is-active { color: #FFFFFF; }
.site-nav-links a:focus-visible { outline: 2px solid var(--accent-dim); outline-offset: 2px; }
/* On posts.html / activities.html there is no moving marker: the
   page's own link wears it. */
.site-nav-links a[aria-current="page"] { color: #FFFFFF; background: rgba(255, 255, 255, 0.09); }
.site-nav-blob {
  position: absolute;
  top: 0; bottom: 0;
  left: var(--bx, 0px);
  width: var(--bw, 0px);
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.09);
  transition: left 0.3s ease, width 0.3s ease;
}

/* ── #about and the career, on the reference layout ──
   (aliitawi.me, by the user's wish: the design of these two sections
   only — its structure, with this site's content, type and gold.)
   A strip; a small numbered label; a very large, heavy title; copy
   with its key names in bold beside a portrait frame; a 2x2 grid of
   numbers under the copy; and the career as a timeline — a line, a
   ring per entry, the date in gold capitals, what and where. */
.ab { padding-top: clamp(110px, 16vh, 170px); }
.ab-strip { display: flex; align-items: center; gap: 16px; margin: 0 0 clamp(36px, 6vh, 56px); }
.ab-mark { width: 9px; height: 9px; border-radius: 50%; background: var(--accent); box-shadow: 0 0 14px rgba(var(--accent-rgb), 0.7); }
.ab-word { font-size: 0.7rem; font-weight: 700; letter-spacing: 0.34em; text-transform: uppercase; color: var(--accent); }
.ab-strip-rule { width: 80px; height: 1px; background: rgba(255, 255, 255, 0.16); }
.ab-strip-line { font-size: 0.86rem; font-style: italic; font-weight: 300; color: #8892A6; }
.ab-label { margin: 0 0 0.5rem; font-size: 0.7rem; font-weight: 500; letter-spacing: 0.3em; text-transform: uppercase; color: #7C869A; }
.ab-title {
  margin: 0;
  font-size: clamp(2.8rem, 7.4vw, 5.6rem);
  font-weight: 700;
  line-height: 1;
  letter-spacing: -0.035em;
  color: #FFFFFF;
}
.ab-split {
  display: grid;
  grid-template-columns: minmax(0, 1.15fr) minmax(0, 0.85fr);
  gap: clamp(36px, 8vw, 128px);
  align-items: start;
  margin-top: clamp(44px, 9vh, 96px);
}
.ab-copy p { margin: 0 0 1.5em; max-width: 31em; font-size: 1.02rem; line-height: 1.9; color: #9AA3B8; }
.ab-copy p:first-child { font-size: clamp(1.1rem, 1.6vw, 1.25rem); color: #C3CAD8; }
.ab-copy strong { font-weight: 600; color: #FFFFFF; }
.ab-stats {
  display: grid;
  grid-template-columns: 1fr 1fr;
  max-width: 496px;
  margin-top: clamp(28px, 5vh, 52px);
  border: 1px solid rgba(255, 255, 255, 0.1);
  border-radius: 22px;
  background: rgba(6, 8, 14, 0.5);
  overflow: hidden;
}
.ab-stat { padding: 30px 28px 34px; }
.ab-stat:nth-child(odd) { border-right: 1px solid rgba(255, 255, 255, 0.1); }
.ab-stat:nth-child(n + 3) { border-top: 1px solid rgba(255, 255, 255, 0.1); }
.ab-num {
  font-size: clamp(2.4rem, 4.6vw, 3.5rem);
  font-weight: 700;
  line-height: 1;
  letter-spacing: -0.03em;
  color: var(--accent);
  text-shadow: 0 0 34px rgba(var(--accent-rgb), 0.35);
}
.ab-stat-label { margin-top: 14px; font-size: 0.62rem; font-weight: 500; letter-spacing: 0.2em; text-transform: uppercase; color: #7C869A; }
/* The ways to reach him, under the numbers and in the same frame: a
   row each — the mark, the name, where it leads, an arrow. Flat on
   the sheet (no glass: there is nothing behind it to blur). */
.ab-contact {
  max-width: 496px;
  margin: 14px 0 0;
  padding: 0;
  list-style: none;
  border: 1px solid rgba(255, 255, 255, 0.1);
  border-radius: 22px;
  background: rgba(6, 8, 14, 0.5);
  overflow: hidden;
}
.ab-contact:empty { display: none; }
.ab-contact li + li { border-top: 1px solid rgba(255, 255, 255, 0.1); }
.ab-link {
  display: grid;
  grid-template-columns: 20px auto minmax(0, 1fr) auto;
  align-items: center;
  column-gap: 16px;
  padding: 19px 26px 19px 28px;
  text-decoration: none;
  color: #C3CAD8;
  transition: background-color 0.25s ease, color 0.25s ease;
}
.ab-link-mark { width: 20px; height: 20px; color: #8892A6; transition: color 0.25s ease; }
.ab-link-name { font-size: 0.98rem; font-weight: 600; letter-spacing: -0.005em; color: #FFFFFF; }
.ab-link-to {
  justify-self: end;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-family: var(--j-mono, 'JetBrains Mono', ui-monospace, monospace);
  font-size: 0.78rem;
  color: #7C869A;
  transition: color 0.25s ease;
}
.ab-link-go {
  font-family: var(--j-mono, 'JetBrains Mono', ui-monospace, monospace);
  font-size: 0.9rem;
  color: #5E677A;
  transition: color 0.25s ease, translate 0.3s var(--pf-ease, ease);
}
@media (hover: hover) {
  .ab-link:hover { background-color: rgba(var(--accent-rgb), 0.07); }
  .ab-link:hover .ab-link-mark, .ab-link:hover .ab-link-go { color: var(--accent); }
  .ab-link:hover .ab-link-to { color: var(--accent-pale); }
  .ab-link:hover .ab-link-go { translate: 3px 0; }
}
.ab-link:active { background-color: rgba(var(--accent-rgb), 0.12); }
/* Inside the frame: the list clips, so the ring is drawn inward. */
.ab-link:focus-visible { outline: 2px solid var(--accent); outline-offset: -4px; border-radius: 18px; background-color: rgba(var(--accent-rgb), 0.07); }
.ab-link:focus-visible .ab-link-mark, .ab-link:focus-visible .ab-link-go { color: var(--accent); }
.ab-frame {
  position: relative;
  justify-self: end;
  width: min(100%, 400px);
  aspect-ratio: 4 / 5;
  margin-top: clamp(0px, 8vh, 80px);
  border: 1px solid rgba(var(--accent-rgb), 0.45);
  border-radius: 24px;
  background: radial-gradient(120% 90% at 30% 20%, #1a2136 0%, #0a0d16 60%, #05070c 100%);
  box-shadow: 0 30px 80px rgba(0, 0, 0, 0.55), 0 0 60px rgba(var(--accent-rgb), 0.07);
  overflow: hidden;
  display: grid;
  place-items: center;
}
.ab-frame img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.ab-initials { font-family: 'Orbitron', var(--font-mono, monospace); font-size: clamp(2.6rem, 6vw, 4.4rem); font-weight: 800; letter-spacing: 0.06em; color: rgba(255, 255, 255, 0.82); }

.tl { position: relative; margin: clamp(40px, 8vh, 80px) 0 0; padding: 0; list-style: none; }
.tl::before { content: ''; position: absolute; left: 0; top: -34px; bottom: -34px; width: 1px; background: linear-gradient(180deg, transparent, rgba(var(--accent-rgb), 0.45) 12%, rgba(var(--accent-rgb), 0.45) 80%, transparent); }
.tl-item {
  position: relative;
  display: grid;
  grid-template-columns: minmax(150px, 232px) minmax(0, 1fr);
  align-items: start;
  margin-left: 48px;
  padding: 34px 0 38px;
  border-bottom: 1px solid rgba(255, 255, 255, 0.07);
}
.tl-item:last-child { border-bottom: 0; }
.tl-dot { position: absolute; left: -55px; top: 37px; width: 13px; height: 13px; border: 2px solid var(--accent); border-radius: 50%; background: #06080d; box-shadow: 0 0 16px rgba(var(--accent-rgb), 0.55); }
/* A job (data.experience) wears a lit ring; a school an open one. */
.tl-item[data-kind="work"] .tl-dot { background: var(--accent); box-shadow: 0 0 0 3px rgba(var(--accent-rgb), 0.16), 0 0 18px rgba(var(--accent-rgb), 0.7); }
.tl-date { padding-top: 0.35em; font-size: 0.7rem; font-weight: 600; letter-spacing: 0.16em; text-transform: uppercase; color: var(--accent); }
/* The school's logo beside its entry, on a small tile: dark with a
   hairline for a white mark (42), white for a logo drawn for a white
   page (data-tile="light", USAL's seal). The initials sit under the
   image and show when there is none. */
.tl-body { display: grid; grid-template-columns: auto minmax(0, 1fr); align-items: center; column-gap: 22px; }
.tl-logo {
  position: relative;
  display: grid;
  place-items: center;
  width: 56px; height: 56px;
  border: 1px solid rgba(255, 255, 255, 0.12);
  border-radius: 15px;
  background: rgba(255, 255, 255, 0.04);
  overflow: hidden;
}
.tl-mono { font-size: 0.86rem; font-weight: 700; letter-spacing: 0.06em; color: #9AA3B8; }
.tl-logo img { position: absolute; inset: 0; width: 100%; height: 100%; padding: 13px; object-fit: contain; }
.tl-logo.has-logo .tl-mono { visibility: hidden; }
.tl-logo[data-tile="light"] { border-color: rgba(255, 255, 255, 0.5); background: #FFFFFF; }
.tl-logo[data-tile="light"] img { padding: 2px; }
.tl-body h3 { margin: 0; font-size: 1.22rem; font-weight: 700; letter-spacing: -0.01em; color: #FFFFFF; }
.tl-body p { margin: 0.45rem 0 0; font-size: 0.92rem; color: #8892A6; }
@media (max-width: 860px), (max-aspect-ratio: 1/1) {
  .ab-split { grid-template-columns: minmax(0, 1fr); }
  .ab-frame { justify-self: start; width: min(78vw, 320px); margin-top: 0; order: -1; }
  .ab-strip-rule { width: 36px; }
  .ab-stat { padding: 22px 20px 26px; }
  .tl-item { grid-template-columns: minmax(0, 1fr); gap: 10px; margin-left: 34px; }
  .tl-dot { left: -41px; }
  .tl-body { column-gap: 16px; align-items: start; }
  .tl-logo { width: 48px; height: 48px; border-radius: 13px; }
  .tl-logo img { padding: 11px; }
  .ab-link { column-gap: 13px; padding: 17px 18px 17px 20px; }
}

/* ── #stack ──
   Three stages on one line of flight: languages, then the tools they
   are written in, then the technologies around them. The line runs
   through the nodes and on past the last one; it draws on entry. */
.pf-stack {
  position: relative;
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: clamp(24px, 4vw, 56px);
  margin: 0;
  padding: 0;
  list-style: none;
}
.pf-stack::before {
  content: '';
  position: absolute;
  left: 23px; right: 0;
  top: 23px;
  height: 1px;
  background: linear-gradient(90deg, rgba(var(--accent-rgb), 0.6), rgba(var(--accent-dim-rgb), 0.45) 70%, rgba(var(--accent-dim-rgb), 0));
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 1.3s cubic-bezier(0.65, 0, 0.35, 1) 0.15s;
}
.pf-sec.is-in .pf-stack::before { transform: scaleX(1); }
.pf-stage { --tone: #E6E9F2; --tone-line: rgba(255, 255, 255, 0.14); --tone-fill: rgba(255, 255, 255, 0.04); --tone-text: #C9D0DD; --tone-hot: rgba(255, 255, 255, 0.38); --tone-fill-hot: rgba(255, 255, 255, 0.09); }
.pf-stage[data-tone="accent"], .pf-stage[data-tone="gold"] { --tone: var(--accent); --tone-line: rgba(var(--accent-rgb), 0.32); --tone-fill: rgba(var(--accent-rgb), 0.08); --tone-text: var(--accent-pale); --tone-hot: rgba(var(--accent-rgb), 0.7); --tone-fill-hot: rgba(var(--accent-rgb), 0.16); }
.pf-stage[data-tone="blue"] { --tone: var(--accent-dim); --tone-line: rgba(var(--accent-dim-rgb), 0.32); --tone-fill: rgba(var(--accent-dim-rgb), 0.1); --tone-text: var(--accent-pale); --tone-hot: rgba(var(--accent-dim-rgb), 0.75); --tone-fill-hot: rgba(var(--accent-dim-rgb), 0.2); }
.pf-stage-node {
  position: relative;
  display: grid;
  place-items: center;
  width: 46px; height: 46px;
  border: 1px solid var(--tone-line);
  border-radius: 50%;
  background: #080B13;                 /* solid: it sits on the line */
  color: var(--tone);
}
.pf-stage-node svg { width: 22px; height: 22px; }
.pf-stage-title { margin: 1.2rem 0 0.9rem; font-size: 1.2rem; font-weight: 600; letter-spacing: -0.01em; }
.pf-chips { display: flex; flex-wrap: wrap; gap: 8px; margin: 0; padding: 0; list-style: none; }
.pf-chips li {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 7px 13px;
  border: 1px solid var(--tone-line);
  border-radius: 9px;
  background: var(--tone-fill);
  font-size: 0.92rem;
  color: var(--tone-text);
  transition: translate 0.3s var(--pf-ease, ease), border-color 0.25s ease, background-color 0.25s ease,
              color 0.25s ease, box-shadow 0.3s ease;
}
.pf-logo { width: 20px; height: 20px; flex: none; object-fit: contain; transition: scale 0.3s var(--pf-ease, ease); }
/* Under the pointer a chip lifts a little and lights up in its
   stage's tone; its logo grows a touch. Not a link — the arrow stays. */
@media (hover: hover) {
  .pf-chips li:hover {
    translate: 0 -2px;
    border-color: var(--tone-hot);
    background: var(--tone-fill-hot);
    color: #FFFFFF;
    box-shadow: 0 10px 22px -14px var(--tone);
  }
  .pf-chips li:hover .pf-logo { scale: 1.14; }
}

/* ── #build ── the way on */
/* A screen of its own, its content centred, so the mark is never
   pressed up under the top bar at the end of the page. */
.pf-build {
  min-height: 100vh;
  min-height: 100svh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding-top: clamp(104px, 14vh, 150px);
  padding-bottom: clamp(48px, 9vh, 110px);
  text-align: center;
}
.profile-launch-lead {
  max-width: 34em;
  margin: 1.3rem auto 0;
  font-size: clamp(1.05rem, 1.7vw, 1.25rem);
  line-height: 1.5;
  color: #C9CFDB;
}
.profile-launch { margin-top: clamp(36px, 6vh, 64px); }

/* One column: a narrow or portrait viewport has no room for two. */
@media (max-width: 860px), (max-aspect-ratio: 1/1) {
  /* The stages stack and the line turns to run down their nodes. */
  .pf-stack { grid-template-columns: minmax(0, 1fr); gap: 34px; }
  .pf-stack::before {
    left: 23px; right: auto;
    top: 23px; bottom: 0;
    width: 1px; height: auto;
    background: linear-gradient(180deg, rgba(var(--accent-rgb), 0.6), rgba(var(--accent-dim-rgb), 0.45) 70%, rgba(var(--accent-dim-rgb), 0));
    transform: scaleY(0);
    transform-origin: top;
  }
  .pf-sec.is-in .pf-stack::before { transform: scaleY(1); }
  .pf-stage { position: relative; padding-left: 66px; }
  .pf-stage-node { position: absolute; left: 0; top: 0; }
  .pf-stage-title { margin-top: 0.55rem; }
}
@media (max-width: 480px) {
  .site-nav { top: 12px; padding: 4px; }
  .site-nav-links { gap: 0; }
  .site-nav-links a { padding: 9px 12px; font-size: 0.84rem; }
  .site-nav-sep { margin: 0 3px; }
}
@media (prefers-reduced-motion: reduce) {
  .pf-rise { opacity: 1; translate: 0 0; transition: none; }
  .pf-stack::before { transform: none; transition: none; }
  .pf-chips li, .pf-logo, .ab-link, .ab-link-mark, .ab-link-to, .ab-link-go { transition: none; }
  .pf-chips li:hover, .ab-link:hover .ab-link-go { translate: none; }
  .pf-chips li:hover .pf-logo { scale: none; }
  .site-nav, .site-nav-blob { transition: none; }
  .gname-cue-chevron { animation: none; }
}

/* ============================================================
   PLAIN PAGES — posts.html, activities.html (and work.html on a phone)
   ============================================================
   Pages of their own, reached from the top bar. No canvas: the night
   is a CSS gradient with a handful of painted stars, and the bar is
   the same (shown by a static .nav-on on the root). Each page's own
   sheet (posts.css, activities.css, work.css) lays out the rest. The
   bar's section links go back to ../index.html#section. */
body.page {
  min-height: 100vh;
  min-height: 100svh;
  display: flex;
  flex-direction: column;
  color: #E6E9F2;
  font-family: 'Space Grotesk', system-ui, sans-serif;
  background:
    radial-gradient(1.6px 1.6px at 12% 18%, rgba(255, 255, 255, 0.7), transparent 60%),
    radial-gradient(1.3px 1.3px at 27% 64%, rgba(255, 255, 255, 0.5), transparent 60%),
    radial-gradient(1.8px 1.8px at 41% 31%, rgba(255, 214, 170, 0.7), transparent 60%),   /* a warm star, not the accent */
    radial-gradient(1.4px 1.4px at 58% 82%, rgba(255, 255, 255, 0.55), transparent 60%),
    radial-gradient(1.6px 1.6px at 69% 22%, rgba(190, 214, 255, 0.75), transparent 60%),
    radial-gradient(1.3px 1.3px at 83% 57%, rgba(255, 255, 255, 0.5), transparent 60%),
    radial-gradient(1.7px 1.7px at 91% 12%, rgba(255, 255, 255, 0.65), transparent 60%),
    radial-gradient(1.4px 1.4px at 6% 88%, rgba(190, 214, 255, 0.6), transparent 60%),
    radial-gradient(ellipse 85% 65% at 50% 42%, #0B0F1A 0%, #05070D 55%, #030409 100%);
  background-attachment: fixed;
}

/* ============================================================
   AFTER THE NAME — shared tokens, the gate, the seal
   ============================================================
   The sky stays (the canvas is fixed); glass panels sit over it.
   Gold is the bulge, blue-white is the arms; nothing else gets a
   colour. (The waypoints trip that used to follow the planet was
   removed on 2026-09-25; it will be redesigned.) */
:root {
  /* THE ACCENT — one family, change it here (and in style.css :root,
     which pages load first). Literal copies that cannot read a CSS
     variable: the cursors in cursor.css, the map in system.js, the
     constants at the top of emblems.js, beyond.html. */
  --accent: #9CC4FF;                 /* ice blue */
  --accent-rgb: 156, 196, 255;       /* for rgba(var(--accent-rgb), a) */
  --accent-dim: #6F9CE8;             /* the same hue, deeper: second voice, focus rings */
  --accent-dim-rgb: 111, 156, 232;
  --accent-pale: #DDEBFF;            /* text on an accent-tinted fill */
  --j-text: #E6E9F2;
  --j-muted: #9AA3B8;
  --j-ink: rgba(9, 12, 20, 0.58);
  --j-line: rgba(255, 255, 255, 0.09);
  --j-mono: 'JetBrains Mono', ui-monospace, monospace;
}

/* The name lets go as the stars leave it (--release from galaxy.js),
   and gives up the pointer once it is mostly gone. */
/* --warp-fade is warp.js taking the name away under the ship as the
   trip begins; a plain multiply, no transition, same as --release. */
.galaxy-name {
  opacity: calc((1 - var(--release, 0)) * (1 - var(--warp-fade, 0)));
}
:root.is-released .galaxy-name { visibility: hidden; }

/* The gate. The planet does not exist until the launch button is
   pressed: with it collapsed the page ends on the finished name and
   the reader cannot scroll past it. galaxy.js sets .is-launched on
   the click. */
:root:not(.is-launched) .planet { display: none; }

/* THE SEAL — system.html and work.html carry .is-launched (and
   .is-sealed) from the start: the state the old one-page flow reached
   at the launch. The hero collapses to nothing, so the page's own top
   is its first section, and only the fixed sky of #hero stays. */
:root.is-launched #hero { min-height: 0; height: 0; padding: 0; margin: 0; }

/* While the launch sequence plays the reader cannot scroll: overflow
   hidden on the root keeps the scroll offset and still lets warp.js
   scrollTo() under its cover. (Touch is refused in warp.js itself;
   iOS ignores this rule for gestures.) */
:root.is-warping { overflow: hidden; }

/* ============================================================
   ARRIVAL — the planet
   ============================================================
   A screen of its own after the name — the end of the page. The
   system is system.js (three.js) on the canvas; the sky behind it is
   the fixed galaxy canvas, so this section is transparent. */
.planet {
  position: relative;
  z-index: 3;
  height: 100vh;
  overflow: hidden;
  color: var(--j-text);
  font-family: 'Space Grotesk', system-ui, sans-serif;
}
/* The system IS the window: pinned to the viewport edge to edge, so
   the browser's borders are the only frame. The :not()s beat
   style.css's `section:not(#hero):not(#contact)` (2,0,1), which
   capped this section at 1200px, centred and padded — on a wider
   window the system was clipped at that box. Fixed, not 100vh: on
   phones 100vh is taller than the visible area (the toolbar). The
   page behind is one screen after the launch, so nothing scrolls. */
:root.is-launched #planet:not(#hero):not(#contact):not(.planet--css) {
  position: fixed;
  inset: 0;
  width: auto;
  height: auto;
  max-width: none;
  margin: 0;
  padding: 0;
}
.planet canvas#planetCanvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  touch-action: none;        /* OrbitControls owns the gestures */
  cursor: grab;
}
.planet canvas#planetCanvas:active { cursor: grabbing; }
.planet-caption {
  position: absolute;
  left: 16px; right: 16px;
  bottom: 7vh;
  text-align: center;
  pointer-events: none;
  opacity: var(--planet-in, 1);   /* system.js: in with the reveal, out with the pull-back */
}
/* The dash in "Arrival — Koaik" and "Pilot — orbiting Koaik". */
.sep { color: var(--j-muted); margin: 0 0.4em; }
.planet-label {
  margin: 0 0 0.45rem;
  font-family: var(--j-mono);
  font-size: 0.74rem;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--accent);
}
.planet-sub {
  margin: 0 0 0.6rem;
  font-size: 1.05rem;
  letter-spacing: 0.02em;
  color: var(--j-text);
}
.planet-hint {
  margin: 0;
  font-family: var(--j-mono);
  font-size: 0.72rem;
  letter-spacing: 0.08em;
  color: var(--j-muted);
}
/* A chevron under the hint that breathes downward: the one cue on
   this screen that the trip continues. Sine-eased, slow, ambient. */
.planet-hint:empty { display: none; }
.planet-hint::after {
  content: '';
  display: block;
  width: 8px; height: 8px;
  margin: 0.9rem auto 0;
  border-right: 1px solid var(--j-muted);
  border-bottom: 1px solid var(--j-muted);
  transform: rotate(45deg);
  animation: hintBob 2.2s ease-in-out infinite;
}
@keyframes hintBob {
  0%, 100% { transform: translateY(0) rotate(45deg); opacity: 0.55; }
  50%      { transform: translateY(6px) rotate(45deg); opacity: 1; }
}
/* ============================================================
   THE SYSTEM — labels, HUD and the panel over the three.js canvas
   ============================================================
   system.js positions the labels and draws the map; hud.js writes the
   text. Everything here is class-driven (.is-system on the section,
   .is-open on the panel, .is-hot/.is-active on labels), so it can
   transition; the only JS-driven token is --planet-in on the caption. */
.sys-labels { position: absolute; inset: 0; pointer-events: none; }
.sys-label {
  position: absolute; left: 0; top: 0;
  display: inline-flex; align-items: center; gap: 6px;
  padding: 4px 8px 4px 6px;
  margin: 0;
  border: 1px solid transparent;
  border-radius: 999px;
  background: rgba(9, 12, 20, 0.35);
  color: var(--j-text);
  font-family: var(--j-mono);
  font-size: 0.66rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  white-space: nowrap;
  cursor: pointer;
  pointer-events: auto;
  will-change: transform;
  transition: border-color 0.25s ease, background 0.25s ease, color 0.25s ease;
}
.sys-label-dot { width: 5px; height: 5px; border-radius: 50%; background: var(--j-muted); flex: 0 0 auto; transition: background 0.25s ease, box-shadow 0.25s ease; }
.sys-label--sun .sys-label-dot { background: var(--accent); box-shadow: 0 0 8px var(--accent); }
.sys-label:hover, .sys-label.is-hot, .sys-label:focus-visible { border-color: rgba(var(--accent-rgb), 0.55); background: rgba(9, 12, 20, 0.7); color: #fff; }
.sys-label.is-hot .sys-label-dot, .sys-label:hover .sys-label-dot { background: var(--accent); box-shadow: 0 0 8px var(--accent); }
.sys-label.is-active { border-color: var(--accent); color: var(--accent); }
.sys-label:focus-visible { outline: 2px solid var(--accent-dim); outline-offset: 2px; }
/* The reticle: a dashed ring around the hovered body, turning slowly;
   solid gold and still once the body is selected. Sized and placed
   by system.js each frame. */
.sys-reticle {
  position: absolute; left: 0; top: 0;
  width: 40px; height: 40px;
  border-radius: 50%;
  border: 1px dashed rgba(230, 233, 242, 0.7);
  box-shadow: 0 0 12px rgba(var(--accent-dim-rgb), 0.25);
  pointer-events: none;
  animation: reticleTurn 14s linear infinite;
}
.sys-reticle.is-locked { border-style: solid; border-color: rgba(var(--accent-rgb), 0.4); box-shadow: none; animation: none; }
@keyframes reticleTurn { to { rotate: 360deg; } }

/* The HUD fades in once the reader has the controls. */
.sys-hud { position: absolute; inset: 0; pointer-events: none; opacity: 0; visibility: hidden; transition: opacity 0.8s ease 0.3s, visibility 0s 1.1s; }
.planet.is-system .sys-hud { opacity: 1; visibility: visible; transition: opacity 0.8s ease 0.3s, visibility 0s; }
/* The collapse (system.js): the HUD leaves with the system, and a
   black cover, driven per frame by system.js (no transition), takes
   the screen before the next page. */
.planet.is-system.is-collapsing .sys-hud { opacity: 0; visibility: hidden; transition: opacity 0.6s ease, visibility 0s 0.6s; }
/* The black a collapse ends in: the system's (system.js) and the home
   page's dive (galaxy.js); its opacity is written a frame at a time. */
.sys-void, .dive-void { position: fixed; inset: 0; z-index: 20; background: #000; opacity: 0; pointer-events: none; }
/* Top left: [← Back] [3D | List] [pilot chip], one row; on a tall
   screen the chip drops to a second row (the ⌂ System button is top
   right there). The box itself takes no pointer: only its controls. */
.sys-top {
  position: absolute; top: 18px; left: 18px;
  display: flex; flex-wrap: wrap; align-items: center; gap: 10px;
  max-width: calc(100% - 36px);
  pointer-events: none;
}
.sys-nav { display: flex; align-items: center; gap: 8px; pointer-events: none; }
.sys-back {
  display: inline-flex; align-items: center; gap: 8px;
  min-height: 48px; padding: 0 18px 0 15px;
  border: 1px solid var(--j-line); border-radius: 999px;
  background: var(--j-ink);
  backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px);
  color: var(--j-muted);
  font-family: var(--j-mono); font-size: 0.66rem; letter-spacing: 0.14em; text-transform: uppercase;
  white-space: nowrap;
  pointer-events: auto;
  transition: color 0.3s ease, border-color 0.3s ease;
}
.sys-back:hover, .sys-back:focus-visible { color: var(--j-text); border-color: rgba(var(--accent-rgb), 0.55); }
.sys-back:focus-visible { outline: 2px solid var(--accent-dim); outline-offset: 2px; }
/* The view switch — the same projects in 3D (system.html) or as a
   list (work.html, which uses this block too). Two segments: the
   current one is a marked <span aria-current>, the other a link. */
.view-toggle {
  display: inline-flex; align-items: center; gap: 2px;
  padding: 3px;
  border: 1px solid var(--j-line); border-radius: 999px;
  background: var(--j-ink);
  backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px);
  font-family: var(--j-mono); font-size: 0.66rem; letter-spacing: 0.14em; text-transform: uppercase;
  white-space: nowrap;
  pointer-events: auto;
}
.view-toggle-opt {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 46px; min-height: 40px; padding: 0 14px;
  border-radius: 999px;
  color: var(--j-muted);
  transition: color 0.3s ease, background-color 0.3s ease;
}
.view-toggle-opt[aria-current] { color: var(--accent-pale); background: rgba(var(--accent-rgb), 0.16); }
a.view-toggle-opt:hover, a.view-toggle-opt:focus-visible { color: var(--j-text); background: rgba(255, 255, 255, 0.06); }
a.view-toggle-opt:focus-visible { outline: 2px solid var(--accent-dim); outline-offset: 2px; }
.sys-chip {
  display: flex; align-items: center; gap: 10px;
  padding: 6px 14px 6px 6px;
  border: 1px solid var(--j-line);
  border-radius: 999px;
  background: var(--j-ink);
  backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px);
  color: var(--j-text);
  font-family: 'Space Grotesk', system-ui, sans-serif;
  text-align: left;
  cursor: pointer;
  pointer-events: auto;
  transition: border-color 0.3s ease;
}
.sys-chip:hover, .sys-chip:focus-visible { border-color: rgba(var(--accent-rgb), 0.55); }
.sys-chip:focus-visible { outline: 2px solid var(--accent-dim); outline-offset: 2px; }
.sys-chip-portrait {
  position: relative; width: 34px; height: 34px; border-radius: 50%; overflow: hidden;
  border: 1px solid rgba(var(--accent-rgb), 0.55);
  background: radial-gradient(circle at 35% 30%, rgba(var(--accent-dim-rgb), 0.18), rgba(9, 12, 20, 0.6) 70%);
  display: grid; place-items: center;
  font-family: 'Orbitron', var(--j-mono); font-weight: 800; font-size: 0.7rem; letter-spacing: 0.04em;
}
.sys-chip-portrait img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.sys-chip-portrait img:not([hidden]) + span { display: none; }
.sys-chip-text { display: grid; gap: 1px; }
.sys-chip-name { font-size: 0.9rem; font-weight: 600; letter-spacing: 0.01em; }
.sys-chip-status { display: flex; align-items: center; gap: 6px; font-family: var(--j-mono); font-size: 0.6rem; letter-spacing: 0.1em; color: var(--j-muted); }
.dash-dot { width: 7px; height: 7px; border-radius: 50%; background: #6EE7A0; box-shadow: 0 0 8px #6EE7A0; flex: 0 0 auto; }
.sys-map {
  position: absolute; right: 18px; bottom: 18px;
  width: 150px; height: 150px;
  border-radius: 50%;
  border: 1px solid var(--j-line);
  background: rgba(9, 12, 20, 0.45);
  pointer-events: auto;
  cursor: pointer;
}
.sys-home {
  position: absolute; right: 18px; bottom: 176px;
  margin: 0; padding: 6px 12px;
  border: 1px solid var(--j-line); border-radius: 999px;
  background: var(--j-ink); color: var(--j-muted);
  font: inherit; font-family: var(--j-mono); font-size: 0.64rem; letter-spacing: 0.14em; text-transform: uppercase;
  cursor: pointer; pointer-events: auto;
  transition: color 0.3s ease, border-color 0.3s ease;
}
.sys-home:hover, .sys-home:focus-visible { color: var(--j-text); border-color: rgba(var(--accent-rgb), 0.55); }
.sys-home:focus-visible { outline: 2px solid var(--accent-dim); outline-offset: 2px; }
.sys-hint {
  position: absolute; left: 16px; right: 16px; bottom: 22px;
  margin: 0;
  text-align: center;
  font-family: var(--j-mono);
  font-size: 0.66rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--j-muted);
}

/* ── The panel ── slides in from the side when a body is selected. */
.probe {
  position: absolute;
  right: 24px; top: 50%;
  width: min(400px, 40vw);
  transform: translate(28px, -50%);
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity 0.35s cubic-bezier(0.3, 0, 1, 1), transform 0.35s cubic-bezier(0.3, 0, 1, 1), visibility 0s 0.35s;
}
.probe.is-open {
  transform: translate(0, -50%);
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
  transition: opacity 0.55s cubic-bezier(0.4, 0, 0.2, 1), transform 0.55s cubic-bezier(0.4, 0, 0.2, 1), visibility 0s;
}
.probe-frame {
  padding: 22px 24px 20px;
  background: var(--j-ink);
  border: 1px solid var(--j-line);
  border-radius: 14px;
  backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px);
  color: var(--j-text);
  font-family: 'Space Grotesk', system-ui, sans-serif;
}
/* Parts arrive in order as the panel lands. */
.probe-frame > * { opacity: 0; transform: translateY(12px); transition: none; }
.probe.is-open .probe-frame > * { opacity: 1; transform: none; transition: opacity 0.5s cubic-bezier(0.4, 0, 0.2, 1), transform 0.5s cubic-bezier(0.4, 0, 0.2, 1); }
.probe.is-open .probe-frame > *:nth-child(1) { transition-delay: 0.10s; }
.probe.is-open .probe-frame > *:nth-child(2) { transition-delay: 0.18s; }
.probe.is-open .probe-frame > *:nth-child(3) { transition-delay: 0.26s; }
.probe.is-open .probe-frame > *:nth-child(4) { transition-delay: 0.34s; }
.probe.is-open .probe-frame > *:nth-child(5) { transition-delay: 0.42s; }
.probe.is-open .probe-frame > *:nth-child(n + 6) { transition-delay: 0.50s; }
.probe-label { margin: 0 0 0.9rem; font-family: var(--j-mono); font-size: 0.7rem; letter-spacing: 0.22em; text-transform: uppercase; color: var(--accent); }
.probe-head { display: flex; align-items: center; gap: 16px; margin-bottom: 0.9rem; }
.probe-portrait {
  position: relative; flex: 0 0 auto;
  width: 78px; height: 78px; border-radius: 50%;
  border: 1px solid rgba(var(--accent-rgb), 0.55);
  background: radial-gradient(circle at 35% 30%, rgba(var(--accent-dim-rgb), 0.18), rgba(9, 12, 20, 0.6) 70%);
  overflow: hidden; display: grid; place-items: center;
}
.probe-portrait::before { content: ''; position: absolute; inset: -7px; border-radius: 50%; border: 1px dashed rgba(var(--accent-rgb), 0.45); animation: probeOrbit 40s linear infinite; }
@keyframes probeOrbit { to { transform: rotate(360deg); } }
.probe-portrait img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; border-radius: 50%; }
.probe-portrait img + .probe-initials { display: none; }
.probe-initials { font-family: 'Orbitron', var(--j-mono); font-weight: 800; font-size: 1.4rem; letter-spacing: 0.06em; }
.probe-id { min-width: 0; }
.probe-title { margin: 0 0 0.2rem; font-size: clamp(1.2rem, 2vw, 1.5rem); font-weight: 600; letter-spacing: 0.02em; }
.probe-meta { margin: 0 0 0.3rem; font-size: 0.9rem; color: var(--j-muted); }
.probe-status { margin: 0; display: flex; align-items: center; gap: 8px; font-family: var(--j-mono); font-size: 0.66rem; letter-spacing: 0.1em; color: var(--j-muted); }
.probe-desc { margin: 0 0 1rem; font-size: 0.94rem; line-height: 1.55; }
.probe-readouts { display: grid; grid-template-columns: repeat(2, 1fr); gap: 8px; margin: 0 0 1rem; }
.probe-readout { padding: 10px 12px; border: 1px solid var(--j-line); border-radius: 10px; background: rgba(255, 255, 255, 0.025); }
.probe-readout dt { margin: 0 0 2px; font-family: var(--j-mono); font-size: 0.62rem; letter-spacing: 0.18em; text-transform: uppercase; color: var(--j-muted); }
.probe-readout dd { margin: 0; font-size: 1.35rem; font-weight: 600; color: var(--accent); font-variant-numeric: tabular-nums; }
/* Read, not seen: the readout's value for a screen reader (hud.js). */
.probe-sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
.probe-links { display: flex; flex-wrap: wrap; gap: 14px; margin-bottom: 1rem; }
.probe-links a { color: var(--accent); text-decoration: none; font-size: 0.88rem; }
.probe-links a:hover { text-decoration: underline; }
.probe-back {
  margin: 0; padding: 0.5em 0;
  border: 0; border-bottom: 1px solid rgba(var(--accent-rgb), 0.35);
  background: none; color: var(--accent);
  font: inherit; font-family: var(--j-mono); font-size: 0.7rem; letter-spacing: 0.18em; text-transform: uppercase;
  cursor: pointer;
  transition: color 0.3s ease, border-color 0.3s ease;
}
.probe-back:hover, .probe-back:focus-visible { color: var(--j-text); border-color: var(--j-text); }

/* The project card: a mission dossier (hud.js projectHtml). Type and
   hairlines; the only filled thing is the Source button. */
.dossier-strip { display: flex; align-items: center; gap: 12px; margin-bottom: 1.3rem; }
.dossier-strip i { flex: 1; height: 1px; background: linear-gradient(90deg, rgba(var(--accent-rgb), 0.45), rgba(var(--accent-rgb), 0.12)); }
.dossier-strip span:last-child { color: var(--j-muted); }
.dossier-id { margin-bottom: 1.1rem; }
.dossier-name { margin: 0 0 0.35rem; font-size: clamp(1.45rem, 2.4vw, 1.9rem); font-weight: 700; letter-spacing: -0.01em; line-height: 1.1; overflow-wrap: anywhere; }
.dossier-tagline { margin: 0; font-size: 0.92rem; color: var(--j-muted); }
.dossier-desc, .dossier-specs, .dossier-actions { margin: 0; padding: 1rem 0; border-top: 1px solid var(--j-line); }
.dossier-desc { font-size: 0.92rem; line-height: 1.6; color: rgba(230, 233, 242, 0.86); }
.dossier-specs { display: grid; gap: 0.55rem; }
.dossier-specs:empty { display: none; }
.dossier-spec { display: grid; grid-template-columns: 4.6rem 1fr; gap: 12px; align-items: baseline; }
.dossier-spec dt { font-family: var(--j-mono); font-size: 0.64rem; letter-spacing: 0.2em; text-transform: uppercase; color: var(--j-muted); }
.dossier-spec dd { margin: 0; font-size: 0.86rem; color: var(--j-text); }
.dossier-actions { display: flex; gap: 10px; margin-bottom: 0.6rem; }
.dossier-btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  min-height: 42px; padding: 0 16px;
  border: 1px solid rgba(var(--accent-rgb), 0.32); border-radius: 8px;
  color: var(--accent); text-decoration: none;
  font-family: var(--j-mono); font-size: 0.72rem; letter-spacing: 0.16em; text-transform: uppercase;
  transition: background-color 0.25s ease, border-color 0.25s ease, color 0.25s ease;
}
.dossier-btn--main { flex: 1; background: rgba(var(--accent-rgb), 0.10); }
.dossier-btn:hover, .dossier-btn:focus-visible { background: rgba(var(--accent-rgb), 0.18); border-color: rgba(var(--accent-rgb), 0.6); color: var(--accent-pale); }
.dossier-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* Portrait screens: the panel is a sheet at the bottom, the map goes. */
@media (max-aspect-ratio: 1/1) {
  .probe { right: 12px; left: 12px; top: auto; bottom: 12px; width: auto; max-height: 58vh; overflow: auto; transform: translateY(24px); }
  .probe.is-open { transform: none; }
  .probe-frame { padding: 16px 16px 14px; }
  .probe-portrait { width: 60px; height: 60px; }
  .probe-initials { font-size: 1.1rem; }
  .probe-readouts { grid-template-columns: repeat(4, 1fr); gap: 6px; }
  .probe-readout { padding: 8px; }
  .probe-readout dt { font-size: 0.55rem; letter-spacing: 0.12em; }
  .probe-readout dd { font-size: 1.1rem; }
  .sys-map { display: none; }
  .sys-home { right: 12px; bottom: auto; top: 14px; }
  /* Back and the view switch on the first row (⌂ System is top right,
     level with them), the chip under them. */
  .sys-top { top: 12px; left: 12px; max-width: calc(100% - 24px); flex-direction: column; align-items: flex-start; gap: 8px; }
  .sys-back { min-height: 42px; padding: 0 14px 0 12px; }
  .view-toggle { padding: 0; }
  .view-toggle-opt { min-width: 44px; padding: 0 12px; }
  .sys-hint { bottom: 14px; font-size: 0.6rem; }
  .sys-label { font-size: 0.6rem; padding: 3px 7px 3px 5px; }
}
@media (max-aspect-ratio: 1/1) and (max-width: 420px) {
  .probe-readouts { grid-template-columns: repeat(2, 1fr); }
}
@media (prefers-reduced-motion: reduce) {
  .probe, .probe.is-open, .probe-frame > *, .probe.is-open .probe-frame > *, .sys-hud, .sys-label, .sys-back, .view-toggle-opt { transition: none; }
  .probe-portrait::before, .sys-reticle { animation: none; }
}

/* No WebGL, or the module never ran: the same content as a scrolling
   list of cards, over the sky. The section may scroll then. */
.planet--css canvas, .planet--css .sys-labels, .planet--css .sys-hud, .planet--css .probe, .planet--css .planet-caption { display: none; }
.planet--css { height: auto; min-height: 100vh; overflow: auto; }
/* …but Back and the view switch stay: this is the state where a way
   out to the plain list matters most. They scroll away with the list. */
.planet--css .sys-hud { display: block; opacity: 1; visibility: visible; transition: none; }
.planet--css .sys-chip, .planet--css .sys-home, .planet--css .sys-hint { display: none; }
.sys-fallback { display: grid; gap: 14px; max-width: 640px; margin: 0 auto; padding: max(10vh, 84px) 16px 10vh; }
.sys-fallback .probe-frame { position: static; }

/* ============================================================
   WARP — the launch overlay
   ============================================================
   Above everything (the planet section is z 3 and the dashboard sits inside it;
   z 3 context; this is z 6 at the root). Invisible until .is-on;
   warp.js paints the streaks and the flash on the canvas. */
.warp {
  position: fixed;
  inset: 0;
  z-index: 6;
  pointer-events: none;
  visibility: hidden;
}
.warp.is-on { visibility: visible; }
/* Out of the home page's dive (system-boot.js): the page opens on the
   dark that one ended in, from the first paint, until the warp's flash
   covers the switch to the sky (warp.js). */
:root.is-diving-in .warp { visibility: visible; background: #000; }
.warp canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
}

/* THE PHONE has a design of its own: css/phone.css (every page links
   it after its own sheet). */
