/* os.css — the page IS the monitor.
   ---------------------------------------------------------------------------
   No drawn bezel. The viewport is the tube: corner bulge, glass glare,
   scanlines, and chromatic aberration applied to the whole surface.

   DELIBERATELY DOM, NOT CANVAS. The reference renders its entire site into a
   <canvas> with one aria-label, which costs selectable text, crawlability and
   screen-reader access. Every CRT effect here is CSS or an SVG filter, so the
   same look sits over real HTML that a recruiter can select and a crawler can
   read. The nostalgia is a surface treatment, not a rendering strategy.

   Sequence: DARK (a single pulsing dot) -> POWER (tube switch-on) -> BOOT
   (short, and sci-fi rather than technical) -> FACE (the machine opens its
   eyes) -> READY (an ordinary scrollable page).
   --------------------------------------------------------------------------- */

:root {
  --display: "Helvetica Neue", Helvetica, Arial, sans-serif;
  --body: system-ui, -apple-system, "Helvetica Neue", Arial, sans-serif;
  --mono: ui-monospace, "SF Mono", Menlo, Consolas, monospace;

  --void:   #050706;      /* never pure black — it smears on OLED */
  --ink:    #0A0F0D;
  --fg:     #DCE6DD;
  --fg-dim: #7E8C84;
  --amber:  #F0A83C;
  --warm:   #FF6B35;
  --cool:   #2FD9C5;
}

* { box-sizing: border-box; }

/* [hidden] MUST WIN. The attribute maps to `display:none` in the UA stylesheet,
   which any author rule setting `display` on a class beats — so .below, which is
   display:grid, stayed on screen while its .hidden property read true. That is
   how the prose flashed between hello. and the eyes: the property was a claim,
   the computed style was the fact. Never trust element.hidden again without
   checking what the element actually DOES. */
[hidden] { display: none !important; }

html, body { height: 100%; }
body {
  margin: 0;
  background: var(--void);
  color: var(--fg);
  font-family: var(--body);
  font-size: 17px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  overflow-x: clip;
}

/* ============================================================== THE TUBE === */

/* everything the reader sees lives in here, so the glass sits over all of it */
.tube { position: relative; min-height: 100%; }

/* the phosphor layer — the only thing the chroma filter touches, because
   filtering the glare layers too would smear the highlights */
/* HALATION added on the researched numbers: light scattering inside the glass
   makes bright areas bleed into the dark around them. Emulation guidance puts
   realistic halation at radius 30 / opacity 0.12, so that is literally what
   this is. It is the difference between text that sits ON black and text that
   is GLOWING THROUGH glass. */
.phosphor {
  position: relative; z-index: 1;
  filter: url(#crt-chroma) drop-shadow(0 0 30px rgba(240, 168, 60, .12));
}

/* Corner falloff, NOT geometric barrel distortion — worth naming accurately.
   Real CRT curvature sits around 0.04 as a baseline, and warping the whole
   page by that much would bend the text for no legibility gain. The corner
   darkening carries the read of a curved tube without distorting a word. */
.crt-bulge {
  position: fixed; inset: 0; z-index: 40; pointer-events: none;
  background: radial-gradient(126% 108% at 50% 50%, transparent 54%, rgba(0, 0, 0, .92) 96%);
}
/* Scanlines, a soft top glare, and a vertical mask kept DELIBERATELY faint.
   Emulation guidance is that mask structures do not reproduce convincingly
   below roughly 5K pixel density — on a normal display a strong mask just
   aliases. At .014 it reads as texture rather than as a fake shadow mask. */
.crt-glass {
  position: fixed; inset: 0; z-index: 41; pointer-events: none;
  background:
    linear-gradient(180deg, rgba(220, 240, 235, .045), transparent 22%),
    repeating-linear-gradient(rgba(0, 0, 0, .22) 0 1px, transparent 1px 3px),
    repeating-linear-gradient(90deg, rgba(255, 255, 255, .014) 0 1px, transparent 1px 3px);
}
/* the tube's own glow, breathing very slightly */
.crt-glow {
  position: fixed; inset: 0; z-index: 39; pointer-events: none;
  background: radial-gradient(60% 50% at 50% 45%, rgba(240, 168, 60, .05), transparent 70%);
  animation: breathe 7s ease-in-out infinite;
}
@keyframes breathe { 0%, 100% { opacity: .8 } 50% { opacity: 1 } }

/* ============================================================ 1 · DARK ==== */

.gate {
  position: fixed; inset: 0; z-index: 50;
  display: grid; place-items: center;
  background: var(--void);
  cursor: pointer;
  transition: opacity .45s ease;
}
.gate[hidden] { display: none; }
/* The beacon is not a round dot. It obeys the same square-pixel rules as the
   eyes, because it IS an eye — shut, waiting. Pressing it is what opens it. */
.gate__dot {
  --px: 15px;
  display: grid;
  grid-template-columns: repeat(3, var(--px));
  gap: calc(var(--px) * .3);
  animation: beacon 1.9s linear infinite;
}
.gate__dot i {
  height: var(--px); border-radius: 2px;
  background: var(--amber);
  box-shadow: 0 0 16px rgba(240, 168, 60, .8), 0 0 48px rgba(240, 168, 60, .25);
}
.gate__dot i:nth-child(1), .gate__dot i:nth-child(3) { background: rgba(240, 168, 60, .22); box-shadow: none; }

/* PHOSPHOR, not a sine wave. Real phosphor snaps on and decays to about 10%
   over tens of milliseconds, so the pulse is a fast attack with a long fall.
   A symmetric ease-in-out is what makes fake CRTs read as fake. */
@keyframes beacon {
  0%   { opacity: 1;   transform: scaleY(1); }
  6%   { opacity: .96; }
  55%  { opacity: .52; }
  100% { opacity: .34; transform: scaleY(.94); }
}
.gate__hint {
  position: absolute; bottom: 16%;
  font-family: var(--mono); font-size: 11px; letter-spacing: .28em;
  text-transform: uppercase; color: var(--fg-dim);
  animation: fadein 1.1s ease 2.4s both;
}
.gate__hint b { color: var(--amber); font-weight: 400; }
@keyframes fadein { from { opacity: 0 } to { opacity: .85 } }

/* ============================================================ 2 · POWER ===
   A tube does not switch on cleanly. It arrives as noise, collapses to a hot
   line as the yoke energises, unfolds vertically with overshoot, then rolls and
   flickers while the vertical hold catches. The first version was a tidy white
   line expanding — correct in outline and far too clean to belong in here. */

.power {
  position: fixed; inset: 0; z-index: 49; pointer-events: none;
  display: grid; place-items: center; background: var(--void);
  overflow: hidden;
}
.power[hidden] { display: none; }

/* the burst of snow before there is a picture */
.power__snow {
  position: absolute; inset: -20%;
  background-image:
    repeating-conic-gradient(#fff 0deg 1deg, #000 1deg 2deg),
    repeating-linear-gradient(97deg, rgba(255,255,255,.5) 0 2px, transparent 2px 4px);
  background-size: 3px 3px, 5px 5px;
  opacity: 0;
  animation: snow .34s steps(3) forwards;
}
@keyframes snow {
  0%   { opacity: .30; transform: translate(0, 0); }
  40%  { opacity: .18; transform: translate(-2px, 3px); }
  75%  { opacity: .09; transform: translate(3px, -2px); }
  100% { opacity: 0; }
}

/* the yoke energising: everything collapses to one hot scanline.
   The line stays THIN and bright; a separate raster layer does the opening.
   Growing the line itself just stretched its horizontal gradient into a dull
   grey block, which looked like a loading bar rather than a tube. */
.power__line {
  position: absolute; left: 0; right: 0; top: 50%; height: 2px; margin-top: -1px;
  background: linear-gradient(90deg, transparent, #EAFFF7 12%, #fff 50%, #EAFFF7 88%, transparent);
  box-shadow: 0 0 40px 6px rgba(226, 255, 246, .85);
  transform: scaleX(.04);
  animation:
    yoke    .22s cubic-bezier(.2, .9, .2, 1) .26s both,
    lineout .34s ease .62s both;
}
@keyframes yoke    { from { transform: scaleX(.04); } to { transform: scaleX(1); } }
@keyframes lineout { to { opacity: 0; box-shadow: 0 0 0 0 rgba(226,255,246,0); } }

/* the raster opening out from the line, bright at its centre and falling off
   top and bottom, with an overshoot as it snaps to full height */
.power__raster {
  position: absolute; left: 0; right: 0; top: 50%; height: 2px;
  transform: translateY(-50%);
  background: linear-gradient(180deg,
    transparent 0%, rgba(226, 255, 246, .10) 34%, rgba(226, 255, 246, .34) 50%,
    rgba(226, 255, 246, .10) 66%, transparent 100%);
  animation:
    raster .48s cubic-bezier(.25, 1.45, .35, 1) .46s both,
    settle .44s steps(5) .94s both;
}
@keyframes raster {
  0%   { height: 2px;   opacity: 1; }
  70%  { height: 112vh; opacity: .62; }
  100% { height: 100vh; opacity: .30; }
}
/* the vertical hold catching: hard flickers, not a smooth fade */
@keyframes settle {
  0%   { opacity: .30; transform: translateY(-50%); }
  25%  { opacity: .52; transform: translateY(calc(-50% - 11px)); }
  50%  { opacity: .12; transform: translateY(calc(-50% + 7px)); }
  75%  { opacity: .28; transform: translateY(calc(-50% - 3px)); }
  100% { opacity: 0;   transform: translateY(-50%); }
}

/* ============================================================= 3 · BOOT === */

.boot {
  /* OVERLAYS the stage rather than stacking above it in flow. Stacked, the
     eyes woke a full viewport below the boot text and were never seen. */
  position: fixed; top: 0; left: 0; right: 0; bottom: 0; z-index: 20;
  /* height stated outright: the box was collapsing to its content (160px), so
     align-content:center had nothing to centre the lines within and they sat
     at the top of the screen */
  height: 100svh;
  background: var(--void);
  /* CENTRED, because this is the machine addressing you rather than the page
     being read. The hand-off to Juan's words is what moves everything left. */
  display: grid; align-content: center; justify-items: center;
  text-align: center;
  padding: 8vh 7vw;
  font-family: var(--mono);
  font-size: clamp(12px, 1.35vw, 15px);
  letter-spacing: .04em;
  color: var(--fg-dim);
}
.boot[hidden] { display: none; }
.boot__l { margin: 0 0 6px; white-space: pre-wrap; }
.boot__l b { color: var(--amber); font-weight: 400; }
.boot__l i { color: var(--cool); font-style: normal; }
.boot__l--big {
  font-family: var(--display); font-weight: 800; text-transform: lowercase;
  letter-spacing: -.03em; font-size: clamp(26px, 4vw, 44px);
  color: var(--fg); margin: 4px 0 18px;
}
.cursor {
  display: inline-block; width: .58em; height: 1.05em;
  background: var(--amber); vertical-align: -.16em; margin-left: 2px;
  animation: blink 1.05s steps(1) infinite;
}
@keyframes blink { 0%, 49% { opacity: 1 } 50%, 100% { opacity: 0 } }

/* ============================================================= 4 · FACE === */
/* Not a portrait. The machine's own face: two pixel eyes that open, blink and
   track the pointer a little, so it reads as looking back at you. */

/* The eyes are centred on screen while they are alone, then GLIDE to their
   place in the hero as the copy arrives. --fx/--fy are measured in JS, because
   the delta between "centre of the viewport" and "top-left of the hero" is not
   something CSS can know. Removing the class animates the transform back to 0,
   which is the whole trick. */
.face70--center { transform: translate(var(--fx, 0px), var(--fy, 0px)); }

.face70 {
  transition: transform .85s cubic-bezier(.22, .8, .24, 1);
  display: grid; grid-auto-flow: column; gap: clamp(20px, 3vw, 34px);
  justify-content: start; align-items: center;
  /* HUG THE EYES. As a stretched grid item this box spanned the whole content
     column, so its centre already equalled the viewport centre and the centring
     maths correctly computed a zero offset — I was measuring the container, not
     the eyes. Shrink-to-fit makes the box and the eyes the same thing. */
  justify-self: start;
  padding: 0 0 clamp(22px, 4vh, 40px);
}
.eye {
  --px: clamp(8px, 1.2vw, 12px);
  display: grid;
  grid-template-columns: repeat(3, var(--px));
  grid-template-rows: repeat(3, var(--px));
  gap: calc(var(--px) * .3);
  transform: translate(var(--ex, 0px), var(--ey, 0px));
  transition: transform .5s cubic-bezier(.2, .7, .3, 1);
}
.eye i {
  background: var(--amber);
  border-radius: 2px;
  box-shadow: 0 0 12px rgba(240, 168, 60, .7);
  /* persistence: the glow lags the pixel, the way phosphor lags the beam */
  transition: box-shadow 240ms cubic-bezier(.1, .7, .2, 1), background-color 60ms linear;
  animation: lid 5.6s ease-in-out infinite;
}
/* dim the corners into a rounded shape and burn the centre as a pupil —
   a fully-lit grid reads as a block, not an eye */
.eye i:nth-child(1), .eye i:nth-child(3),
.eye i:nth-child(7), .eye i:nth-child(9) { background: rgba(240, 168, 60, .20); box-shadow: none; }
.eye i:nth-child(5) {
  background: #FFF2DC;
  box-shadow: 0 0 16px rgba(255, 242, 220, .95), 0 0 30px rgba(240, 168, 60, .6);
}
.eye--r i { animation-delay: .04s; }         /* the lids are never quite in sync */
@keyframes lid {
  0%, 92%, 100% { transform: scaleY(1); }
  95%           { transform: scaleY(.06); }
}
/* eyes wake open rather than appearing */
.face70--wake .eye i { animation: wake .5s ease both, lid 5.6s ease-in-out 1s infinite; }
@keyframes wake { from { transform: scaleY(.04) } to { transform: scaleY(1) } }

/* ================================================================ THE ROLL ==
   A failing vertical hold: the frame slips and a bright band sweeps through.
   The reference leans on this harder than any other effect. Rare and brief on
   purpose — held longer it stops being a tube and starts being an effect. */

.roll {
  position: fixed; inset: 0; z-index: 42; pointer-events: none; opacity: 0;
  background: linear-gradient(180deg,
    transparent 0%, rgba(226, 255, 246, .05) 42%, rgba(226, 255, 246, .13) 50%,
    rgba(226, 255, 246, .05) 58%, transparent 100%);
}
.rolling .roll { animation: rollband .76s linear; }
@keyframes rollband {
  0%   { opacity: 0; transform: translateY(-100%); }
  8%   { opacity: 1; }
  92%  { opacity: 1; }
  100% { opacity: 0; transform: translateY(100%); }
}
/* the picture itself slips while the band passes */
.rolling .phosphor { animation: rollslip .76s cubic-bezier(.4, 0, .6, 1); }
@keyframes rollslip {
  0%, 100% { transform: translateY(0); }
  14%      { transform: translateY(-7px); }
  50%      { transform: translateY(3px); }
  78%      { transform: translateY(-2px); }
}

/* ============================================================== THE ICONS ==
   The objects from the workbench direction, brought back as Juan asked — not
   as drawn things this time but as PIXEL SPRITES, so they obey tier one and
   belong to the machine's own vocabulary. Each is an 8x8 grid: the machine is
   rendering him in the only alphabet it has. */

.icons { display: flex; flex-wrap: wrap; gap: clamp(20px, 3vw, 34px); margin-top: 38px; }
.icon { display: grid; justify-items: start; gap: 9px; }
.spr {
  --p: clamp(3px, .38vw, 4px);
  display: grid;
  grid-template-columns: repeat(8, var(--p));
  grid-auto-rows: var(--p);
  gap: 1px;
}
.spr i { background: transparent; border-radius: 1px; }
.spr i.on { background: var(--amber); box-shadow: 0 0 7px rgba(240, 168, 60, .55); }
/* the icons carry the same phosphor lag as the eyes */
.icon .spr i { transition: box-shadow 240ms cubic-bezier(.1, .7, .2, 1), background-color 120ms linear; }
.icon:hover .spr i.on { background: var(--warm); box-shadow: 0 0 12px rgba(255, 107, 53, .9); }
.icon__l {
  font-family: var(--mono); font-size: 9px; letter-spacing: .16em;
  text-transform: uppercase; color: var(--fg-dim);
}

/* ================================================================ GLITCH ==
   Kept, and kept SMALL: a brief chromatic tear fired only alongside a roll
   rather than on its own timer. Barrel warp was considered and REJECTED —
   real curvature baselines near 0.04, and warping the page that much bends the
   text for nothing. Burn-in was also REJECTED: on a personal site a permanent
   ghost reads as a dirty screen rather than as history. */
.glitching .phosphor { animation: tear .2s steps(2) 2; }
@keyframes tear {
  0%   { transform: translateX(0); }
  50%  { transform: translateX(-3px); }
  100% { transform: translateX(0); }
}

/* ============================================================ 5 · READY === */

.wrap { max-width: 940px; margin: 0 auto; padding: 0 7vw; }

/* LEFT, EVERYWHERE. The centred variant is retired: the eyes, the boot text and
   the headline all hang off one edge, so the sequence reads as one surface
   instead of three compositions. */

.stage { min-height: 100svh; display: grid; align-content: center; padding-block: 10vh; }

/* the copy is held back until the eyes have opened — the gap is the reveal */
.stage__copy > * {
  opacity: 0; transform: translateY(14px);
  transition: opacity .7s ease, transform .7s cubic-bezier(.2, .8, .25, 1);
}
.stage--full .stage__copy > * { opacity: 1; transform: none; }
.stage--full .stage__copy > *:nth-child(1) { transition-delay: .00s; }
.stage--full .stage__copy > *:nth-child(2) { transition-delay: .07s; }
.stage--full .stage__copy > *:nth-child(3) { transition-delay: .14s; }
.stage--full .stage__copy > *:nth-child(4) { transition-delay: .21s; }
.stage--full .stage__copy > *:nth-child(5) { transition-delay: .28s; }

/* the boot text dissolves rather than cutting */
.boot { transition: opacity .45s ease; }
/* --out fades it; the script then sets [hidden] so the eyes are genuinely
   alone on the screen rather than sitting on top of dimmed boot text */
.boot--out { opacity: 0; pointer-events: none; }
/* full-bleed blackout behind the measured boot column */
.boot::before { content: ""; position: fixed; inset: 0; z-index: -1; background: var(--void); }
.boot--out::before { opacity: 0; transition: opacity .55s ease; }
.stage[hidden] { display: none; }

.eyebrow {
  font-family: var(--mono); font-size: 11px; letter-spacing: .24em;
  text-transform: uppercase; color: var(--amber); margin: 0 0 20px;
  text-shadow: 0 0 12px rgba(240, 168, 60, .5);
}
.lede {
  font-family: var(--display); font-weight: 800; letter-spacing: -.035em;
  line-height: .98; text-transform: lowercase;
  font-size: clamp(30px, 5.4vw, 62px); margin: 0 0 24px; color: var(--fg);
  text-wrap: balance;
}
.lede em { font-style: normal; color: var(--warm); text-shadow: 0 0 30px rgba(255, 107, 53, .55); }
.say { margin: 0; max-width: 54ch; font-size: clamp(16px, 1.5vw, 18px); color: var(--fg-dim); }

/* THE SECOND SCREEN. The hero holds the eyes, the headline and one row of
   links — nothing that explains. The prose is one scroll down, so the first
   frame stays mysterious without the substance being hidden from anyone who
   wants it (or from a crawler, which reads it either way). */
.below {
  min-height: 62svh; display: grid; align-content: center;
  padding-block: 12vh 16vh;
  border-top: 1px solid rgba(220, 230, 221, .09);
}
.scrollcue {
  display: inline-flex; align-items: center; gap: 10px; margin-top: 40px;
  font-family: var(--mono); font-size: 10.5px; letter-spacing: .22em;
  text-transform: uppercase; color: var(--fg-dim);
}
/* the cue is a pixel too — it belongs to the machine, so it obeys tier one */
.scrollcue i {
  width: 7px; height: 7px; background: var(--amber); border-radius: 1px;
  box-shadow: 0 0 10px rgba(240, 168, 60, .8);
  animation: beacon 1.9s linear infinite;
}
.say strong { color: var(--fg); font-weight: 600; }

.links { display: flex; gap: 12px; flex-wrap: wrap; margin-top: 30px; }
.lk {
  font-family: var(--mono); font-size: 12px; letter-spacing: .12em;
  text-transform: uppercase; text-decoration: none;
  padding: 12px 18px; border-radius: 6px;
  border: 1px solid rgba(220, 230, 221, .18); color: var(--fg);
  background: rgba(220, 230, 221, .04);
  transition: border-color .2s, box-shadow .2s, color .2s;
}
.lk:hover { border-color: var(--cool); box-shadow: 0 0 22px rgba(47, 217, 197, .3); color: var(--fg); }
.lk--go {
  background: var(--amber); border-color: var(--amber); color: #14100A;
  box-shadow: 0 0 26px rgba(240, 168, 60, .45);
}
.lk--go:hover { background: var(--warm); border-color: var(--warm); color: #14100A; }

/* the chrome that stays on screen */
.hud {
  position: fixed; z-index: 45; left: 0; right: 0; bottom: 0;
  display: flex; align-items: center; gap: 16px;
  padding: 12px 4vw;
  font-family: var(--mono); font-size: 10.5px; letter-spacing: .18em;
  text-transform: uppercase; color: var(--fg-dim);
  pointer-events: none;
}
.hud > * { pointer-events: auto; }
.hud__r { margin-left: auto; display: flex; align-items: center; gap: 14px; }
.hud b { color: var(--fg); font-weight: 400; }
.led {
  display: inline-block; width: 7px; height: 7px; border-radius: 50%;
  background: var(--warm); box-shadow: 0 0 10px rgba(255, 107, 53, .9);
  animation: beacon 2.6s ease-in-out infinite;
}
.btn-x {
  background: none; border: 1px solid rgba(220, 230, 221, .2); color: var(--fg-dim);
  font: inherit; letter-spacing: .16em; text-transform: uppercase;
  padding: 6px 11px; border-radius: 4px; cursor: pointer;
}
.btn-x:hover { color: var(--fg); border-color: var(--amber); }
.btn-x[aria-pressed="true"] { color: var(--amber); border-color: var(--amber); }

.skip {
  position: fixed; z-index: 52; right: 4vw; top: 20px;
  font-family: var(--mono); font-size: 10.5px; letter-spacing: .18em;
  text-transform: uppercase;
  background: none; border: 1px solid rgba(220, 230, 221, .22); color: var(--fg-dim);
  padding: 7px 12px; border-radius: 4px; cursor: pointer;
}
.skip:hover { color: var(--amber); border-color: var(--amber); }
.skip[hidden] { display: none; }

/* --------------------------------------------------------------- guards */

@media (prefers-reduced-motion: reduce) {
  /* the whole sequence is motion. Anyone who has asked for less gets the page. */
  .gate, .power, .boot { display: none !important; }
  .crt-glow, .gate__dot, .led, .cursor, .eye i, .roll, .phosphor, .spr i { animation: none !important; }
  .stage { display: grid !important; }
}

@media (max-width: 620px) {
  body { font-size: 16px; }
  .boot { padding: 10vh 7vw; }
  .hud { font-size: 9.5px; letter-spacing: .12em; gap: 10px; }
  .hud__r { gap: 9px; }
}
