  .first-touch {
    position: fixed;
    inset: 0;
    z-index: 1000;
    display: grid;
    place-items: center;
    overflow: hidden;
    /* near-black base; each stage layers its own faint wash for variety */
    background:
      radial-gradient(circle at 50% 42%, rgba(127, 179, 122, 0.07), transparent 34%),
      linear-gradient(180deg, #020303 0%, #060909 52%, #000101 100%);
    color: var(--hg-text-primary);
    font-family: var(--hg-font-body, ui-monospace, 'JetBrains Mono', SFMono-Regular, Menlo, Consolas, monospace);
    width: 100vw;
    /* dvh keeps content truly centered on mobile, where 100vh overshoots
       the visible viewport behind browser chrome */
    height: 100vh;
    height: 100dvh;
    min-height: 100dvh;
  }

  /* per-stage washes: boot goes coldest and blackest, the greeter warms
     up slightly at the base — same family, different rooms */
  .first-touch[data-stage='boot'] {
    background:
      radial-gradient(circle at 50% 30%, rgba(110, 172, 160, 0.05), transparent 40%),
      linear-gradient(180deg, #010202 0%, #040707 55%, #000000 100%);
  }

  /* matches the canvas TV slab (fillPane solid stops + center wash), so
     when the powered-on screen dissolves, its background seamlessly stays
     as the login's backdrop */
  .first-touch[data-stage='handoff'],
  .first-touch[data-stage='greeter'] {
    background:
      radial-gradient(circle at 50% 50%, rgba(127, 179, 122, 0.06), transparent 60%),
      linear-gradient(180deg, #050908 0%, #020404 45%, #000101 100%);
  }

  .first-touch::before {
    content: '';
    position: absolute;
    inset: 0;
    background: radial-gradient(ellipse at center, transparent 42%, rgba(0, 0, 0, 0.72) 100%);
    pointer-events: none;
  }

  /* pure hyperspace: once the old chrome has dived out there is no
     monitor, so no tube wash and no vignette — anything radial reads as
     a circle stamped on the middle of the tunnel */
  .first-touch.in-warp {
    background: #000;
  }

  .first-touch.in-warp::before {
    opacity: 0;
  }

  .first-touch.crt-on {
    animation: crt-power 420ms steps(8, end);
  }

  /* the 3D stage: the boot screen shoots in with a back-eased overshoot
     and a slight perspective tilt, then keeps a slow pan/zoom drift; the
     canvas rides inside, so drawn chrome stays glued to the DOM */
  .stage-3d {
    position: absolute;
    inset: 0;
    display: grid;
    place-items: center;
  }

  .stage-3d[data-stage='boot'] {
    animation:
      screen-enter 950ms cubic-bezier(0.22, 1, 0.36, 1) both,
      screen-drift 10s ease-in-out 950ms infinite alternate;
  }

  @keyframes screen-enter {
    0% {
      transform: perspective(1100px) scale(0.78) rotateX(10deg) rotateY(-6deg);
      opacity: 0.35;
    }
    55% {
      opacity: 1;
    }
    70% {
      /* explicit overshoot PAST rest — the screen visibly shoots too far
         and bounces back while the tilt swings through */
      transform: perspective(1100px) scale(1.055) rotateX(-2.5deg) rotateY(1.8deg);
    }
    100% {
      transform: perspective(1100px) scale(1) rotateX(0deg) rotateY(0deg);
    }
  }

  @keyframes screen-drift {
    0% {
      transform: perspective(1100px) scale(1) translate(0, 0) rotateY(0.6deg) rotateX(0.3deg);
    }
    100% {
      transform: perspective(1100px) scale(1.025) translate(-0.5%, 0.3%) rotateY(-0.6deg)
        rotateX(-0.3deg);
    }
  }

  .scanlines {
    position: absolute;
    inset: 0;
    z-index: 4;
    pointer-events: none;
    opacity: 0.38;
    background: repeating-linear-gradient(
      0deg,
      rgba(238, 242, 247, 0.06) 0,
      rgba(238, 242, 247, 0.06) 1px,
      transparent 1px,
      transparent 4px
    );
    animation: scanlines 800ms linear infinite;
  }

  .sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
  }

  /* transition: none defeats the universal opacity/transform transition in
     global.css, which would otherwise fight the GSAP-driven canvas frames.
     The canvas sits above the boot screen: the doorway reveal paints an
     opaque cover with an expanding hole, so it must win the paint order. */
  .intro-canvas {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    display: block;
    z-index: 2;
    pointer-events: none;
    transition: none;
  }

  .power-hotspot {
    z-index: 3;
    position: absolute;
    left: 50%;
    top: 50%;
    width: min(46vmin, 300px);
    aspect-ratio: 1;
    transform: translate(-50%, -50%);
    border: 0;
    border-radius: 50%;
    background: transparent;
    cursor: pointer;
    transition: none;
    -webkit-tap-highlight-color: transparent;
  }

  .power-hotspot:focus-visible {
    outline: 1px solid rgba(127, 179, 122, 0.55);
    outline-offset: 10px;
  }

  .power-hotspot:disabled {
    cursor: default;
  }

  .boot-screen,
  .greeter,
  .loading-screen {
    position: relative;
    width: 100vw;
    height: 100vh;
    height: 100dvh;
    min-height: 100dvh;
    display: grid;
    align-content: center;
    justify-items: center;
    gap: 22px;
    border: 0;
    background: transparent;
    box-shadow: none;
    padding: clamp(24px, 5vw, 72px);
    box-sizing: border-box;
    overflow: hidden;
  }

  .pixel-sigil {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: clamp(5px, 0.7vw, 9px);
    width: clamp(86px, 10vw, 140px);
    aspect-ratio: 1;
    padding: clamp(8px, 1vw, 14px);
    background: rgba(127, 179, 122, 0.05);
    box-shadow:
      0 0 0 1px rgba(127, 179, 122, 0.22),
      0 0 32px rgba(127, 179, 122, 0.2),
      inset 0 0 28px rgba(127, 179, 122, 0.08);
  }

  /* the canvas cuts and lights the sigil gate-style in these cells' rects;
     the DOM version only provides layout, like the wordmark ghost */
  .sigil-ghost {
    visibility: hidden;
  }

  .pixel-sigil span {
    background: rgba(127, 179, 122, 0.18);
    box-shadow: inset 0 0 0 1px rgba(127, 179, 122, 0.36);
  }

  .pixel-sigil span:nth-child(1),
  .pixel-sigil span:nth-child(3),
  .pixel-sigil span:nth-child(5),
  .pixel-sigil span:nth-child(7),
  .pixel-sigil span:nth-child(9) {
    background: var(--hg-accent-primary);
    box-shadow:
      0 0 16px rgba(127, 179, 122, 0.42),
      inset 0 0 0 1px rgba(238, 242, 247, 0.16);
  }

  .loading-screen pre {
    margin: 0;
    color: var(--hg-accent-primary);
    text-shadow: 0 0 12px rgba(127, 179, 122, 0.36);
    line-height: 1.1;
    letter-spacing: 0;
    font-size: clamp(7px, 1.08vw, 16px);
    max-width: calc(100vw - 32px);
    overflow: hidden;
  }

  .boot-lines {
    display: grid;
    gap: 8px;
    /* shrink to content and center under the wordmark — the lines stay
       left-aligned inside, like a real terminal */
    width: fit-content;
    justify-self: center;
    text-align: left;
    color: color-mix(in srgb, var(--hg-accent-primary) 55%, var(--hg-text-muted) 45%);
    font-size: clamp(12px, 1.05vw, 15px);
    letter-spacing: 0.04em;
    text-shadow: 0 0 10px rgba(127, 179, 122, 0.25);
  }

  .boot-lines span {
    /* terminal lines flicker in one by one while the wordmark assembles */
    animation: line-in 460ms steps(5, end) backwards;
  }

  .boot-lines span:nth-child(1) { animation-delay: 1450ms; }
  .boot-lines span:nth-child(2) { animation-delay: 1850ms; }
  .boot-lines span:nth-child(3) { animation-delay: 2250ms; }

  .boot-lines span::before {
    content: '> ';
    color: var(--hg-accent-warning);
  }

  /* live terminal cursor on the newest line */
  .boot-lines span:last-child::after {
    content: '█';
    margin-left: 7px;
    color: var(--hg-accent-primary);
    animation: cursor-blink 900ms steps(1, end) infinite;
  }

  @keyframes cursor-blink {
    0%,
    100% {
      opacity: 1;
    }
    50% {
      opacity: 0;
    }
  }

  /* handoff: the boot plane moves out into the background, holds a beat,
     then slides away left — timings mirror the canvas exit tweens
     (recede 0-0.8s = 34%, slide 1.15-2.35s = 49%..100% of 2350ms,
     S-curve matching power3.inOut) */
  .boot-exit {
    position: absolute;
    inset: 0;
    /* above the canvas (z 2): the terminal lines sit on the departing
       screen slab and ride the zoom-out and swipe with it */
    z-index: 3;
    pointer-events: none;
    animation: boot-zoom-out 2350ms linear forwards;
  }

  .greeter {
    /* mirrors the canvas monitor in power-intro.ts drawArriveFrame:
       bezel = clamp(14px, 3vmin, 36px), chin = bezel * 2.15 */
    --crt-bezel: clamp(14px, 3vmin, 36px);
    --crt-chin: calc(var(--crt-bezel) * 2.15);
    /* fillPane's seam-light band width: 7% of the tube's short side */
    --crt-tube-edge: calc(
      min(100vw - var(--crt-bezel) * 2, 100dvh - var(--crt-bezel) - var(--crt-chin)) * 0.07
    );
    grid-template-columns: 1fr;
    align-content: center;
    justify-items: center;
    gap: clamp(20px, 4vh, 42px);
    padding: calc(var(--crt-bezel) + clamp(16px, 3vw, 32px));
    padding-bottom: calc(var(--crt-chin) + clamp(16px, 3vw, 32px));
    /* own stacking context BELOW the intro canvas (z 2), so the monitor
       chrome / tube / picture order stays internal and the canvas element
       seam traces still draw over the login containers */
    z-index: 1;
    /* the flat slab's seam bands are replaced by the CRT monitor chrome
       (.crt-monitor); only the soft phosphor bleed stays on the screen */
    box-shadow: inset 0 0 90px rgba(127, 179, 122, 0.07);
    /* the greeter stays hidden while the window slides in, slams, and
       powers on; its image flickers on under the dissolving slab like a
       CRT picture appearing */
    animation: greeter-arrive 450ms 4200ms backwards;
  }

  /* old CRT monitor around the login screen: dark plastic bezel with
     per-edge lighting for shallow 3D, a recessed rounded tube behind
     curved glass, and a power LED + badge on the bottom bezel. All edge
     gradients settle into the flat lip colour (#141614) by 55%, so the
     ::after lip ring can overlap them invisibly while it covers the
     square-bezel / rounded-screen corner gaps. */
  .crt-monitor {
    position: absolute;
    inset: 0;
    z-index: 2;
    pointer-events: none;
  }

  .crt-monitor::before {
    /* the case plastic: lit along the top, falling into shadow at the
       chin, matching the canvas gradient stops */
    content: '';
    position: absolute;
    inset: 0;
    background: linear-gradient(180deg, #2c302c 0%, #212421 16%, #151715 72%, #1f221f 100%);
    box-shadow: inset 0 1px 0 rgba(214, 232, 220, 0.16);
    /* square at full screen — the canvas case rounds its corners only
       while the monitor is an object out in space, and eases them to 0
       as it expands into the viewport */
  }

  .crt-monitor::after {
    /* The recessed tube. Opaque on purpose: the case plastic covers the
       whole viewport, so the tube has to repaint the picture area. Its
       base stops are exactly fillPane's solid variant + center wash, so
       the canvas monitor dissolves into this one with no seam. */
    content: '';
    position: absolute;
    top: var(--crt-bezel);
    left: var(--crt-bezel);
    right: var(--crt-bezel);
    bottom: var(--crt-chin);
    border-radius: clamp(14px, 3.8vmin, 44px);
    box-shadow:
      0 0 0 2px rgba(0, 0, 0, 0.75),
      inset 0 0 0 1px rgba(127, 179, 122, 0.1),
      /* the two seam-light bands the canvas tube shows, so the dissolve
         does not change the picture area */
      inset 0 0 0 calc(var(--crt-tube-edge) * 0.4) rgba(127, 179, 122, 0.05),
      inset 0 0 0 var(--crt-tube-edge) rgba(127, 179, 122, 0.045),
      inset 0 3px 16px rgba(0, 0, 0, 0.8);
    background:
      linear-gradient(180deg, rgba(214, 232, 220, 0.05), transparent 50%),
      radial-gradient(circle at 50% 50%, transparent 22%, rgba(0, 0, 0, 0.55) 100%),
      radial-gradient(circle at 50% 50%, rgba(127, 179, 122, 0.06), transparent 60%),
      linear-gradient(180deg, #050908 0%, #020404 45%, #000101 100%);
  }

  /* the picture rides above the tube glass; the monitor chrome is at
     z-index 2, so everything on screen sits above it */
  .greeter-copy,
  .greeter-panel {
    position: relative;
    z-index: 4;
  }

  .greeter-system-bar {
    z-index: 4;
  }

  /* two chin buttons, left of the LED */
  .crt-monitor .crt-btn {
    position: absolute;
    z-index: 1;
    bottom: calc(var(--crt-chin) / 2);
    transform: translateY(50%);
    width: calc(var(--crt-chin) * 0.5);
    height: calc(var(--crt-chin) * 0.16);
    border-radius: 999px;
    background: rgba(214, 232, 220, 0.12);
  }

  /* canvas button centers sit chin * 1.5 and chin * 2.35 left of the LED
     center, which is itself bezel + chin * 0.9 from the right edge; these
     offsets place each button's right edge to land on the same centers */
  .crt-monitor .crt-btn:nth-of-type(1) {
    right: calc(var(--crt-bezel) + var(--crt-chin) * 3);
  }

  .crt-monitor .crt-btn:nth-of-type(2) {
    right: calc(var(--crt-bezel) + var(--crt-chin) * 2.15);
  }

  .crt-badge {
    position: absolute;
    /* printed on the chin, centered on the same line as the buttons/LED */
    z-index: 1;
    left: 50%;
    bottom: calc(var(--crt-chin) / 2);
    transform: translate(-50%, 50%);
    line-height: 1;
    /* exactly the canvas badge: fillText of the same spaced string at
       chin * 0.26 in the same mono stack — no letter-spacing, the spaces
       are in the markup */
    color: rgba(238, 242, 247, 0.28);
    font-family: ui-monospace, Menlo, monospace;
    font-size: calc(var(--crt-chin) * 0.26);
    letter-spacing: 0;
    white-space: nowrap;
    user-select: none;
  }

  .crt-led {
    position: absolute;
    z-index: 1;
    /* matches the canvas LED at bezel + chin * 0.9 from the right edge */
    right: calc(var(--crt-bezel) + var(--crt-chin) * 0.9);
    bottom: calc(var(--crt-chin) / 2);
    transform: translate(50%, 50%);
    /* canvas LED radius is chin * 0.11 */
    width: calc(var(--crt-chin) * 0.22);
    height: calc(var(--crt-chin) * 0.22);
    border-radius: 50%;
    background: #9fd49a;
    box-shadow:
      0 0 6px rgba(127, 179, 122, 0.9),
      0 0 14px rgba(127, 179, 122, 0.45);
  }

  /* the post-warp login lives in a modern-retro browser window: same
     dark plastic and seam essence as the CRT, but thin edges and a title
     bar carrying window dots, an address pill and the power LED. Every
     measure mirrors the canvas warpArrive geometry so the slab dissolve
     stays seamless. */
  .greeter.browser {
    --bf-edge: clamp(6px, 1.2vmin, 14px);
    --bf-top: clamp(34px, 5.2vmin, 52px);
    --crt-tube-edge: calc(
      min(100vw - var(--bf-edge) * 2, 100dvh - var(--bf-top) - var(--bf-edge)) * 0.07
    );
    padding: calc(var(--bf-top) + clamp(16px, 3vw, 32px))
      calc(var(--bf-edge) + clamp(16px, 3vw, 32px)) calc(var(--bf-edge) + clamp(16px, 3vw, 32px));
  }

  .browser-frame {
    position: absolute;
    inset: 0;
    z-index: 2;
    pointer-events: none;
  }

  .browser-frame::before {
    /* the same case plastic as the CRT — the essence carries over */
    content: '';
    position: absolute;
    inset: 0;
    background: linear-gradient(180deg, #2c302c 0%, #212421 16%, #151715 72%, #1f221f 100%);
    box-shadow: inset 0 1px 0 rgba(214, 232, 220, 0.16);
  }

  .browser-frame::after {
    /* the page viewport: flat glass in a thin recess; base stops match
       fillPane exactly so the canvas frame dissolves into this one */
    content: '';
    position: absolute;
    top: var(--bf-top);
    left: var(--bf-edge);
    right: var(--bf-edge);
    bottom: var(--bf-edge);
    border-radius: clamp(8px, 1.4vmin, 18px);
    box-shadow:
      0 0 0 2px rgba(0, 0, 0, 0.75),
      inset 0 0 0 1px rgba(127, 179, 122, 0.1),
      inset 0 0 0 calc(var(--crt-tube-edge) * 0.4) rgba(127, 179, 122, 0.05),
      inset 0 0 0 var(--crt-tube-edge) rgba(127, 179, 122, 0.045),
      inset 0 2px 10px rgba(0, 0, 0, 0.7);
    /* flat glass, NO radial washes — any center glow reads as a circle
       stamped on the page at the end of the warp */
    background:
      linear-gradient(180deg, rgba(214, 232, 220, 0.05), transparent 50%),
      linear-gradient(180deg, #050908 0%, #020404 45%, #000101 100%);
  }

  .browser-frame .bf-dot {
    position: absolute;
    z-index: 1;
    top: calc(var(--bf-top) / 2);
    transform: translateY(-50%);
    width: calc(var(--bf-top) * 0.28);
    height: calc(var(--bf-top) * 0.28);
    border-radius: 50%;
    background: rgba(214, 232, 220, 0.22);
  }

  /* dot centers sit at edge + top * (0.45 + i * 0.55), like the canvas */
  .browser-frame .bf-dot:nth-of-type(1) {
    left: calc(var(--bf-edge) + var(--bf-top) * 0.31);
  }

  .browser-frame .bf-dot:nth-of-type(2) {
    left: calc(var(--bf-edge) + var(--bf-top) * 0.86);
  }

  .browser-frame .bf-dot:nth-of-type(3) {
    left: calc(var(--bf-edge) + var(--bf-top) * 1.41);
  }

  .browser-frame .bf-addr {
    position: absolute;
    z-index: 1;
    left: 50%;
    top: calc(var(--bf-top) / 2);
    transform: translate(-50%, -50%);
    width: min(44%, calc(var(--bf-top) * 9));
    height: calc(var(--bf-top) * 0.52);
    line-height: calc(var(--bf-top) * 0.52);
    border-radius: 999px;
    background: rgba(0, 0, 0, 0.35);
    box-shadow: inset 0 0 0 1px rgba(127, 179, 122, 0.18);
    color: rgba(238, 242, 247, 0.28);
    font-family: ui-monospace, Menlo, monospace;
    font-size: calc(var(--bf-top) * 0.24);
    letter-spacing: 0;
    text-align: center;
    white-space: nowrap;
    overflow: hidden;
    user-select: none;
  }

  .browser-frame .bf-led {
    position: absolute;
    z-index: 1;
    top: calc(var(--bf-top) / 2);
    right: calc(var(--bf-edge) + var(--bf-top) * 0.45);
    transform: translate(50%, -50%);
    width: calc(var(--bf-top) * 0.24);
    height: calc(var(--bf-top) * 0.24);
    border-radius: 50%;
    background: #9fd49a;
    box-shadow:
      0 0 6px rgba(127, 179, 122, 0.9),
      0 0 14px rgba(127, 179, 122, 0.45);
  }

  .greeter.browser .regress {
    top: var(--bf-top);
    left: var(--bf-edge);
    right: var(--bf-edge);
    bottom: var(--bf-edge);
    border-radius: clamp(8px, 1.4vmin, 18px);
  }

  .greeter.browser .greeter-system-bar {
    top: calc(var(--bf-top) + clamp(14px, 2.6vh, 24px));
    left: calc(var(--bf-edge) + clamp(16px, 2.6vw, 30px));
    right: calc(var(--bf-edge) + clamp(16px, 2.6vw, 30px));
  }

  /* infinite regression: frame echoes drift toward the center and fade
     out well before reaching it, like video feedback between two screens.
     The convergence point follows the cursor via transform-origin, so
     inner (deeper) echoes parallax more than outer ones. */
  /* confined to the tube and sitting on the glass, under the picture */
  .regress {
    position: absolute;
    top: var(--crt-bezel);
    left: var(--crt-bezel);
    right: var(--crt-bezel);
    bottom: var(--crt-chin);
    border-radius: clamp(14px, 3.8vmin, 44px);
    z-index: 3;
    pointer-events: none;
    overflow: hidden;
  }

  .regress span {
    position: absolute;
    inset: 0;
    border: 1px solid rgba(127, 179, 122, 0.22);
    box-shadow: inset 0 0 26px rgba(127, 179, 122, 0.06);
    opacity: 0;
    transform-origin: calc(50% + var(--tunnel-x, 0px)) calc(50% + var(--tunnel-y, 0px));
    transition: transform-origin 400ms ease-out;
    animation: regress-in 11s linear infinite;
    will-change: transform, opacity;
  }

  .regress span:nth-child(1) { animation-delay: 0s; }
  .regress span:nth-child(2) { animation-delay: -2.2s; }
  .regress span:nth-child(3) { animation-delay: -4.4s; }
  .regress span:nth-child(4) { animation-delay: -6.6s; }
  .regress span:nth-child(5) { animation-delay: -8.8s; }

  @keyframes regress-in {
    0% {
      /* born just beyond the frame — each echo zooms in through it */
      transform: scale(1.12);
      opacity: 0;
    }
    10% {
      opacity: 0.6;
    }
    55% {
      opacity: 0.3;
    }
    80% {
      /* the tunnel dies out before the center — no vanishing point */
      transform: scale(0.24);
      opacity: 0;
    }
    100% {
      transform: scale(0.2);
      opacity: 0;
    }
  }

  /* the hero wordmark rides its own full-viewport canvas as a sibling of
     the greeter (same z, later in the DOM = painted above it), so its
     hyperspace streaks hold still while the monitor chrome dives past */
  .wordmark-stage {
    position: absolute;
    inset: 0;
    z-index: 1;
    pointer-events: none;
    /* the wordmark screen shoots in with the same overshoot + tilt as
       the boot screen (timed to the particle assembly finishing) and
       then holds a slow pan/zoom drift — the picture swaying inside the
       monitor tube */
    animation:
      screen-enter 950ms cubic-bezier(0.22, 1, 0.36, 1) 4.35s backwards,
      screen-drift 12s ease-in-out 5.3s infinite alternate;
  }

  /* bullet time tilts the whole scattered cloud on its axis: a slow,
     deep 3D pendulum around the frozen particles — the engine's own
     sway is near-stopped, so this carries the depth */
  .wordmark-stage.bullet-tilt {
    animation: bullet-tilt 7s ease-in-out infinite alternate;
  }

  @keyframes bullet-tilt {
    0% {
      transform: perspective(1000px) rotateY(8deg) rotateX(-3.5deg) scale(1.03);
    }
    100% {
      transform: perspective(1000px) rotateY(-8deg) rotateX(3.5deg) scale(1.06);
    }
  }

  .wordmark-stage .particle-wordmark {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
  }

  /* the clickable band over the wordmark itself; repulsion tracking runs
     on the root pointermove, so the cursor disturbs particles before the
     hotspot is even entered */
  .wordmark-hotspot {
    position: absolute;
    left: 8%;
    right: 8%;
    top: 27%;
    height: 46%;
    pointer-events: auto;
    border: 0;
    background: transparent;
    cursor: pointer;
    transition: none;
    -webkit-tap-highlight-color: transparent;
  }

  .wordmark-hotspot:disabled {
    cursor: default;
    pointer-events: none;
  }

  .wordmark-hotspot:focus-visible {
    outline: 1px solid rgba(127, 179, 122, 0.55);
    outline-offset: -8px;
  }

  /* bullet time pushes the camera in: the CRT frame swells slowly out
     past the viewport edges while the scattered glyphs hang in space —
     by the time the gate is teased, the chrome has mostly left the
     sides. The warp dive picks up from this scale. */
  .greeter.bullet-zoom {
    transform: scale(1.5);
    transition: transform 2600ms cubic-bezier(0.22, 1, 0.36, 1);
  }

  /* warp click: the camera dives deep into the tv — the whole screen
     (chrome, tunnel, glass) blows out through the foreground while the
     wordmark streaks to hyperspace on its own canvas; the frame stays
     solid past the halfway mark so it visibly flies by the camera.
     Starts at the bullet-zoom scale (bullet time always precedes the
     warp now). The 4s warp ride keeps going long after this 1600ms dive
     ends — WARP_DIVE_MS (3200) is when the next monitor starts forming */
  .greeter.warping {
    animation: warp-dive 1600ms cubic-bezier(0.55, 0, 0.85, 0.45) forwards;
  }

  /* ...deep in the tunnel the silver monitor laser-draws itself on the
     intro canvas (intro.warpArrive), slams in with overshoot, and the DOM
     flips in under the dissolving canvas slab — same flicker language as
     greeter-arrive; its login elements stay unlit until their seam traces
     fire */
  .greeter.warp-arrive {
    animation: warp-arrive 450ms both;
  }

  @keyframes warp-dive {
    0% {
      transform: scale(1.5);
      opacity: 1;
    }
    55% {
      opacity: 1;
    }
    100% {
      transform: scale(4.4);
      opacity: 0;
    }
  }

  @keyframes warp-arrive {
    0% {
      opacity: 0;
    }
    45% {
      opacity: 0.9;
    }
    60% {
      opacity: 0.3;
    }
    100% {
      opacity: 1;
    }
  }

  .greeter-system-bar {
    position: absolute;
    /* kept inside the CRT screen, clear of the bezel and its lip */
    top: calc(var(--crt-bezel) + clamp(14px, 2.6vh, 24px));
    left: calc(var(--crt-bezel) + clamp(16px, 2.6vw, 30px));
    right: calc(var(--crt-bezel) + clamp(16px, 2.6vw, 30px));
    display: flex;
    justify-content: space-between;
    color: var(--hg-text-dim);
    font-size: 11px;
    letter-spacing: 0;
  }

  .greeter-copy {
    display: grid;
    justify-items: center;
    gap: 14px;
    min-width: 0;
    text-align: center;
  }

  .user-glyph {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 4px;
    width: 58px;
    aspect-ratio: 1;
    padding: 8px;
    background: rgba(238, 242, 247, 0.04);
    box-shadow:
      0 0 0 1px rgba(127, 179, 122, 0.26),
      0 0 28px rgba(127, 179, 122, 0.12);
  }

  .user-glyph span {
    background: rgba(127, 179, 122, 0.18);
  }

  .user-glyph span:nth-child(2),
  .user-glyph span:nth-child(4),
  .user-glyph span:nth-child(5),
  .user-glyph span:nth-child(6),
  .user-glyph span:nth-child(8) {
    background: var(--hg-accent-primary);
  }

  .subtitle,
  .new-user p {
    margin: 0;
    color: var(--hg-text-muted);
    font-size: 12px;
    letter-spacing: 0;
  }

  .greeter-panel {
    display: grid;
    gap: 14px;
    width: min(430px, calc(100vw - 40px));
    min-width: 0;
  }

  .greeter .login-panel {
    background:
      linear-gradient(180deg, rgba(24, 30, 32, 0.84), rgba(11, 14, 15, 0.88));
    border-color: rgba(127, 179, 122, 0.32);
    box-shadow:
      0 18px 58px rgba(0, 0, 0, 0.42),
      0 0 0 1px rgba(238, 242, 247, 0.04);
  }

  .greeter .panel-header {
    justify-content: center;
    padding: 12px 16px 10px;
  }

  .greeter .panel-title {
    color: var(--hg-text-muted);
    font-size: 11px;
  }

  .greeter .tabs {
    border-color: rgba(127, 179, 122, 0.18);
  }

  .new-user {
    display: grid;
    gap: 6px;
    border: 1px solid rgba(214, 174, 104, 0.38);
    background: rgba(214, 174, 104, 0.05);
    padding: 12px;
    box-shadow: 0 14px 42px rgba(0, 0, 0, 0.26);
  }

  .new-user-button {
    border: 1px solid var(--hg-accent-warning);
    background: color-mix(in srgb, var(--hg-accent-warning) 10%, transparent);
    color: var(--hg-accent-warning);
    cursor: pointer;
    font: inherit;
    font-size: 11px;
    padding: 8px 12px;
  }

  .new-user-button:hover,
  .new-user-button:focus-visible {
    background: color-mix(in srgb, var(--hg-accent-warning) 18%, transparent);
    outline: none;
  }

  .loading-screen {
    place-items: center;
  }

  @keyframes crt-power {
    0% {
      transform: scaleY(0.02);
      filter: brightness(3);
    }
    38% {
      transform: scaleY(1);
      filter: brightness(1.8);
    }
    100% {
      transform: scaleY(1);
      filter: brightness(1);
    }
  }

  /* while the greeter is arriving, its elements wait unlit; once the slam
     lands (trace-go), each flickers on just as its canvas seam trace
     completes — delays mirror beginHandoff's trace schedule */
  .greeter.trace-in .user-glyph,
  .greeter.trace-in .subtitle,
  .greeter.trace-in .new-user,
  .greeter.trace-in .greeter-system-bar,
  .greeter.trace-in .login-panel {
    opacity: 0;
  }

  .greeter.trace-go .user-glyph {
    animation: elem-in 260ms steps(3, end) 380ms both;
  }

  .greeter.trace-go .greeter-system-bar {
    animation: elem-in 260ms steps(3, end) 460ms both;
  }

  .greeter.trace-go .subtitle {
    animation: elem-in 260ms steps(3, end) 560ms both;
  }

  .greeter.trace-go .login-panel {
    animation: elem-in 260ms steps(3, end) 740ms both;
  }

  .greeter.trace-go .new-user {
    animation: elem-in 260ms steps(3, end) 1080ms both;
  }

  @keyframes elem-in {
    0% {
      opacity: 0;
    }
    45% {
      opacity: 1;
    }
    65% {
      opacity: 0.3;
    }
    100% {
      opacity: 1;
    }
  }

  @keyframes boot-zoom-out {
    0% {
      transform: scale(1) translateX(0);
      animation-timing-function: cubic-bezier(0.45, 0, 0.55, 1);
    }
    34% {
      transform: scale(0.45) translateX(0);
    }
    49% {
      transform: scale(0.45) translateX(0);
      /* ≈ power3.inOut, matching the canvas pan */
      animation-timing-function: cubic-bezier(0.66, 0, 0.34, 1);
      opacity: 1;
    }
    100% {
      /* translate happens before the 0.45 scale, so -220vw lands ~-99vw */
      transform: scale(0.45) translateX(-220vw);
      opacity: 0.4;
    }
  }

  @keyframes greeter-arrive {
    0% {
      opacity: 0;
    }
    45% {
      opacity: 0.9;
    }
    60% {
      opacity: 0.3;
    }
    100% {
      opacity: 1;
    }
  }

  @keyframes line-in {
    0% {
      opacity: 0;
      transform: translateX(-6px);
    }
    35% {
      opacity: 0.9;
    }
    50% {
      opacity: 0.25;
    }
    100% {
      opacity: 1;
      transform: none;
    }
  }

  @media (prefers-reduced-motion: reduce) {
    .stage-3d[data-stage='boot'],
    .wordmark-stage,
    .wordmark-stage.bullet-tilt,
    .boot-lines span,
    .boot-lines span:last-child::after,
    .boot-exit,
    .greeter,
    .greeter.warping,
    .greeter.warp-arrive,
    .greeter.trace-go .user-glyph,
    .greeter.trace-go .greeter-system-bar,
    .greeter.trace-go .subtitle,
    .greeter.trace-go .login-panel,
    .greeter.trace-go .new-user {
      animation: none;
    }

    .greeter.bullet-zoom {
      transform: none;
      transition: none;
    }

    /* without the reveal animations the elements must not stay hidden */
    .greeter.trace-in .user-glyph,
    .greeter.trace-in .subtitle,
    .greeter.trace-in .new-user,
    .greeter.trace-in .greeter-system-bar,
    .greeter.trace-in .login-panel {
      opacity: 1;
    }

    .regress {
      display: none;
    }
  }

  @media (max-width: 760px) {
    .greeter {
      /* centered when it fits, top-anchored (scrollable) when it doesn't */
      align-content: safe center;
      overflow-y: auto;
      gap: clamp(18px, 3.5vh, 30px);
      /* thinner monitor on small screens; the badge goes with the bar */
      --crt-bezel: clamp(12px, 2.6vmin, 28px);
    }

    .crt-badge {
      display: none;
    }

    .boot-screen,
    .greeter,
    .loading-screen {
      min-height: 100dvh;
      padding: clamp(20px, 4vh, 32px) 20px;
    }

    .greeter {
      /* content stays clear of the thin mobile bezel */
      padding: calc(var(--crt-bezel) + 16px) calc(var(--crt-bezel) + 12px);
    }

    .greeter.browser {
      padding: calc(var(--bf-top) + 12px) calc(var(--bf-edge) + 12px) calc(var(--bf-edge) + 12px);
    }

    .greeter-system-bar {
      display: none;
    }

    .greeter-copy {
      gap: 8px;
    }
  }

  @media (max-height: 420px) {
    .greeter {
      padding-top: 14px;
      gap: 12px;
    }

    .user-glyph {
      width: 36px;
      padding: 6px;
      gap: 3px;
    }

    .greeter-copy {
      gap: 6px;
    }
  }
