/* ternmail — a terminal held between two reaching hands.
   Codex blue comes up from the bottom-left, Claude orange down from the
   top-right. Design system is fixed here; nothing below invents its own. */

:root {
  --void:   #04050a;
  /* thinner than it looks: the signal filament runs behind this panel, and at
     0.70 the glass swallowed it whole. At 0.58 the 16px backdrop blur turns
     the wire into a soft band of light under the text instead of hiding it. */
  --pane:   rgba(8, 11, 18, 0.58);
  --text:   #dbe6ee;
  --muted:  #6c7f90;
  /* the site's own voice, for the annotations beside the replay. A touch
     lighter than --muted because it is set smaller and in a sans, and at
     --muted it dropped out of the page. Never used for anything ccx printed. */
  --note:   #7d8ea0;
  --edge:   rgba(120, 170, 220, 0.16);

  --blue:   #5aa2ff;   /* codex  — bottom-left hand */
  --orange: #ff9448;   /* claude — top-right hand   */

  --r:      2px;       /* terminals are sharp. one radius, near zero. */
  --dur:    220ms;
  --ease:   cubic-bezier(.2, .8, .2, 1);

  --mono: ui-monospace, "SF Mono", SFMono-Regular, "JetBrains Mono",
          "Cascadia Mono", Menlo, Consolas, monospace;

  --gap: 34rem;        /* terminal width; solved in terminal.js */
}

* { box-sizing: border-box; }

html, body {
  height: 100%;
  margin: 0;
  overflow: hidden;                 /* one page. it does not scroll. */
  background: var(--void);
  color: var(--text);
  font-family: var(--mono);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

/* ── the void ─────────────────────────────────────────────────────────────
   Each hand's corner is a light source, not a decorative pool: the light is
   anchored to the true corner the hand enters from, so the glow reads as
   spilling off that agent. Separate layers because opacity transitions and
   gradient backgrounds do not — these have to react to a keystroke. */
.void {
  position: fixed;
  inset: 0;
  overflow: hidden;
}

.glow {
  position: absolute;
  inset: -20%;                    /* overscan, so a scaled breathe never shows an edge */
  pointer-events: none;
  opacity: 0;
  /* additive, like the hands themselves — light in a void adds, it does not
     paint over. Also puts the glow above the vignette instead of under it. */
  mix-blend-mode: screen;
  transition: opacity 420ms var(--ease);
}
body.lit .glow--blue   { opacity: .9; }
body.lit .glow--orange { opacity: .85; }

.glow--blue {
  background:
    radial-gradient(64% 64% at 10% 90%, rgba(48, 118, 246, .52), transparent 70%),
    radial-gradient(30% 30% at  2% 98%, rgba(126, 186, 255, .40), transparent 72%);
  animation: breathe 12s var(--ease) infinite;
}
.glow--orange {
  background:
    radial-gradient(64% 64% at 90% 10%, rgba(255, 126, 40, .46), transparent 70%),
    radial-gradient(30% 30% at 98%  2%, rgba(255, 186, 116, .36), transparent 72%);
  /* a beat out of phase, so the two corners read as two presences breathing
     independently rather than one backdrop pulsing */
  animation: breathe 12s var(--ease) -5.5s infinite;
}

/* the corner answers its own hand — the same signal that flares the fingertip */
body.hit .glow,
body.hit--key  .glow--blue,
body.hit--warm .glow--orange { opacity: .92; }

/* Large dark gradients band badly on 8-bit panels, and the banding is most of
   what makes a dark page look cheap. A little noise dithers it away. */
.grain {
  position: absolute;
  inset: 0;
  pointer-events: none;
  opacity: .05;
  mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)'/%3E%3C/svg%3E");
}

/* keeps the corners reading as the light and the terminal as the subject */
.vignette {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: radial-gradient(72% 62% at 50% 50%, transparent 38%, rgba(0, 0, 0, .55) 100%);
}

@keyframes breathe {
  0%, 100% { transform: scale(1);    filter: brightness(.86); }
  50%      { transform: scale(1.06); filter: brightness(1.12); }
}

/* the drifting node constellation, drawn in terminal.js */
.mesh {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  opacity: 0;
  transition: opacity 2s var(--ease) .3s;
}
body.lit .mesh { opacity: 1; }

.stage {
  position: relative;
  height: 100%;
  display: grid;
  place-items: center;
}

/* ── the hands ────────────────────────────────────────────────────────────
   Each is placed by its own fingertip, not by its box: terminal.js sets
   --x/--y to the image's top-left so the tip lands on a terminal corner.
   `screen` drops the black ground and makes the mesh glow additive. */
.hand {
  position: absolute;
  left: 0;
  top: 0;
  width: var(--hand-s, 40vmax);   /* each hand sizes itself; see layout() */
  height: auto;
  mix-blend-mode: screen;
  pointer-events: none;
  user-select: none;
  opacity: 0;
  will-change: transform;
  /* Rotation pivots on the fingertip, so a rotated hand still points at exactly
     the same corner — the tip is the anchor, the arm is what swings. */
  transform-origin: var(--ox, 50%) var(--oy, 50%);
  transform: translate3d(calc(var(--x, 0px) + var(--px, 0px)),
                         calc(var(--y, 0px) + var(--py, 0px)), 0)
             rotate(var(--rot, 0deg));
  transition: opacity 1.6s var(--ease);
}
body.lit .hand { opacity: 1; }

.hand > img,
.hand > video { display: block; width: 100%; height: auto; }

/* Light, not geometry. It screens against the still inside the hand's own blend
   group, so it can only brighten pixels the still already draws — the pose, and
   therefore the fingertip the whole layout is measured from, stays the still's.
   `.hand`'s own mix-blend-mode is what isolates this pair into a group, so the
   two screens compose in the right order: glow onto hand, then hand onto void. */
.hand__glow,
.hand__surge,
.hand__pass {
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  mix-blend-mode: screen;
  pointer-events: none;
}

/* The one-shots: a surge when that agent does something, a pass when the reason
   crosses from the driver to the reviewer. Held at zero the rest of the time so a
   paused video's last frame is never sitting on screen. */
.hand__surge,
.hand__pass { opacity: 0; transition: opacity .2s linear; }
.hand.is-surging .hand__surge,
.hand.is-passing .hand__pass { opacity: 1; }

/* Held while that agent is working and nothing is being said. `screen` means a
   brighter layer adds more light, so brightness() on the glow alone reads as the
   mesh working harder without touching the still underneath. */
.hand__glow { transition: filter .45s var(--ease); }
.hand.is-working .hand__glow { filter: brightness(2.3); }

/* ── the reaching tips ────────────────────────────────────────────────────
   Sit on each fingertip and flare when a key lands. */
.spark {
  position: absolute;
  left: 0;
  top: 0;
  width: 17rem;
  height: 17rem;
  margin: -8.5rem 0 0 -8.5rem;
  border-radius: 50%;
  pointer-events: none;
  opacity: .45;
  filter: blur(3px);
  transform: translate3d(var(--sx, 0px), var(--sy, 0px), 0) scale(1);
  transition: opacity var(--dur) var(--ease), transform var(--dur) var(--ease);
}
.spark--blue   { background: radial-gradient(circle, rgba(90, 162, 255, .34), transparent 62%); }
.spark--orange { background: radial-gradient(circle, rgba(255, 148, 72, .32), transparent 62%); }
body.hit .spark,
body.hit--key .spark--blue,
body.hit--warm .spark--orange {
  opacity: .95;
  transform: translate3d(var(--sx, 0px), var(--sy, 0px), 0) scale(1.16);
}

/* ── the terminal ─────────────────────────────────────────────────────────
   The only object in the middle. The two hands reach for its corners. */
.term {
  position: relative;
  z-index: 2;
  width: var(--gap);
  height: min(46vh, 22rem);
  display: flex;
  flex-direction: column;
  background: var(--pane);
  border: 1px solid var(--edge);
  border-radius: var(--r);
  backdrop-filter: blur(16px) saturate(120%);
  -webkit-backdrop-filter: blur(16px) saturate(120%);
  box-shadow:
    0 0 0 1px rgba(0, 0, 0, .55),
    0 2rem 6rem rgba(0, 0, 0, .85),
    0 0 5rem rgba(90, 162, 255, .09),
    0 0 5rem rgba(255, 148, 72, .07);
  overflow: hidden;
  opacity: 0;
  transform: scale(.97);
  transition: opacity .9s var(--ease) .4s, transform .9s var(--ease) .4s;
}
body.lit .term { opacity: 1; transform: scale(1); }

/* the two corners the hands reach for, marked in each hand's colour */
.term::before,
.term::after {
  content: "";
  position: absolute;
  width: 14px;
  height: 14px;
  pointer-events: none;
}
.term::before {                     /* bottom-left, toward codex */
  left: -1px; bottom: -1px;
  border-left: 1px solid var(--blue);
  border-bottom: 1px solid var(--blue);
  box-shadow: -2px 2px .8rem rgba(90, 162, 255, .55);
}
.term::after {                      /* top-right, toward claude */
  right: -1px; top: -1px;
  border-right: 1px solid var(--orange);
  border-top: 1px solid var(--orange);
  box-shadow: 2px -2px .8rem rgba(255, 148, 72, .5);
}

.term__bar {
  flex: none;
  display: flex;
  align-items: center;
  gap: .6rem;
  padding: .55rem .85rem;
  border-bottom: 1px solid var(--edge);
  background: rgba(255, 255, 255, .022);
}
.term__dot {
  width: .42rem; height: .42rem;
  border-radius: 50%;
  background: var(--blue);
  box-shadow: 0 0 .55rem var(--blue);
  animation: pulse 3.2s var(--ease) infinite;
}
@keyframes pulse { 0%,100% { opacity: 1 } 50% { opacity: .3 } }
.term__title {
  font-size: .68rem;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--muted);
}

.term__body {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: .9rem 1rem 1.1rem;
  /* The ceiling is a column budget, not a taste call. The widest row in the
     replay is the run's own footer at 54 characters, and the box is 480px less a
     border, 2rem of padding and a 6px scrollbar — 440px. At .86rem a monospace
     advance is 8.26px and that line overflowed by two, orphaning "calls" on a
     row of its own. .82rem buys 55 columns with room to spare. */
  font-size: clamp(.76rem, 1.5vw, .82rem);
  line-height: 1.72;                 /* generous: this gets read, not skimmed */
  scrollbar-width: thin;
  scrollbar-color: var(--edge) transparent;
}
.term__body::-webkit-scrollbar { width: 6px; }
.term__body::-webkit-scrollbar-thumb { background: var(--edge); }

.row { white-space: pre-wrap; word-break: break-word; }
.row + .row { margin-top: .05rem; }
.row--cmd  { color: var(--text); }
.row--dim  { color: var(--muted); }
.row--key  { color: var(--blue); }
.row--warm { color: var(--orange); }
.row--gap  { height: .55rem; }

/* Narration: the site talking, never the tool.
   Everything else in this box is a shape ccx actually prints, and `dim` in
   particular is real metadata — the round count, the call budget, the reason the
   loop stopped. A visitor who cannot tell those apart from marketing copy has no
   reason to believe either. So narration leaves the monospace grid entirely and
   hangs off a rule: a different typeface at a glance, before a word is read. */
.row--note {
  font-family: ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI",
               system-ui, sans-serif;
  font-size: .92em;
  letter-spacing: .005em;
  color: var(--note);
  border-left: 1px solid var(--edge);
  padding-left: .62rem;
  margin-left: .12rem;
}
/* Narration wraps across two rows more often than it doesn't, and the .05rem
   step between rows breaks the left rule into dashes when it does. */
.row--note + .row--note { margin-top: 0; }
.row a { color: var(--blue); text-underline-offset: 3px; }
.row a:hover { color: var(--orange); }

.term__line { display: flex; align-items: baseline; }
.term__prompt { color: var(--blue); flex: none; margin-right: .45rem; }
.row--cmd .term__prompt { margin-right: .45rem; }
.term__typed { white-space: pre-wrap; word-break: break-word; }
.term__caret {
  display: inline-block;
  width: .5em;
  height: 1.05em;
  translate: 0 .16em;
  background: var(--orange);
  box-shadow: 0 0 .6rem rgba(255, 148, 72, .8);
  animation: blink 1.05s steps(1) infinite;
}
@keyframes blink { 0%, 55% { opacity: 1 } 56%, 100% { opacity: 0 } }
body.typing .term__caret { animation: none; opacity: 1; }

/* real input, parked under the render so mobile raises a keyboard */
.sink {
  position: absolute;
  inset: auto 0 0 0;
  height: 1px;
  opacity: 0;
  border: 0;
  padding: 0;
  font-size: 16px;                   /* < 16px makes iOS zoom on focus */
}

/* scanlines: the one skeuomorphic nod, kept under 4% so text stays sharp */
.term__scan {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: repeating-linear-gradient(
    to bottom, rgba(255,255,255,.03) 0 1px, transparent 1px 3px);
  mix-blend-mode: overlay;
}

.hint {
  position: fixed;
  left: 50%;
  bottom: 1.5rem;
  translate: -50% 0;
  margin: 0;
  font-size: .66rem;
  letter-spacing: .17em;
  text-transform: uppercase;
  color: var(--muted);
  opacity: 0;
  transition: opacity var(--dur) var(--ease);
}
.hint b { color: var(--blue); font-weight: 400; }
body.lit .hint { opacity: .75; }
body.touched .hint { opacity: 0; }

/* ── replaying ────────────────────────────────────────────────────────────
   A blinking caret under text that types itself reads as a live session. It
   is not one, so the prompt goes away and the bar says what this is. */
body.replaying .term__line { visibility: hidden; }
body.replaying .term__dot  { background: var(--orange); box-shadow: 0 0 .55rem var(--orange); }

.skip {
  position: absolute;
  right: .6rem; bottom: .5rem;
  display: none;
  padding: .2rem .5rem;
  font: inherit;
  font-size: .6rem;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--muted);
  background: rgba(8, 11, 18, .8);
  border: 1px solid var(--edge);
  border-radius: var(--r);
  cursor: pointer;
}
body.replaying .skip { display: block; }
.skip:hover { color: var(--text); }

/* ── tap targets ──────────────────────────────────────────────────────────
   Fixed, never a child of .stage: .stage is `place-items: center` with the
   terminal as its only grid item, so a sibling there re-centres the group and
   drags both hands with it. */
.chips {
  position: fixed;
  left: 50%; bottom: 1.1rem;
  translate: -50% 0;
  display: none;
  gap: .4rem;
  z-index: 3;
  max-width: 94vw;
  flex-wrap: wrap;
  justify-content: center;
}
.chips button {
  font: inherit;
  font-size: .68rem;
  padding: .34rem .6rem;
  color: var(--muted);
  background: rgba(8, 11, 18, .72);
  border: 1px solid var(--edge);
  border-radius: var(--r);
}
.chips button:active { color: var(--text); }

@media (max-width: 40rem) {
  /* shorter than desktop: the hands now come from above and below, and they
     need that vertical room more than the terminal needs the rows */
  .term { height: 40vh; }
  .spark { width: 10rem; height: 10rem; margin: -5rem 0 0 -5rem; }
}

@media (prefers-reduced-motion: reduce) {
  .void, .glow, .term__dot, .term__caret { animation: none; }
  .hand, .term, .hint, .mesh { transition: none; opacity: 1; }
  /* terminal.js already skips attaching the source, so this is only the backstop
     for a setting flipped after load — the still hand is the whole picture.
     Written as a child selector because `.hand > video` above is one class plus one
     type and outweighs a bare `.hand__glow` no matter which comes last. */
  .hand > .hand__glow,
  .hand > .hand__surge,
  .hand > .hand__pass { display: none; }
  .glow--blue { opacity: .62; }
  .glow--orange { opacity: .58; }
  .term { transform: none; }
  body.replaying .term__line { visibility: visible; }
  .skip { display: none !important; }
}

@media (hover: none) {
  .chips { display: flex; }
  .hint  { display: none; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   THE SHELL, ON THE VOID
   ───────────────────────────────────────────────────────────────────────────
   Appended. Nothing above this line is touched, and nothing below it changes
   the hands, the terminal or the light — every selector here is either the
   shared nav/footer from shell.css being re-placed for a page that cannot
   scroll, or a two-line nudge to the hint and the chips so the footer has
   somewhere to sit.

   The front page is one screen. It has no bottom to put a footer on, so the
   shell takes the free diagonal instead: nav in the top-left, footer in the
   bottom-right. Those are the two corners the hands do not enter from — blue
   comes up from the bottom-left, orange comes down from the top-right — so
   the chrome frames the composition on the axis the composition leaves empty.
   ═══════════════════════════════════════════════════════════════════════════ */

/* How much room the footer takes off the bottom of the viewport. Declared on
   the root, not on the footer, because the hint and the chips have to clear it
   and they are its siblings, not its children. */
:root { --foot-h: 2.5rem; }
@media (max-width: 40rem) { :root { --foot-h: 3.6rem; } }

/* Clicking dead space anywhere on this page puts you in the terminal. A
   full-width fixed nav would eat the top 52px of that, and the footer strip
   the bottom-right corner, so both are transparent to the pointer and only
   the links themselves are not. */
.shell-nav,
.shell-foot { pointer-events: none; }
.shell-nav a,
.shell-foot a { pointer-events: auto; }

/* This page never scrolls, so the nav never earns a background — it stays a
   line of text over the void, which is the only version of it that does not
   put a bar across the top of the picture. */
.shell-nav {
  --nav-h: 46px;
  background: none;
  border-bottom: 0;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}

/* Both the nav and the footer fade up with the rest of the picture. Driven by an
   animation rather than by `body.lit`, because `lit` is set by terminal.js — and
   with scripting off, a shell keyed to it would never appear at all, which is the
   one state where a visitor most needs a link out of here. */
.shell-nav,
.shell-foot { animation: shell-in 1.2s var(--ease) .5s both; }

@keyframes shell-in { from { opacity: 0; } to { opacity: 1; } }

/* The footer collapses to what a one-screen page can carry: the links and the
   line of small print, right-aligned, out of the blue hand's way. */
.shell-foot {
  position: fixed;
  inset: auto 0 0 auto;
  z-index: 60;
  max-width: min(100vw, 34rem);
  padding: 0 var(--pad) 1.1rem 0;
  border-top: 0;
}

.shell-foot__in {
  max-width: none;
  padding: 0;
  justify-content: flex-end;
  align-items: baseline;
  gap: .35rem 1.15rem;
}
.shell-foot__note { display: none; }          /* no room, and the terminal says it better */
.shell-foot__links { justify-content: flex-end; gap: .35rem 1.15rem; }
.shell-foot__links a { color: var(--muted); }
.shell-foot__legal {
  flex-basis: auto;
  padding-top: 0;
  border-top: 0;
  text-align: right;
}

@media (max-width: 40rem) {
  /* Two right-aligned rows on a phone, so the footer stays clear of the blue
     hand coming up through the bottom-left. */
  .shell-foot { max-width: 74vw; padding-right: var(--s-4); }
  .shell-foot__links,
  .shell-foot__legal { font-size: 11px; }
}

/* Both of these were measured off the bottom of the viewport, which is now
   where the footer lives. */
.hint  { bottom: calc(1.5rem + var(--foot-h)); }
.chips { bottom: calc(1.1rem + var(--foot-h)); }

@media (prefers-reduced-motion: reduce) {
  .shell-nav, .shell-foot { animation: none; opacity: 1; }
}

/* ── the front door with scripting off ────────────────────────────────────
   Everything above this line draws a terminal that has to be typed into, and
   a picture terminal.js lights. With scripts off the terminal is empty, the
   canvas never draws, and the page was a black rectangle carrying nav, footer
   and no product copy at all — the one page on the site that failed the rule
   the rest of it keeps.

   These rules only ever match inside <noscript>, so nothing here can reach the
   lit page. The hands stay exactly where they are underneath; this sits over
   them on its own scroll, and it takes the shell's tokens rather than
   inventing a second set. */
.ns {
  position: fixed;
  inset: 0;
  z-index: 40;                             /* under the nav (60), over the void */
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  /* literal rather than color-mix(): if the function is not understood the
     declaration is dropped and the panel goes transparent, which is the one
     failure mode this whole block exists to prevent. */
  background: rgba(4, 5, 10, 0.94);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
}
.ns__in {
  width: 100%;
  max-width: 62ch;
  margin: 0 auto;
  /* the tail clears the fixed footer bar, which is three wrapped rows at 375px */
  padding: calc(var(--nav-h) + var(--s-7)) var(--pad) calc(var(--s-7) + 7rem);
  font-family: var(--sans);
  font-size: var(--t-base);
  line-height: 1.65;
  color: var(--ink-2);
}
.ns__eyebrow {
  margin: 0;
  font-family: var(--mono);
  font-size: var(--t-xs);
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--muted);
}
.ns__h {
  margin: var(--s-3) 0 0;
  font-family: var(--mono);
  font-size: var(--t-2xl);
  font-weight: 400;
  line-height: 1.08;
  letter-spacing: -.02em;
  color: var(--text);
}
.ns__lede { margin: var(--s-5) 0 0; font-size: var(--t-lg); }
.ns__in p { margin: var(--s-4) 0 0; }
.ns__in p b { color: var(--text); font-weight: 500; }
.ns__in code {
  font-family: var(--mono);
  font-size: .92em;
  color: var(--text);
  overflow-wrap: break-word;
}
.ns__links { margin: var(--s-6) 0 0; padding: 0; list-style: none; }
.ns__links li {
  position: relative;
  margin: 0;
  padding: var(--s-2) 0 var(--s-2) var(--s-4);
}
.ns__links li::before { content: "·"; position: absolute; left: var(--s-2); color: var(--blue); }
.ns__in a { color: var(--blue); text-underline-offset: 3px; }
.ns__in a:hover { color: var(--orange); }

/* html and body are `overflow: hidden` on this page, so the overlay has to be
   the thing that scrolls and the shell has to stay fixed on top of it. Both
   the nav and the footer strip are transparent here by design — over a picture
   that is correct, over scrolling paragraphs it is text on text. So they earn a
   ground, but only in this one state.

   `:has(.ns)` is what scopes it: <noscript> content is not parsed into the DOM
   at all while scripting is on, so `.ns` cannot exist on the lit page and these
   rules cannot reach it. A browser without :has() drops them and gets the
   transparent shell, which is the old behaviour rather than a broken one. */
body:has(.ns) .shell-nav {
  background: rgba(4, 5, 10, 0.96);
  border-bottom: 1px solid var(--line);
}
/* And it spans the full width rather than staying a 74vw corner strip: at 375px
   the strip left a band of paragraph showing beside it, so the last two lines of
   every screenful read as text printed over text. A bar with an edge is the only
   version of this that is legible while something scrolls underneath it. */
body:has(.ns) .shell-foot {
  inset: auto 0 0 0;
  max-width: none;
  padding: .6rem var(--pad);
  background: rgba(4, 5, 10, 0.96);
  border-top: 1px solid var(--line);
}

/* The footer took a third row when Docs, Receipts and Changelog joined it, so
   the strip is 81px on a phone against a --foot-h of 57.6px — which left the
   hint sitting one pixel above the first footer link. --foot-h is the single
   number both the hint and the chips are measured from, so it is the only thing
   that has to move. Desktop is unchanged: there the footer is a right-hand
   corner strip and the hint is centred, so they never share a column. */
@media (max-width: 40rem) {
  :root { --foot-h: 5.2rem; }
}
