/* ═══════════════════════════════════════════════════════════
   PORTFOLIO — CORE STYLESHEET
   Layer order: background → content → cursor → CRT overlay → preloader
   ═══════════════════════════════════════════════════════════ */

/* ── TOKENS ─────────────────────────────────────────────── */
:root {
  --bg:            #FBFBFB;
  --bg-boot:       #1a1614;
  --fg:            #2D2D2D;
  --fg-dim:        rgba(17, 17, 17, 0.25);
  --fg-dimmer:     rgba(17, 17, 17, 0.08);
  --accent:        #22c55e;
  --border:        rgba(17, 17, 17, 0.10);
  --border-strong: rgba(17, 17, 17, 0.20);

  --font-display: 'Bebas Neue', 'Arial Black', Impact, sans-serif;
  --font-name:    'Barlow Condensed', 'Arial Black', Impact, sans-serif;
  --font-ui:      'Space Grotesk', 'Helvetica Neue', Arial, sans-serif;
  --font-mono:    'VT323', 'Courier New', monospace;

  --name-fs: clamp(110px, 15vw, 220px);
  --name-lh: 0.82;
  --name-h:  calc(var(--name-fs) * var(--name-lh));

  --ease-out-quint: cubic-bezier(0.22, 1, 0.36, 1);
  --ease-in-out:    cubic-bezier(0.25, 0.46, 0.45, 0.94);
  --ease-bounce:    cubic-bezier(0.34, 1.56, 0.64, 1);

  /* z-index scale — kept here so layering is auditable in one place */
  --z-bg:        0;
  --z-content:   10;
  --z-side:      20;
  --z-nav:       60;
  --z-crt:       9500;
  --z-overlay:   9800;
  --z-preloader: 9900;
  /* Above everything, including the menu overlay (9700), the transition
     panels and the preloader — a cursor painted underneath a full-screen
     surface is simply gone, whatever colour it is. */
  --z-cursor:    10000;
}

/* ── RESET ──────────────────────────────────────────────── */
*, *::before, *::after { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  height: 100%;
  overflow: hidden;
}

body {
  background: var(--bg);
  color: var(--fg);
  font-family: var(--font-ui);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

/* Custom cursor replaces the system one across the whole document */
@media (pointer: fine) {
  body, body * { cursor: none !important; }
}

img { display: block; max-width: 100%; }

.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;
}

.skip-to-content {
  position: absolute;
  top: -100px; left: 16px;
  z-index: 10050;
  padding: 12px 20px;
  background: var(--fg);
  color: #fff;
  font-family: var(--font-ui);
  font-size: 13px;
  letter-spacing: 0.08em;
  text-decoration: none;
  transition: top 0.25s var(--ease-out-quint);
}
.skip-to-content:focus { top: 16px; }

/* ═══════════ BACKGROUND LAYERS ═══════════ */
.bg-layer {
  position: fixed;
  inset: 0;
  z-index: var(--z-bg);
  pointer-events: none;
  background: var(--bg);
}

.bg-canvas,
.fluid-canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
}

/* Held at 0 until the boot sequence hands over */
body.boot-pending .bg-canvas,
body.boot-pending .fluid-canvas { opacity: 0; }

.fluid-canvas { mix-blend-mode: multiply; }

.bg-noise {
  position: absolute;
  inset: 0;
  opacity: 0.035;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='4'/%3E%3C/filter%3E%3Crect width='180' height='180' filter='url(%23n)'/%3E%3C/svg%3E");
}

/* ═══════════ SIDE NAVIGATION ═══════════ */
.side {
  position: fixed;
  top: 50%;
  z-index: var(--z-side);
  display: flex;
  align-items: center;
  gap: 14px;
  text-decoration: none;
  color: var(--fg);
  transition:
    opacity   0.6s var(--ease-out-quint),
    transform 0.6s var(--ease-out-quint);
}

.side--left  { left: 40px;  transform: translateY(-50%); flex-direction: row; }
.side--right { right: 40px; transform: translateY(-50%); flex-direction: row; }

body.boot-pending .side--left  { opacity: 0; transform: translateY(-50%) translateX(-180%); }
body.boot-pending .side--right { opacity: 0; transform: translateY(-50%) translateX(180%); }

.side-text {
  writing-mode: vertical-rl;
  font-family: var(--font-ui);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.22em;
  white-space: nowrap;
  transition: color 0.4s var(--ease-out-quint), letter-spacing 0.4s var(--ease-out-quint);
}
.side--left .side-text { transform: rotate(180deg); }

.side-arrow {
  display: inline-block;
  transition: transform 0.45s var(--ease-bounce);
}

/* The numeral reads horizontally at display scale — only the label is
   rotated. Its accent colour comes from config via --glow. */
.side-nr {
  font-family: var(--font-display);
  font-size: clamp(96px, 15vh, 210px);
  line-height: 0.78;
  letter-spacing: -0.01em;
  color: var(--fg-dimmer);
  transition:
    color 0.55s var(--ease-out-quint),
    text-shadow 0.7s var(--ease-out-quint),
    transform 0.65s var(--ease-out-quint),
    opacity 0.55s var(--ease-out-quint);
}

.side:hover .side-text { letter-spacing: 0.34em; }
.side--left:hover  .side-arrow { transform: translateX(-6px); }
.side--right:hover .side-arrow { transform: translateX(6px); }

/* ── Active side ──────────────────────────────────────────
   The half the cursor is in lights its numeral in the section's accent
   colour, with a soft bloom behind it. The layered text-shadow is the
   bloom: three increasingly wide, increasingly transparent copies. */
body.cursor-left  .side--left  .side-nr,
body.cursor-right .side--right .side-nr {
  color: var(--glow, #ef4444);
  transform: scale(1.06);
  text-shadow:
    0 0 18px color-mix(in srgb, var(--glow, #ef4444) 55%, transparent),
    0 0 46px color-mix(in srgb, var(--glow, #ef4444) 38%, transparent),
    0 0 92px color-mix(in srgb, var(--glow, #ef4444) 24%, transparent);
  animation: sideGlowPulse 2.6s ease-in-out infinite;
}

body.cursor-left  .side--left  .side-text,
body.cursor-right .side--right .side-text { color: var(--glow, #ef4444); }

body.cursor-left  .side--left  .side-arrow,
body.cursor-right .side--right .side-arrow { color: var(--glow, #ef4444); }

/* The opposite side recedes rather than disappearing. */
body.cursor-left  .side--right,
body.cursor-right .side--left { opacity: 0.4; }

@keyframes sideGlowPulse {
  0%, 100% { filter: brightness(1); }
  50%      { filter: brightness(1.13); }
}

/* Browsers without color-mix still get the colour, just no bloom. */
@supports not (color: color-mix(in srgb, red 50%, transparent)) {
  body.cursor-left  .side--left  .side-nr,
  body.cursor-right .side--right .side-nr { text-shadow: 0 0 42px var(--glow, #ef4444); }
}

/* ═══════════ CONTENT: PORTRAIT + NAMEPLATE ═══════════ */
main {
  position: relative;
  z-index: var(--z-content);
  height: 100%;
}

.slide-group {
  position: relative;
  height: 100%;
  will-change: transform;
}

.portrait-section {
  position: absolute;
  left: 50%;
  /* Sized to fill most of the frame height and run close to the top edge —
     the portrait is the subject, not an element floating in whitespace. */
  bottom: clamp(40px, 6vh, 96px);
  transform: translateX(-50%);
  /* Height-driven so the portrait dominates on any aspect ratio, capped by
     width so it never spills sideways. A width-only clamp shrinks to ~40% of
     a tall phone viewport, which loses the whole composition. */
  width: min(84vh, 92vw, 780px);
  z-index: 2;
  will-change: transform;
}

body.boot-pending .portrait-section { opacity: 0; }

.portrait-frame {
  position: relative;
  width: 100%;
  /* Square, matching the head-cutout asset. */
  aspect-ratio: 1 / 1;

  /* The source cutout is cropped straight across at the shoulders, which
     leaves a hard rectangular edge sitting on top of the nameplate. Fading
     the last band dissolves the portrait into the type instead. Applied to
     the frame so the base, the masked illustration and the fluid-composited
     canvas all fade together. */
  -webkit-mask-image: linear-gradient(to bottom, #000 78%, rgba(0, 0, 0, 0.45) 92%, transparent 100%);
  mask-image: linear-gradient(to bottom, #000 78%, rgba(0, 0, 0, 0.45) 92%, transparent 100%);
}

.portrait-base,
.portrait-illustration,
.illustration-canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  object-position: bottom center;
  user-select: none;
}

/* The illustration is revealed by the gooey mask; the canvas copy is
   revealed by the fluid. Two independent reveal channels over one base. */
.portrait-illustration {
  mask: url(#liquidMask);
  -webkit-mask: url(#liquidMask);
}

.illustration-canvas { pointer-events: none; }
body.boot-pending .illustration-canvas { opacity: 0; }

/* ── Portrait reveal: CRT signal lock ─────────────────────
   Blooms in over-bright and desaturated, dips once, then settles — the look
   of a tube finding its signal. Replaces a 17-step on/off stutter that ran
   for 3.3s and read as a stuck page rather than an effect.

   Runs on .portrait-frame, NOT .portrait-section: main.js writes an inline
   transform on the section every frame for parallax, and an animation on the
   same property would fight it. Only opacity/transform/filter animate, so it
   stays on the compositor. */
.portrait-frame.is-striking {
  animation: portraitStrike 1.15s cubic-bezier(0.22, 1, 0.36, 1) both;
}

@keyframes portraitStrike {
  0%   { opacity: 0;    transform: scale(1.05); filter: brightness(2.1) contrast(0.6) saturate(0.25); }
  20%  { opacity: 1;                            filter: brightness(1.5) contrast(0.85) saturate(0.6); }
  32%  { opacity: 0.6;                          filter: brightness(1.7) contrast(0.8) saturate(0.5); }
  46%  { opacity: 1;                            filter: brightness(1.12) contrast(0.98) saturate(0.9); }
  100% { opacity: 1;    transform: scale(1);    filter: none; }
}

@media (prefers-reduced-motion: reduce) {
  .portrait-frame.is-striking { animation: none; }
}

/* ── Nameplate ── */
/* Bottom-anchored, not centred: the face overlaps its upper edge, which is
   what makes the name read as set *behind* the portrait rather than beside it. */
.name-section {
  position: absolute;
  left: 0;
  right: 0;
  /* Bleeds a little past the bottom edge so the type is cropped by the
     frame rather than sitting politely inside it. */
  bottom: -1.5vh;
  z-index: 1;
  display: flex;
  justify-content: center;
  pointer-events: none;
  will-change: transform;
}

body.boot-pending .name-section { opacity: 0; }

.name-display {
  display: flex;
  align-items: flex-start;
  justify-content: center;
  height: var(--name-h);
}

.name-slot {
  position: relative;
  height: var(--name-h);
  overflow: hidden;
  transition:
    width   0.42s var(--ease-out-quint),
    opacity 0.42s var(--ease-out-quint);
}
.name-slot.exiting { width: 0 !important; opacity: 0 !important; }

.name-reel {
  display: flex;
  flex-direction: column;
  will-change: transform;
}

.name-char {
  display: block;
  height: var(--name-h);
  line-height: var(--name-lh);
  font-family: var(--font-name);
  font-weight: 900;
  font-size: var(--name-fs);
  color: var(--fg);
  text-transform: uppercase;
  white-space: pre;
}

/* ═══════════ CUSTOM CURSOR ═══════════ */
.cursor { position: fixed; inset: 0; z-index: var(--z-cursor); pointer-events: none; }
@media (pointer: coarse) { .cursor { display: none; } }

.cursor-dot,
.cursor-ring {
  position: fixed;
  top: 0; left: 0;
  border-radius: 50%;
  transform: translate(-50%, -50%);
  pointer-events: none;
}

.cursor-dot {
  width: 6px; height: 6px;
  background: var(--fg);
  transition: width 0.3s var(--ease-out-quint), height 0.3s var(--ease-out-quint);
}

.cursor-ring {
  width: 34px; height: 34px;
  border: 1px solid var(--border-strong);
  transition:
    width  0.4s var(--ease-out-quint),
    height 0.4s var(--ease-out-quint),
    border-color 0.4s var(--ease-out-quint);
}

body.cursor-hover .cursor-ring { width: 56px; height: 56px; border-color: var(--fg); }
body.cursor-hover .cursor-dot  { width: 3px;  height: 3px; }

/* ── Cursor over dark full-screen surfaces ────────────────
   The cursor is near-black by default, which disappears entirely against the
   menu overlay, the transition panels and the boot screen — all of which are
   ~#1a1614. `on-dark` is set by nav.js and transition.js; `boot-pending`
   covers the preloader. */
body.on-dark .cursor-dot,
body.boot-pending .cursor-dot {
  background: #f4f4f4;
  box-shadow: 0 0 12px rgba(0, 0, 0, 0.45);
}

body.on-dark .cursor-ring,
body.boot-pending .cursor-ring {
  border-color: rgba(255, 255, 255, 0.55);
}

body.on-dark.cursor-hover .cursor-ring,
body.boot-pending.cursor-hover .cursor-ring {
  border-color: #fff;
}

/* ═══════════ PERSISTENT CRT OVERLAY ═══════════ */
.page-tv-overlay {
  position: fixed;
  inset: 0;
  z-index: var(--z-crt);
  pointer-events: none;
  overflow: hidden;
}

.page-scanlines,
.tv-scanlines {
  position: absolute;
  inset: 0;
  background: repeating-linear-gradient(
    to bottom,
    rgba(0,0,0,0.055) 0px,
    rgba(0,0,0,0.055) 1px,
    transparent 1px,
    transparent 3px
  );
}

.page-grain-canvas,
.tv-grain-canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  opacity: 0.055;
  mix-blend-mode: overlay;
}
.tv-grain-canvas { opacity: 0.10; }

/* Slow bands drifting down the screen */
.page-scanner-lines,
.tv-scanner-lines { position: absolute; inset: 0; overflow: hidden; }

.page-scanner-lines span,
.tv-scanner-lines span {
  position: absolute;
  left: 0; right: 0;
  height: 180px;
  background: linear-gradient(
    to bottom,
    transparent,
    rgba(255,255,255,var(--op, 0.10)),
    transparent
  );
  animation: pageScanDown var(--dur, 12s) linear infinite;
  animation-delay: var(--del, 0s);
}

@keyframes pageScanDown {
  0%   { transform: translateY(-200px); }
  100% { transform: translateY(100vh); }
}

.page-inner-glow,
.tv-inner-glow {
  position: absolute;
  inset: 0;
  box-shadow: inset 0 0 120px 20px rgba(255,255,255,0.16);
}

.page-vignette,
.tv-vignette {
  position: absolute;
  inset: 0;
  background: radial-gradient(
    ellipse at center,
    transparent 52%,
    rgba(0,0,0,0.11) 78%,
    rgba(0,0,0,0.28) 100%
  );
}

/* ── CRT barrel frame (clip-path set by crt-overlay.js) ── */
.page-tv-frame {
  position: fixed;
  inset: 0;
  z-index: 9600;
  pointer-events: none;
  background: var(--bg-boot);
  visibility: hidden;
}

.page-crt-bevel {
  position: fixed;
  inset: 0;
  z-index: 9550;
  pointer-events: none;
  box-shadow: inset 0 0 3px 1px rgba(0,0,0,0.20);
}

.page-tv-stroke {
  position: fixed;
  inset: 0;
  z-index: 9570;
  width: 100%;
  height: 100%;
  pointer-events: none;
}

/* ═══════════ PAGE TRANSITION OVERLAY ═══════════ */
#pageOverlay {
  position: fixed;
  inset: 0;
  z-index: var(--z-overlay);
  pointer-events: none;
  display: flex;
  align-items: center;
  justify-content: center;
}

#pageOverlay .tx-panel {
  position: absolute;
  top: 0;
  height: 100%;
  background: var(--tx-color, var(--bg-boot));
  transform: scaleY(0);
  transform-origin: bottom center;
}

.overlay-label {
  position: relative;
  z-index: 2;
  font-family: var(--font-display);
  font-size: clamp(40px, 7vw, 110px);
  letter-spacing: 0.04em;
  color: #fff;
  opacity: 0;
  text-transform: uppercase;
}

/* ═══════════ CRT BOOT PRELOADER ═══════════ */
.preloader {
  position: fixed;
  inset: 0;
  z-index: var(--z-preloader);
  background: var(--bg-boot);
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}
.preloader.is-done { display: none; }

/* Set by the inline head script when this session has already booted, so the
   CRT screen never paints on a return to home. */
html.skip-boot .preloader { display: none !important; }

/* ══════════ RETURN-TO-HOME ENTRANCE ══════════
   With the boot skipped the page would otherwise snap in fully formed. This
   staggers the pieces in from the edges they belong to: portrait up from the
   bottom, numerals in from the sides, nav down from the top.

   Each animation targets a CHILD of the elements main.js drives, never the
   elements themselves — the render loop writes inline transforms on
   .portrait-section and .name-section every frame for parallax, and animating
   the same property would fight it. */

/* Both paths share the choreography. `skip-boot` = returning to home,
   `is-entering` = set by preloader.js the moment the CRT hands over. */
html.skip-boot   .portrait-frame { animation: enterUp       0.95s cubic-bezier(0.22, 1, 0.36, 1) 0.05s both; }
html.is-entering .portrait-frame { animation: enterUpStrike 1.20s cubic-bezier(0.22, 1, 0.36, 1) 0.05s both; }

html.skip-boot .name-display,
html.is-entering .name-display   { animation: enterUp    0.95s cubic-bezier(0.22, 1, 0.36, 1) 0.20s both; }

html.skip-boot .side--left,
html.is-entering .side--left     { animation: enterLeft  0.85s cubic-bezier(0.22, 1, 0.36, 1) 0.38s both; }

html.skip-boot .side--right,
html.is-entering .side--right    { animation: enterRight 0.85s cubic-bezier(0.22, 1, 0.36, 1) 0.38s both; }

html.skip-boot .site-nav,
html.is-entering .site-nav       { animation: enterDown  0.75s cubic-bezier(0.22, 1, 0.36, 1) 0.50s both; }

/* Rise + CRT signal-lock as a single animation. Two separate ones on the same
   element would overwrite each other — only the last declared would run. */
@keyframes enterUpStrike {
  0%   { opacity: 0;    transform: translateY(70px); filter: brightness(2.1) contrast(0.6) saturate(0.25); }
  22%  { opacity: 1;                                 filter: brightness(1.5) contrast(0.85) saturate(0.6); }
  34%  { opacity: 0.62;                              filter: brightness(1.7) contrast(0.8) saturate(0.5); }
  48%  { opacity: 1;                                 filter: brightness(1.12) contrast(0.98) saturate(0.9); }
  100% { opacity: 1;    transform: translateY(0);    filter: none; }
}

@keyframes enterUp {
  from { opacity: 0; transform: translateY(70px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* The side links sit on translateY(-50%) for vertical centring — the keyframes
   have to carry it, or they'd snap down half their height mid-animation. */
@keyframes enterLeft {
  from { opacity: 0; transform: translateY(-50%) translateX(-70px); }
  to   { opacity: 1; transform: translateY(-50%) translateX(0); }
}

@keyframes enterRight {
  from { opacity: 0; transform: translateY(-50%) translateX(70px); }
  to   { opacity: 1; transform: translateY(-50%) translateX(0); }
}

@keyframes enterDown {
  from { opacity: 0; transform: translateY(-44px); }
  to   { opacity: 1; transform: translateY(0); }
}

@media (prefers-reduced-motion: reduce) {
  html.skip-boot .portrait-frame,
  html.skip-boot .name-display,
  html.skip-boot .side--left,
  html.skip-boot .side--right,
  html.skip-boot .site-nav,
  html.is-entering .portrait-frame,
  html.is-entering .name-display,
  html.is-entering .side--left,
  html.is-entering .side--right,
  html.is-entering .site-nav { animation: none; }
}

.tv-outer {
  position: relative;
  width: 100%;
  height: 100%;
  background: #0d0b0a;
  /* clip-path (barrel curve) applied by preloader.js */
}

.tv-screen { position: absolute; inset: 0; overflow: hidden; }

.tv-inner { position: absolute; inset: 0; will-change: transform; }

/* ── SMPTE colour bars ── */
.tv-bars {
  position: absolute;
  top: 0; left: 0; right: 0;
  height: 62%;
  display: flex;
}
.tv-bar { flex: 1 1 0; height: 100%; }

.tv-bars-bottom {
  position: absolute;
  top: 62%; left: 0; right: 0;
  height: 14%;
  display: flex;
}
.tv-bar-b { flex: 1 1 0; height: 100%; }

/* ── Barcode strips ── */
.tv-barcode {
  position: absolute;
  bottom: 0;
  height: 24%;
  width: 34%;
  background-image: repeating-linear-gradient(
    to right,
    #cfcfcf 0px, #cfcfcf 3px,
    #111 3px, #111 7px,
    #cfcfcf 7px, #cfcfcf 9px,
    #111 9px, #111 12px
  );
  opacity: 0.55;
}
.tv-barcode--left  { left: 0; }
.tv-barcode--right { right: 0; transform: scaleX(-1); }

/* ── Chaos blocks (positions assigned by preloader.js) ── */
.tv-chaos { position: absolute; inset: 0; }
.tv-cb {
  position: absolute;
  background: #ededed;
  mix-blend-mode: overlay;
  opacity: 0.85;
}
.tv-cross {
  position: absolute;
  width: 26px; height: 26px;
}
.tv-cross::before,
.tv-cross::after {
  content: '';
  position: absolute;
  background: rgba(255,255,255,0.75);
}
.tv-cross::before { left: 50%; top: 0; width: 2px; height: 100%; margin-left: -1px; }
.tv-cross::after  { top: 50%; left: 0; height: 2px; width: 100%; margin-top: -1px; }

/* ── Centre panel ── */
.tv-center {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 5;
}

.tv-panel {
  background: rgba(10,9,8,0.86);
  border: 2px solid rgba(255,255,255,0.14);
  padding: clamp(20px, 3vw, 40px) clamp(26px, 4vw, 54px);
  text-align: center;
  min-width: min(78vw, 460px);
}

.tv-msg {
  font-family: var(--font-display);
  font-size: clamp(34px, 5.5vw, 68px);
  line-height: 0.94;
  letter-spacing: 0.06em;
  color: #f2f2f2;
  margin-bottom: 22px;
}

.tv-panel-bar {
  display: flex;
  align-items: center;
  gap: 14px;
}

.tv-progress {
  flex: 1;
  height: 14px;
  background: rgba(255,255,255,0.10);
  overflow: hidden;
}
.tv-progress-fill { height: 100%; width: 0; }

.tv-pct {
  font-family: var(--font-mono);
  font-size: 22px;
  color: #f2f2f2;
  min-width: 56px;
  text-align: right;
}

.tv-skip-hint {
  margin-top: 16px;
  font-family: var(--font-mono);
  font-size: 17px;
  letter-spacing: 0.1em;
  color: rgba(255,255,255,0.55);
  opacity: 0;
  animation: tvSkipPulse 1.6s ease-in-out infinite;
}
.tv-skip-hint.is-visible { opacity: 1; }

@keyframes tvSkipPulse {
  0%, 100% { opacity: 0.35; }
  50%      { opacity: 0.9; }
}

.tv-skip-mobile  { display: none; }
@media (pointer: coarse) {
  .tv-skip-desktop { display: none; }
  .tv-skip-mobile  { display: inline; }
}

/* ── Glitch + grain canvases ── */
.tv-glitch-canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: 6;
  pointer-events: none;
}

/* ── BIOS handoff ── */
.tv-bios {
  position: absolute;
  inset: 0;
  z-index: 7;
  background: #0a0908;
  padding: clamp(18px, 4vw, 54px);
  font-family: var(--font-mono);
  font-size: clamp(15px, 1.5vw, 21px);
  line-height: 1.45;
  color: #d8d8d8;
  white-space: pre-wrap;
  display: none;
}
.tv-bios.is-visible { display: block; }

.bios-cursor {
  display: inline-block;
  width: 0.6em;
  background: #d8d8d8;
  animation: biosCursorBlink 0.75s steps(1) infinite;
}
@keyframes biosCursorBlink { 0%, 49% { opacity: 1; } 50%, 100% { opacity: 0; } }

/* ═══════════ RESPONSIVE ═══════════ */
@media (max-width: 900px) {
  .side--left  { left: 22px; }
  .side--right { right: 22px; }
  .side-nr     { font-size: clamp(44px, 8vw, 66px); }
  .side-text   { font-size: 11px; }
  /* Leave room for the side labels, otherwise the portrait sits over them. */
  .portrait-section { width: min(76vh, 82vw); }
}

@media (max-width: 600px) {
  :root { --name-fs: clamp(64px, 19vw, 120px); }
  .side-nr   { display: none; }
  .side-text { font-size: 10px; letter-spacing: 0.18em; }
  .side--left  { left: 14px; }
  .side--right { right: 14px; }
  .portrait-section { width: min(64vh, 90vw); }
  .page-inner-glow, .tv-inner-glow { box-shadow: inset 0 0 60px 10px rgba(255,255,255,0.12); }
}

/* ═══════════ REDUCED MOTION ═══════════
   The boot glitch, shake and grain are genuinely uncomfortable for
   motion-sensitive users. Hold the static composition instead. */
@media (prefers-reduced-motion: reduce) {
  .preloader { display: none !important; }

  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }

  .page-scanner-lines,
  .tv-scanner-lines,
  .page-grain-canvas,
  .tv-grain-canvas { display: none !important; }

  body.boot-pending .portrait-section,
  body.boot-pending .name-section,
  body.boot-pending .side,
  body.boot-pending .bg-canvas,
  body.boot-pending .fluid-canvas,
  body.boot-pending .illustration-canvas {
    opacity: 1 !important;
    transform: translateY(-50%) !important;
  }
  body.boot-pending .portrait-section { transform: translateX(-50%) !important; }
  body.boot-pending .name-section     { transform: none !important; }
}
