/* Stations, cards and the hero. A station is one scroll stop; the 3D
   camera keyframe with the same index lives in src/data/keyframes.js. */
/* ── Content ─────────────────────────────────────────────────── */
  main { position: relative; z-index: 3; }
  .station { min-height: 100svh; display: flex; align-items: center; padding: 96px 30px; pointer-events: none; }
  .station > * { pointer-events: auto; }
  .st-hero  { justify-content: center; text-align: center; }

  /* While the page is still in space the type has to invert. Remapping the
     tokens covers the hero and the bar in one go, and the transition keeps
     it from snapping as the sky whitens on the way down. */
  /* The frosted panel belongs to the white part of the page — over space it
     has nothing to pick up and reads as a grey slab. The name itself stays;
     it just sits straight on the sky, and over-space already lightens it. */
  /* `near-face` joins it for the closing drive: the panel is gone from the
     moment the portrait starts filling the frame, which is earlier than the
     palette flip (src/scene/index.js). */
  :root.over-space .hud-bar.scrolled,
  :root.near-face .hud-bar.scrolled {
    background: none;
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
  }
  /* The lighter tier swaps the blur for a more opaque bar, so it needs the
     same release or the slab stays. */
  :root.lite.over-space .hud-bar.scrolled,
  :root.lite.near-face .hud-bar.scrolled { background: none; }
  :root.over-space .hud-name,
  :root.closer-dark .hud-name { text-shadow: 0 1px 12px rgba(0, 0, 0, .55); }

  :root.over-space,
  :root.closer-dark {
    --ink:       #f5f5f7;
    --ink-soft:  #b9bec9;
    --ink-faint: #7e8492;
    --accent:    #6cb2ff;
    --hair:      rgba(255,255,255,.16);
    --bar:       rgba(10,12,24,.72);
  }
  .hud-name, .hero h1, .hero p, .hud-bl {
    transition: color .5s ease;
  }
  /* Half a second is right where the page whitens gradually. On the drive into
     the headset the ground under the bar goes white to black inside 38px of
     scroll — the portrait's hair sweeping past — which one hard flick crosses
     in a single frame. Any fade at all leaves the name dark on black while it
     runs: at .15s it was invisible for about a quarter of a second. It snaps
     instead, on the same frame the ground does. */
  :root.near-face .hud-name { transition: none; }
  /* The type sits straight on the planet, and a lit continent behind a line
     of body copy will swallow it — so it gets a shadow while it is up there. */
  :root.over-space .hero h1,
  :root.over-space .hero p {
    text-shadow: 0 1px 3px rgba(0,0,0,.45), 0 4px 30px rgba(0,0,0,.55);
  }
  /* The imagery credit, in the HUD's mono caps, bottom right so it mirrors the scroll
     cue. The loop owns its opacity: up while the plates are on screen, gone with them. */
  .hero-credit { position: absolute; right: 30px; bottom: 28px; margin: 0;
                 font-family: ui-monospace, "SF Mono", "Roboto Mono", Menlo, monospace;
                 font-size: var(--fs-cap); letter-spacing: .075em; text-transform: uppercase;
                 color: rgba(255, 255, 255, .38); opacity: 0; pointer-events: none;
                 white-space: nowrap; }
  :root.no-gl .hero-credit, :root.no-motion .hero-credit { display: none; }
  .st-left  { justify-content: flex-start; }
  .st-right { justify-content: flex-end; }
  /* The globe holds, then the descent needs scroll room to breathe. The copy
     is pinned for that whole stretch, because it has somewhere to go: it
     travels down onto the planet as the planet zooms in. Scrolling it off the
     top instead wasted the hold. */
  .station[data-station="0"] { align-items: stretch; padding: 0; min-height: 200svh; }
  .hero-sticky {
    position: sticky; top: 0; height: 100svh; width: 100%;
    display: flex; align-items: center; justify-content: center; padding: 0 30px;
    pointer-events: none;
  }
  .hero-sticky > * { pointer-events: auto; }

  /* Cards animate opacity/transform only — never display — so find-in-page
     and screen readers always reach the text. */
  .card {
    max-width: 400px;
    background: var(--card-solid);
    border-radius: 24px;
    padding: 28px 30px 30px;
    box-shadow: var(--shadow);
    opacity: 0; transform: translateY(20px) scale(.985);
    transition: opacity .5s ease, transform .55s cubic-bezier(.32,.9,.24,1);
    will-change: opacity, transform;
  }
  .card.in { opacity: 1; transform: none; }
  :root.no-motion .card, :root.no-gl .card { opacity: 1; transform: none; }
  /* The page is held still while the opening plays — see src/scene/intro.js.
     Dormant: the opening is shelved (CFG.intro.enabled), so the class is never added. */
  :root.intro-lock, :root.intro-lock body { overflow: hidden; height: 100%; }
  /* The hero planet drags (src/scene/index.js, heroDrag). The loop sets the class
     while the page is on the hero and the opening is over; links keep their own
     cursor. pan-y leaves vertical scrolling to the browser on touch, so a sideways
     swipe turns the planet and an up-and-down one still scrolls. */
  :root.globe-grab { cursor: grab; }
  :root.globe-grabbing { cursor: grabbing; }
  :root.globe-grab body { touch-action: pan-y; }
  .st-hero { -webkit-user-select: none; user-select: none; }


  .card h2 { font-size: var(--fs-title); font-weight: 600;
             letter-spacing: var(--ls-title); line-height: var(--lh-title); margin: 0 0 4px; }
  /* Employer -> role -> what you did. Warsaw is two separate companies and
     the other two are one each; a flat list of roles couldn't say who you
     worked for, so the company anchors every block. */
  .job { margin-top: 18px; padding-top: 16px; border-top: 1px solid var(--hair); }
  .job-h { display: flex; align-items: baseline; justify-content: space-between; gap: 14px; }
  .job-h b { font-size: var(--fs-body); font-weight: 600; letter-spacing: var(--ls-body); }
  .job-h span { flex: none; font-size: 11.5px; color: var(--ink-faint); white-space: nowrap; }
  .job-role { margin-top: 2px; font-size: var(--fs-sub); font-weight: 500;
              letter-spacing: var(--ls-body); color: var(--accent); }
  .job ul { margin: 9px 0 0; padding: 0; list-style: none; }
  .job li { position: relative; padding-left: 15px; margin-top: 5px;
            font-size: var(--fs-sub); line-height: 1.5; color: var(--ink-soft); }
  .job li::before { content: ""; position: absolute; left: 3px; top: .62em;
                    width: 4px; height: 4px; border-radius: 50%; background: var(--ink-faint); }
  .note { margin-top: 22px; padding-top: 18px; border-top: 1px solid var(--hair);
          font-size: var(--fs-body); line-height: var(--lh-body); letter-spacing: var(--ls-body); }
  .note em { font-style: normal; font-weight: 600; }

  .chips { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 15px; }
  .chips i { font-style: normal; font-size: 12px; letter-spacing: -.008em;
             padding: 5px 11px; border-radius: 980px;
             background: var(--surface); color: var(--ink-soft); }

  /* Hero */
  /* Full width, not shrink-to-fit: the lead paragraph is capped at 620px and
     was running wider than the shrink-to-fit box, which nothing showed until
     the box became a mask — then it clipped the first and last glyph. */
  .hero { width: 100%; max-width: 880px; background: none; box-shadow: none; padding: 0; }
  /* Scroll writes this transform every frame, so it must not be transitioned:
     a transition here trails the wheel instead of tracking it. The in/out
     state changes opacity only, or the copy would jump on the class flip. */
  /* The planet's disc, punched out of the type. Both radii come from the same
     projection the planet is drawn with, so the edge lands exactly on the
     limb. Unset — no WebGL — the circle sits off the card and nothing is cut.
     No transition on any of it: scroll writes these every frame, and a
     transition would trail the wheel instead of tracking it. */
  .hero, .hero.in {
    transform: translate(var(--heroX, 0px), var(--heroY, 0px));
    opacity: var(--heroFade, 0);   /* the loop owns it; no-gl/no-motion override below */
    transition: none;
    -webkit-mask-image: radial-gradient(circle at var(--mx, 50%) var(--my, -99999px),
                        transparent var(--mr0, 0px), #000 var(--mr1, 0px));
            mask-image: radial-gradient(circle at var(--mx, 50%) var(--my, -99999px),
                        transparent var(--mr0, 0px), #000 var(--mr1, 0px));
    -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
  }
  .hero h1 { font-size: clamp(34px, 6.4vw, 66px); font-weight: 600;
             letter-spacing: var(--ls-display); line-height: var(--lh-display);
             min-height: 1.06em; }        /* holds the line while it retypes */
  /* Placed against the h1 and moved by transform, so it glides along the line
     as it types instead of hopping a character at a time. Step timing on the
     blink, because a fade reads as a glow rather than a cursor. */
  .hero h1 { position: relative; }
  .hero .caret {
    position: absolute; left: 0; top: 0;
    width: .07em; height: .88em; margin-left: .06em;
    background: currentColor;
    transform: translate(var(--cx, 0px), calc(var(--cy, 0px) - 50%));
    will-change: transform;
  }
  .hero .caret.idle { animation: caret 1.05s steps(1) infinite; }
  @keyframes caret { 0%, 50% { opacity: 1 } 50.01%, 100% { opacity: 0 } }

  /* The loader (src/load.js): two lanes of pre-rendered 3D forms over the
     count, on black, above the page and below the cursor; the forms swap
     and resolve into the inflated K and D, then the panel fades out over
     the scene. The page holds still under it (html.loading). Startup owes
     nothing to WebGL: six WebP images. */
  html.loading { overflow: hidden; }
  /* Nothing here should ever scroll sideways. `clip` rather than `hidden`:
     it does not turn the root into a scroll container, and iOS honours it
     where a hidden body alone still let the page pan (Khusan, 11 Sep). */
  html { overflow-x: clip; }
  /* The scroll smoother (src/ui/smooth.js) shapes the wheel and the keys
     only; touch scrolling is native (a takeover of the finger was slow and
     glitchy on a phone, 12 Sep), so no touch-action rule is needed here. */
  .load { position: fixed; inset: 0; z-index: 30; background: #000; color: #f5f5f7;
    display: grid; place-items: center; transition: opacity .65s ease; }
  .load[hidden] { display: none; }
  .load.fade { opacity: 0; pointer-events: none; }
  .load-art { position: relative; display: flex; justify-content: center; align-items: center;
    gap: clamp(8px, 2vw, 24px); width: min(580px, 86vw); height: min(380px, 53vh); }
  .load-slot { position: relative; width: 46%; height: 100%; transform: scale(.78);
    transition: transform 1.1s cubic-bezier(.16,1,.3,1); }
  .load-slot:first-of-type { transform: scale(.78) rotate(-7deg); }
  .load-slot:last-child { transform: scale(.78) rotate(7deg); }
  .load-art.resolved .load-slot { transform: scale(1.08) rotate(0); }
  .load-form { position: absolute; inset: 0; opacity: 0; visibility: hidden; }
  .load-form.active { opacity: 1; visibility: visible; }
  .load-form img { width: 100%; height: 100%; object-fit: contain;
    filter: saturate(1.08) contrast(1.04); }
  .load-form.letter img { filter: none; }
  .load-form.letter { transform: translateY(12px) scale(.92); }
  .load-art.resolving .load-form.letter { visibility: visible;
    transition: opacity .75s cubic-bezier(.22,1,.36,1), transform 1.1s cubic-bezier(.16,1,.3,1); }
  .load-form.letter.active { transform: translateY(0) scale(1); }
  .load-form.leaving { visibility: visible; transform: scale(.92);
    transition: opacity .5s ease, transform .8s cubic-bezier(.16,1,.3,1); }
  .load-fallback { position: absolute; inset: 0; display: grid; place-items: center;
    font-size: clamp(100px, 23vw, 240px); font-weight: 650; letter-spacing: -.06em; opacity: 0; }
  .load-art.fallback .load-fallback { opacity: 1; }
  /* Objects and letters share fixed centers, bounds and spacing throughout. */
  .load-art.typographic .load-slot { height: min(280px, 40vw); transform: none; transition: none; }
  .load-art.typographic .load-form.text-letter { display: grid; place-items: center;
    color: #fff; font-family: Arial, Helvetica, sans-serif;
    font-size: clamp(100px, 53vw, 360px); font-weight: 900;
    line-height: .75; letter-spacing: 0; transform: none;
    transition: opacity .36s ease-in-out; }
  .load-art.typographic .load-form.leaving { transform: none; transition: opacity .3s ease-in-out; }
  .text-letter > span { position: relative; display: block; transform-origin: 50% 85%; }
  .text-letter.active > span { animation: kd-reveal .72s cubic-bezier(.16,1,.3,1) both; }
  .text-letter > span::after { content: attr(data-letter); position: absolute; inset: 0;
    color: transparent; background: linear-gradient(110deg, transparent 35%, #99dfff 47%, #fff 51%, transparent 63%);
    background-size: 300% 100%; background-position: 100% 0;
    background-clip: text; -webkit-background-clip: text; opacity: 0; pointer-events: none; }
  .load-art.resolved .text-letter.active > span::after { animation: kd-light .8s .72s ease-in-out both; }
  @keyframes kd-reveal {
    0% { clip-path: inset(100% 0 0); transform: translateY(22px) scale(.9) skewY(5deg); }
    58% { clip-path: inset(0); transform: translateY(-5px) scale(1.035) skewY(-1deg); }
    100% { clip-path: inset(0); transform: translateY(0) scale(1) skewY(0); }
  }
  @keyframes kd-light {
    0% { background-position: 100% 0; opacity: 0; }
    25% { opacity: .85; }
    75% { opacity: .65; }
    100% { background-position: 0% 0; opacity: 0; }
  }
  .load-progress { position: absolute; width: min(220px, 58vw); bottom: max(40px, 8vh); }
  .load-meta { display: flex; justify-content: space-between; margin-bottom: 12px;
    font-size: 11px; line-height: 1; letter-spacing: .025em; font-weight: 400;
    font-variant-numeric: tabular-nums; color: #b8b8bb; }
  .load-track { height: 1px; background: #303033; overflow: hidden; }
  #loadFill { width: 100%; height: 100%; background: #efeff1; transform: scaleX(0); transform-origin: left; }
  @media (prefers-reduced-motion: reduce) {
    .load-slot, .load-art.resolved .load-slot { transition: none; transform: none; }
    .load-art.resolving .load-form.letter, .load-form.leaving { transition: none; transform: none; }
    .load-art.typographic .load-form.text-letter { transition: none; transform: none; }
    .text-letter.active > span, .load-art.resolved .text-letter.active > span::after { animation: none; }
    .load { transition-duration: .15s; }
  }
  .hero p { margin: 22px auto 0; max-width: 620px; font-size: var(--fs-lead);
            line-height: var(--lh-lead); letter-spacing: var(--ls-lead);
            font-weight: 400; color: var(--ink-soft); }

  /* Outro */
