@font-face {
  font-family: "JetBrains Mono";
  src: url("assets/fonts/JetBrainsMono-Regular.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "JetBrains Mono";
  src: url("assets/fonts/JetBrainsMono-Bold.woff2") format("woff2");
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}
/* =========================================================================
   STRAY FRAME - Linux tty homepage
   type: JetBrains Mono (self-hosted woff2, OFL) with system mono fallbacks.
   ========================================================================= */

:root {
  /* pure black so the CRT refresh sweep (color-dodge) is a no-op over empty
     space and only brightens the text it passes over */
  --bg:        #000000;
  --fg:        #d8d8d8;
  --dim:       #8a8a8a;
  --dimmer:    #4a4a4a;
  --accent:    #d8d8d8;
  --accent-lo: #5a5a5a;

  --mono: "JetBrains Mono", ui-monospace, "Cascadia Mono", "Cascadia Code",
          "SF Mono", "Segoe UI Mono", Menlo, Consolas, monospace;

  --pad: clamp(1rem, 4vw, 3rem);
}

* { box-sizing: border-box; }

html, body { height: 100%; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--fg);
  font-family: var(--mono);
  font-size: clamp(14px, 1rem, 15px);
  line-height: 1.42;
  letter-spacing: 0;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overflow-x: hidden;
}

body.cold {
  background: #000;
}

::selection { background: var(--accent); color: #0a0a0a; }

a { color: var(--accent); text-underline-offset: 3px; }

/* accessibility: keyboard skip link, hidden until focused */
.skip-link {
  position: fixed; top: 0; left: 0; z-index: 100;
  transform: translateY(-120%);
  background: var(--accent); color: #0a0a0a;
  padding: .4em .8em; text-decoration: none; font-weight: 700;
}
.skip-link:focus { transform: translateY(0); }

/* -------------------------------------------------------------------------
   layout
   ------------------------------------------------------------------------- */
.terminal {
  max-width: 76ch;
  margin: 0 auto;
  min-height: 100dvh;
  padding: var(--pad);
  padding-top: clamp(1.25rem, 6vh, 4rem);
  display: flex;
  flex-direction: column;
  gap: .7rem;
  transition: opacity .55s ease;
}

body.cold .terminal {
  opacity: 0;
  pointer-events: none;
}

/* main session hidden while the boot sequence plays (js only) */
.js #main { opacity: 0; }
.js #main.ready { opacity: 1; }

.js #main:not(.logo-ready) .motd,
.js #main:not(.logo-ready) #screen,
.js #main:not(.logo-ready) #prompt {
  opacity: 0;
  visibility: hidden;
}

/* -------------------------------------------------------------------------
   boot / POST sequence
   ------------------------------------------------------------------------- */
#boot {
  color: var(--dim);
  white-space: pre-wrap;
  line-height: 1.42;
}
#boot .ok  { color: var(--fg); }
#boot .hot { color: var(--accent); }
#boot:empty { display: none; }

/* -------------------------------------------------------------------------
   entry gate
   ------------------------------------------------------------------------- */
#gate {
  color: var(--fg);
  white-space: pre-wrap;
}
#gate[hidden] { display: none; }
.gate-line {
  display: flex;
  align-items: baseline;
  gap: 0;
}
.gate-hint {
  margin-top: .75rem;
  color: var(--dim);
  visibility: hidden;
}
#gate.waiting .gate-hint { visibility: visible; }
.gate-audio { color: var(--dimmer); }
/* touch devices have no ENTER key — show the tap verb instead */
.hint-touch { display: none; }
@media (pointer: coarse) {
  .hint-desktop { display: none; }
  .hint-touch { display: inline; }
}

/* -------------------------------------------------------------------------
   logo
   width ~53 cols. clamp keeps it fitting from phones to wide desktops.
   ------------------------------------------------------------------------- */
.logo {
  margin: 0 0 1.25rem;   /* breathing room between the logo and the tty text */
  font-family: var(--mono);
  /* same cell size as the body text on desktop; shrinks only when 53 columns
     can't fit the viewport (phones). no glow — it's terminal text, not a graphic. */
  font-size: clamp(7px, 2.05vw, 14px);
  line-height: 1.0;
  letter-spacing: 0;
  color: var(--fg);
  white-space: pre;
  overflow-x: auto;
}
.logo::-webkit-scrollbar { height: 0; }

/* -------------------------------------------------------------------------
   message of the day
   ------------------------------------------------------------------------- */
.motd {
  margin: 0;
  color: var(--dim);
}
.motd .kbd {
  color: var(--fg);
  border: 1px solid var(--accent-lo);
  padding: 0 .45em;
  border-radius: 2px;
}
.sig { color: var(--accent); }

/* -------------------------------------------------------------------------
   session transcript
   ------------------------------------------------------------------------- */
#screen { display: flex; flex-direction: column; gap: .75rem; }

.entry { display: flex; flex-direction: column; gap: .35rem; }

/* echoed command line */
.echo { color: var(--fg); white-space: pre-wrap; }
.echo .ps1 { margin-right: .6ch; }

/* command output */
.out { white-space: pre-wrap; }
.out.manifesto { color: var(--fg); }
.out .lede, .out .dim { color: var(--dim); }
.out.dim  { color: var(--dim); }
.out.err  { color: var(--accent); }        /* magenta = the alert/stray signal */
.out .hl  { color: var(--accent); }
.out ul   { list-style: none; margin: .2em 0 0; padding: 0; }
.out li   { display: flex; gap: 1.5ch; }
.out li .name { color: var(--accent); min-width: 12ch; }

/* whois member card — prebaked dithered headshot beside the info block */
.whois-card { display: flex; gap: 1.5ch; align-items: flex-start; }
.whois-face {
  flex: 0 0 auto;
  width: 150px; height: 150px;   /* 600px source at 1:4 keeps the dither crisp */
  image-rendering: pixelated;    /* no smoothing — preserve the dots */
  clip-path: inset(0 0 100% 0);  /* hidden until it's decoded, then it paints in */
}
/* fake in-universe decode: JS adds .is-decoding once the image is actually decoded,
   so the row-by-row paint always plays and is never skipped by a load race. */
.whois-face.is-decoding {
  animation: whois-decode 2.3s steps(20) forwards;
}
@keyframes whois-decode {
  from { clip-path: inset(0 0 100% 0); }   /* nothing drawn yet */
  to   { clip-path: inset(0 0 0 0); }      /* fully drawn */
}
.whois-info { display: flex; flex-direction: column; gap: .2rem; }
@media (max-width: 34rem) {
  .whois-card { flex-direction: column; }
}

/* -------------------------------------------------------------------------
   prompt
   ------------------------------------------------------------------------- */
.ps1 {
  color: var(--fg);
  white-space: nowrap;
  user-select: none;
}
.ps1 .user { color: var(--fg); }
.ps1 .path { color: var(--dim); }
.ps1 .sig  { color: var(--accent); }

.prompt-line {
  display: flex;
  align-items: baseline;
  gap: .6ch;
  cursor: text;
}
/* keep the live prompt on the same rhythm as the transcript entries
   (#screen uses gap:.75rem); margin-top:auto was a no-op in this block flow
   and left the prompt hugging the entry above it. */
#prompt { margin-top: .75rem; }

.cmd-input {
  flex: 0 0 auto;
  width: 0;                   /* JS resizes to fit the typed text exactly */
  min-width: 0;
  max-width: 100%;
  background: transparent;
  border: 0;
  outline: none;
  color: var(--fg);
  font: inherit;
  letter-spacing: inherit;
  caret-color: transparent;   /* we draw our own block cursor */
  padding: 0;
}

/* block cursor, 90s style */
.cursor {
  flex: 0 0 auto;
  display: inline-block;
  width: .9ch;
  height: 1.15em;
  transform: translateY(.18em);
  background: var(--accent);
  box-shadow: none;
  animation: blink 1.05s steps(1, end) infinite;
}
/* hide the trailing cursor when the input isn't focused or is mid-word */
.prompt-line:not(.focused) .cursor { opacity: .35; animation: none; }

@keyframes blink { 50% { opacity: 0; } }

/* Touch devices: use the native input + native caret. The custom block cursor
   relies on JS rewriting the input width per keystroke and on caret-color:transparent,
   both of which disrupt Android/GBoard's composing region and trapped the first
   character on backspace. On touch we hand editing back to the browser. */
@media (pointer: coarse) {
  .cmd-input {
    flex: 1 1 auto;
    width: auto !important;
    min-width: 4ch;
    caret-color: var(--accent);
  }
  .prompt-line .cursor { display: none; }
}

/* -------------------------------------------------------------------------
   CRT dressing — kept restrained per the "stark tty" direction
   ------------------------------------------------------------------------- */
/* analog grain — desaturated turbulence, blended so it textures the black
   without lifting it. tune intensity via opacity. */
.noise {
  content: "";
  position: fixed; inset: 0;
  pointer-events: none; z-index: 6;
  opacity: .035;
  transition: opacity 1.1s ease;
  mix-blend-mode: screen;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.8' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  background-size: 160px 160px;
}

body.cold .noise { opacity: 0; }

.scanlines::before {
  content: "";
  position: fixed; inset: 0;
  pointer-events: none; z-index: 8;
  background: repeating-linear-gradient(
    to bottom,
    transparent 0 2px,
    rgba(0, 0, 0, .16) 2px 3px
  );
  opacity: .32;
  transition: opacity 1.1s ease;
}
.scanlines::after {
  content: "";
  position: fixed; inset: 0;
  pointer-events: none; z-index: 7;
  background: radial-gradient(125% 125% at 50% 45%, transparent 70%, rgba(0,0,0,.45) 100%);
  transition: opacity 1.1s ease;
}

body.cold.scanlines::before,
body.cold.scanlines::after {
  opacity: 0;
}

/* periodic refresh sweep — a soft band that rolls down the screen every few
   seconds, like a CRT redraw. color-dodge + a z-index below the grain/scanline
   layers means it brightens only the text it passes over; the near-black
   background and the grain are left untouched (no glowing rectangle). Idle most
   of the cycle so it stays a passive touch. */
.crt-sweep {
  position: fixed;
  left: 0; right: 0; top: 0;
  height: 30vh;
  pointer-events: none;
  z-index: 5;
  mix-blend-mode: color-dodge;
  /* white + fairly strong: color-dodge lifts the text toward white as the band
     passes, but stays a no-op over the pure-black background, so intensity here
     only affects the text — not the empty screen. */
  background: linear-gradient(
    to bottom,
    transparent 0%,
    rgba(255, 255, 255, .16) 40%,
    rgba(255, 255, 255, .5) 50%,
    rgba(255, 255, 255, .16) 60%,
    transparent 100%
  );
  opacity: 0;
  will-change: transform, opacity;
  animation: crt-refresh 10s linear infinite;
}
@keyframes crt-refresh {
  0%   { transform: translateY(-30vh); opacity: 0; }
  3%   { opacity: 1; }
  22%  { opacity: 1; }
  27%  { transform: translateY(120vh); opacity: 0; }
  100% { transform: translateY(120vh); opacity: 0; }
}
body.cold .crt-sweep { opacity: 0; animation: none; }

/* -------------------------------------------------------------------------
   motion / small-screen
   ------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  .cursor { animation: none; }
  .js #main { opacity: 1; }   /* no fade; content is present immediately */
  .crt-sweep { animation: none; opacity: 0; }   /* no rolling glow */
  .whois-face { animation: none !important; clip-path: none !important; }   /* show the portrait at once */
}

@media (max-width: 480px) {
  .terminal {
    gap: 1.1rem;
    /* keep the prompt well off the bottom edge (and clear of the home indicator) */
    padding-bottom: calc(clamp(3rem, 14vh, 7rem) + env(safe-area-inset-bottom, 0px));
  }
  .out li { flex-direction: column; gap: .1rem; }
  .out li .name { min-width: 0; }
  /* when the transcript is long, stop auto-scroll short of the edge so the same
     gap shows below the prompt (scrollIntoView otherwise pins it to the bottom) */
  #prompt { scroll-margin-bottom: clamp(3rem, 14vh, 7rem); }
}
