/* ============================================================================
   monitor.css — "through the glass": the opening sequence.

   READ FIRST: everything that pins, stacks, hides or positions in 3D lives
   under `.mon.is-live`, which monitor.js adds only after every guard passes.
   Without it this section is an ordinary readable stack — headline, monitor,
   seven captioned screenshots, a closing link. This is the first screen of
   the site; a hero that renders black when a script fails loses the visitor
   before they have read a word.

   The monitor lives inside a GENERATED desk photograph (assets/desk.jpg),
   but the screen is still a real box the code knows exactly: an overlay
   positioned by --sx/--sy/--sw/--sh, percentages measured from the photo's
   own pixels (see the canvas-walk note in index.html). Regenerate the photo
   and those numbers must be re-measured, or the camera flies through the
   wrong part of the desk.
   ========================================================================== */

/* ---------- Base: a readable stack --------------------------------------- */
.mon{position:relative;background:var(--bg)}
.mon-stage{
  max-width:var(--maxw);margin-inline:auto;
  padding:clamp(56px,9vh,110px) var(--space-gutter) clamp(40px,6vh,80px);
  display:flex;flex-direction:column;gap:clamp(32px,5vh,56px);
}

.mon-intro{max-width:44ch}
.mon-intro h1{margin:14px 0 16px}
.mon-intro p{color:var(--fg-muted);font-size:var(--text-lg);max-width:46ch}
.mon-vet{
  display:inline-flex;align-items:center;gap:12px;
  color:var(--accent);font-weight:600;letter-spacing:.13em;font-size:.72rem;
  text-transform:uppercase;
}
.mon-vet::before{content:'';width:26px;height:1px;background:currentColor;flex:none;opacity:.8}

/* ---------- The monitor: a generated desk scene ---------- */
/* The photograph replaced the drawn display, but the contract that made the
   drawn version work is kept: the SCREEN is still a real box the code knows
   exactly — an overlay positioned by --sx/--sy/--sw/--sh, percentages
   measured from the photo's own pixels. setOrigin() in monitor.js walks this
   overlay's offsets, so the camera still flies through the glass, not the
   middle of the desk. */
.mon-rig{width:min(88vw,1100px);margin-inline:auto}
.mon-scene{position:relative;border-radius:var(--radius-lg);overflow:hidden;box-shadow:var(--shadow-lg)}
.mon-scene img{display:block;width:100%;height:auto}

.mon-screen{
  position:absolute;
  left:var(--sx,28%);top:var(--sy,16%);
  width:var(--sw,44%);height:var(--sh,47%);
  overflow:hidden;
  /* Everything drawn on the glass sizes against the GLASS, not the viewport.
     The wordmark used to be set in vw, which was fine when the screen was 39.8%
     of the photo and broke the moment the new desk shot made it 19.2% — the
     badge and brand name overflowed a box less than half the old width and got
     clipped, which is the misalignment Jonathan saw. */
  container-type:size;
  /* the photo's panel is already black; this only deepens the corners so the
     glow and reticle sit IN the glass rather than printed on it */
  background:radial-gradient(ellipse at 50% 50%, transparent 55%, rgba(0,0,0,.45));
}
.mon-glow{
  position:absolute;inset:0;opacity:.35;
  background:radial-gradient(ellipse at 50% 62%,
    color-mix(in srgb, var(--accent) 26%, transparent), transparent 65%);
}

/* The screen's idle state: the brand, lit like a boot splash. HTML text so it
   survives the zoom; the panel behind it is black in every theme, so the
   near-white is a screen colour, not a theme colour. */
.mon-wordmark{
  position:absolute;inset:0;
  display:flex;flex-direction:column;align-items:center;justify-content:center;
  gap:4cqh;
  /* cqw = 1% of the screen's own width, so this lockup holds its proportions
     whatever rectangle the photo's monitor happens to occupy. */
  font-size:7cqw;
}
.mon-wordmark b{
  display:grid;place-items:center;
  width:14cqw;height:14cqw;
  background:var(--accent);color:var(--accent-ink);
  font-family:var(--font-display);font-weight:600;
  font-size:5.6cqw;letter-spacing:.02em;
  border-radius:2.5cqw;
}
.mon-wordmark > span{
  font-family:var(--font-display);font-weight:600;
  /* cqw, not vw: sized against the glass it sits on. In vw this was 1.5rem on
     a screen that is now 211px wide — the brand name filled 99% of it. */
  font-size:6.4cqw;letter-spacing:.03em;line-height:1;
  white-space:nowrap;
  color:rgba(255,250,242,.92);
  text-shadow:0 0 30px color-mix(in srgb, var(--accent) 40%, transparent);
}

/* The front-sight reticle: a centre post flanked by two shorter ears, all in
   brass. The brand mark, drawn where the camera is about to go. */
.mon-reticle{
  position:absolute;left:50%;top:50%;translate:-50% -50%;
  width:2px;height:26%;background:var(--accent);
  box-shadow:0 0 18px color-mix(in srgb, var(--accent) 55%, transparent);
}
.mon-reticle::before,
.mon-reticle::after{
  content:'';position:absolute;top:24%;width:2px;height:52%;
  background:var(--accent);opacity:.75;
}
.mon-reticle::before{left:-46px}
.mon-reticle::after{right:-46px}

/* The oversized type behind the scene. Stroke-only Excon so it reads as
   draftwork behind the object, not a competing headline. Hidden in the
   stacked fallback — it is depth dressing, not content. */
.mon-type{display:none}
.mon.is-live .mon-type{
  display:flex;flex-direction:column;align-items:center;justify-content:center;
  position:absolute;inset:0;z-index:0;pointer-events:none;
  will-change:transform,opacity;
}
.mon.is-live .mon-type span{
  font-family:var(--font-display);font-weight:800;
  font-size:clamp(120px,17vw,260px);line-height:.86;letter-spacing:.01em;
  /* -webkit-text-fill-color, not color:transparent — WebKit paints the fill
     BLACK with the plain color keyword once the layer is composited, which is
     exactly what Jonathan's Safari screenshot showed. Stroke colours are
     plain rgba for the same reason: Safari drops the whole shorthand when a
     color-mix() sits inside it. */
  color:rgba(255,255,255,.16);
  -webkit-text-fill-color:transparent;
  -webkit-text-stroke:1px rgba(255,255,255,.16);
}
.mon.is-live .mon-type span:last-child{
  color:rgba(217,149,74,.55);
  -webkit-text-stroke:1px rgba(217,149,74,.55);
}
.mon.is-live .mon-rig{z-index:2}
.mon.is-live .mon-intro{z-index:5}

/* ---------- The corridor, as a stack ---------- */
.mon-corridor{display:flex;flex-direction:column;gap:clamp(28px,5vh,52px)}
.mon-panel{display:block;max-width:900px;margin-inline:auto;text-decoration:none;color:inherit}
.mon-panel img{
  display:block;width:100%;height:auto;
  border-radius:var(--radius-lg);border:1px solid var(--border-soft);
}
.mon-cap{
  display:flex;gap:12px;align-items:baseline;justify-content:center;
  margin-top:12px;color:var(--fg-muted);
  font-size:.72rem;letter-spacing:.14em;text-transform:uppercase;
}
.mon-cap b{
  font-family:ui-monospace,SFMono-Regular,Menlo,monospace;
  color:var(--accent);font-weight:600;font-variant-numeric:tabular-nums;
}

.mon-outro{text-align:center}
.mon-outro p{color:var(--fg-muted);margin-bottom:18px;font-size:var(--text-lg)}

.mon-cue{display:none}

/* ---------- Live: pinned, scrubbed, through the glass --------------------- */
.mon.is-live{height:var(--mon-length,700vh)}
.mon.is-live .mon-stage{
  position:sticky;top:0;height:100vh;max-width:none;overflow:hidden;
  padding:0;display:block;
  /* Real 3D for the scene's lean — rotations without a parent perspective
     render as flat skews, which is why the old drift read as nothing. */
  perspective:1100px;perspective-origin:50% 45%;
}

.mon.is-live .mon-intro{
  position:absolute;left:var(--space-gutter);top:clamp(88px,14vh,150px);
  z-index:5;will-change:opacity;
}

.mon.is-live .mon-rig{
  position:absolute;left:50%;top:54%;translate:-50% -50%;
  width:min(84vw,1240px);
  will-change:transform,opacity;
  /* No CSS transition — monitor.js writes transform every frame, and a
     transition on top of a per-frame write fights the scrub. */
}

.mon.is-live .mon-reticle{opacity:0;will-change:opacity,filter}
.mon.is-live .mon-wordmark{will-change:opacity}
.mon.is-live .mon-scene{border-radius:0;box-shadow:none;overflow:visible}

/* ---- the void -------------------------------------------------------------
   Deep space with a warm vanishing point dead centre — the Star Wars opening
   read: you are not looking at a wall of sites, you are travelling through
   somewhere. Hidden until the sequence arms, so the base layout stays a plain
   readable stack. */
.mon-void{display:none}
.mon.is-live .mon-void{
  display:block;position:absolute;inset:0;z-index:1;
  opacity:0;overflow:hidden;pointer-events:none;
  will-change:opacity,transform;
}
.mon.is-live .mon-void img{
  position:absolute;left:50%;top:50%;translate:-50% -50%;
  min-width:100%;min-height:100%;width:auto;height:auto;
  object-fit:cover;
}

.mon.is-live .mon-corridor{
  position:absolute;inset:0;z-index:2;gap:0;
  perspective:900px;perspective-origin:50% 50%;
  opacity:0;will-change:opacity;
}
.mon.is-live .mon-panel{
  position:absolute;left:50%;top:50%;
  width:var(--w,72vw);max-width:none;margin:0;
  will-change:transform,opacity;backface-visibility:hidden;
}
.mon.is-live .mon-panel img{
  border-radius:3px;border:0;
  box-shadow:0 30px 90px rgba(0,0,0,.35);
}
.mon.is-live .mon-cap{
  font-size:.78rem;
  text-shadow:0 1px 18px color-mix(in srgb, var(--bg) 80%, transparent);
}

/* ---- the code stream ------------------------------------------------------
   Fragments of what the engine actually emits — markup, schema, the CSS that
   lays a page out — flying past the camera between the sites. It exists only
   inside the corridor: base layout hides it, so a no-JS visitor never sees
   loose code on a marketing page.

   aria-hidden and pointer-events:none — this is texture, not content, and it
   must never intercept a click meant for a panel behind it. */
.mon-code{display:none}
.mon.is-live .mon-code{
  display:block;position:absolute;inset:0;z-index:3;
  perspective:900px;perspective-origin:50% 50%;
  opacity:0;pointer-events:none;will-change:opacity;
}
.mon.is-live .mon-code span{
  position:absolute;left:50%;top:50%;
  /* Start invisible. The render loop skips writing to fragments outside its
     depth window, so any fragment the camera has not reached yet is never
     touched — without this it kept the default opacity of 1 and, having no
     transform either, sat at dead centre. Every untouched fragment stacked on
     that one spot and read as a bright unreadable band across the middle. */
  opacity:0;
  font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
  font-size:clamp(.7rem,1vw,1rem);
  letter-spacing:.02em;white-space:nowrap;
  color:var(--accent);
  text-shadow:0 0 10px rgba(0,0,0,.85), 0 0 22px color-mix(in srgb, var(--accent) 55%, transparent);
  will-change:transform,opacity;backface-visibility:hidden;
}

/* Motion here is decoration on top of decoration. If the visitor asked for
   less of it, the corridor still tells the story on its own. */
@media (prefers-reduced-motion: reduce){
  .mon.is-live .mon-code{display:none}
}

.mon.is-live .mon-outro{
  position:absolute;left:50%;bottom:clamp(30px,7vh,72px);translate:-50% 0;
  z-index:6;opacity:0;pointer-events:none;will-change:opacity;
}

.mon.is-live .mon-cue{
  display:flex;align-items:center;gap:10px;
  position:absolute;left:50%;bottom:26px;translate:-50% 0;z-index:5;
  color:var(--fg-muted);font-size:.74rem;letter-spacing:.18em;text-transform:uppercase;
}
.mon.is-live .mon-cue i{
  width:1px;height:26px;background:currentColor;opacity:.5;
  animation:mon-cue 1.9s ease-in-out infinite;
}
@keyframes mon-cue{0%,100%{transform:scaleY(.35);transform-origin:top}50%{transform:scaleY(1);transform-origin:top}}

@media (prefers-reduced-motion: reduce){
  .mon.is-live .mon-cue i{animation:none}
  .mon.is-live .mon-rig,
  .mon.is-live .mon-panel{will-change:auto}
  .mon.is-live .mon-code span{will-change:auto}
}

/* ---- rest state -----------------------------------------------------------
   monitor.js stamps `is-resting` whenever the scrub has converged and the
   pointer is still, and lifts it on the first frame of new input. Compositor
   hints are promises about imminent change; a resting sequence has none to
   make, so every layer hands its memory back. The rig manages its own hint
   inline (rigHinted) and is deliberately absent here. */
.mon.is-live.is-resting .mon-type,
.mon.is-live.is-resting .mon-intro,
.mon.is-live.is-resting .mon-reticle,
.mon.is-live.is-resting .mon-wordmark,
.mon.is-live.is-resting .mon-void,
.mon.is-live.is-resting .mon-corridor,
.mon.is-live.is-resting .mon-panel,
.mon.is-live.is-resting .mon-code,
.mon.is-live.is-resting .mon-code span,
.mon.is-live.is-resting .mon-outro{will-change:auto}
