/* Palette: FreeBSD red on near-black. No webfonts, no external requests --
   the origin only answers Cloudflare and this page must render standalone. */
:root {
  --bg:     #0a0a0b;
  --text:   #d8d4cc;
  --dim:    #6b6862;
  --accent: #ab2b28;
  --mono: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas,
          "DejaVu Sans Mono", monospace;
  /* Art size, stepped by the breakpoints below. One variable so the
     reflection is always drawn at exactly the size of the art. */
  --art: 18px;
}
* { box-sizing: border-box; }
body {
  margin: 0;
  /* svh, not vh: on iOS Safari 100vh is the height with the toolbars
     hidden, so a vh-sized centred layout sits partly under the URL bar
     until the user scrolls. */
  min-height: 100vh;
  min-height: 100svh;
  background: var(--bg);
  color: var(--text);
  font-family: var(--mono);
  /* iOS inflates text in some block layouts, which stretches the art
     independently of its container and shears the columns. */
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
  /* Column, not row: .stamp is a sibling of <main>, so once the mobile
     breakpoint takes it out of `position: fixed` it becomes a second flex
     item -- in a row that puts it beside the daemon and shoves the art off
     the left edge. */
  display: flex;
  flex-direction: column;
  align-items: center;
  /* Anchored to the top, not vertically centred: centring slides the whole
     composition up and down as the viewport height changes -- including
     every time a mobile browser shows or hides its toolbar. */
  justify-content: flex-start;
  padding: 3.5rem 1.25rem 3rem;
  overflow-x: hidden;
}

/* Phosphor bloom behind the art, and a vignette to pull the eye centre. */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  background:
    radial-gradient(38rem 30rem at 38% 50%,
                    rgba(171, 43, 40, .16), transparent 70%),
    radial-gradient(120% 90% at 50% 50%,
                    transparent 45%, rgba(0, 0, 0, .75) 100%);
  pointer-events: none;
}

/* Scanlines. 3px period is fine on 1x and does not moire badly on 2x. */
body::after {
  content: "";
  position: fixed;
  inset: 0;
  background: repeating-linear-gradient(
    to bottom,
    rgba(0, 0, 0, 0) 0px,
    rgba(0, 0, 0, 0) 2px,
    rgba(0, 0, 0, .28) 3px);
  pointer-events: none;
  z-index: 3;
}

/* Power-on. The bloom flares and settles and the picture tears sideways
   for half a second at the moment the boot hands over to the page -- a
   tube warming up. Both animate transform/opacity/filter only, so nothing
   is laid out again. */
.poweron::before { animation: warm .55s ease-out both; }
.poweron main { animation: tear .55s steps(1, end) both; }
@keyframes warm {
  0%   { opacity: 0; }
  18%  { opacity: 1; filter: brightness(2.6); }
  30%  { opacity: .3; filter: brightness(1); }
  46%  { opacity: 1; filter: brightness(1.7); }
  100% { opacity: 1; filter: brightness(1); }
}
@keyframes tear {
  0%   { transform: translateX(0); }
  14%  { transform: translateX(4px); }
  26%  { transform: translateX(-3px); }
  40%  { transform: translateX(1px); }
  58%  { transform: translateX(-1px); }
  100% { transform: translateX(0); }
}

main {
  position: relative;
  z-index: 1;
  max-width: 100%;
  display: flex;
  align-items: center;
  gap: 4rem;
  flex-wrap: wrap;
  justify-content: center;
}

/* The art is 36 columns by 19 rows and a monospace advance is ~0.6em, so at
   18px the block is ~390x397px. Fixed sizes in discrete steps rather than a
   viewport-derived clamp: a clamp rescales the daemon continuously as the
   window changes, and the steps below are chosen so 36 columns still fit the
   narrowest phone. */
pre.daemon {
  margin: 0;
  font-size: var(--art);
  line-height: 1.16;
  color: var(--accent);
  text-shadow: 0 0 12px rgba(171, 43, 40, .55);
  white-space: pre;
  /* Ligatures would fuse pairs like `--` into one glyph of a different
     advance width and pull the whole row out of column. */
  font-variant-ligatures: none;
  letter-spacing: 0;
  user-select: none;
  -webkit-user-select: none;
  cursor: crosshair;
  touch-action: manipulation;
  transition: color .25s, text-shadow .25s;
}
pre.daemon:hover { color: #c9342f; text-shadow: 0 0 20px rgba(171, 43, 40, .85); }
pre.daemon .ln { display: block; height: 1.16em; }
/* One element per character so effects can address a single cell. Each is
   pinned to exactly 1ch: as bare inline boxes their advance widths get
   rounded independently, and the error accumulates across the row until the
   columns visibly drift apart. */
pre.daemon .ln b {
  display: inline-block;
  width: 1ch;
  vertical-align: top;
  line-height: 1.16;
  font-weight: inherit;
  font-style: normal;
}

/* Reflection: the art's last rows, flipped and fading, as if it stood on
   black glass. Absolutely positioned below the art so it adds no height --
   the composition stays centred on the daemon, not on daemon plus mirror --
   and clipped to six rows so it never reaches the stamp. */
.figure { position: relative; }
.reflect {
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  height: calc(6 * 1.16 * var(--art));
  overflow: hidden;
  pointer-events: none;
  opacity: .3;
  -webkit-mask-image: linear-gradient(to bottom, #000, transparent 92%);
  mask-image: linear-gradient(to bottom, #000, transparent 92%);
}
.reflect pre.daemon {
  transform: scaleY(-1);
  filter: blur(.5px);
  text-shadow: none;
  cursor: default;
}
.reflect pre.daemon:hover { color: var(--accent); }
.reflect .ln { display: block; }

/* max-width so the fixed-width shell cannot push the column past a narrow
   viewport; the shell then shrinks to fit and its lines wrap. */
.copy { min-width: 0; max-width: 100%; }
.eyebrow {
  margin: 0 0 .9rem;
  font-size: .72rem;
  letter-spacing: .22em;
  text-transform: uppercase;
  color: var(--dim);
}
h1 {
  margin: 0;
  font-size: 3.4rem;
  font-weight: 500;
  letter-spacing: .04em;
  line-height: 1;
  transition: text-shadow .15s;
}
/* Misconverged colour on hover, the way a tired tube splits a bright line.
   Hover-capable devices only: on touch, :hover sticks after a tap. */
@media (hover: hover) {
  h1:hover {
    cursor: default;
    text-shadow: -2px 0 rgba(255, 46, 62, .8), 2px 0 rgba(48, 214, 255, .7);
    animation: aberr .32s steps(1, end) infinite;
  }
}
@keyframes aberr {
  0%   { text-shadow: -2px 0 rgba(255, 46, 62, .8), 2px 0 rgba(48, 214, 255, .7); }
  50%  { text-shadow: 1px 0 rgba(255, 46, 62, .8), -1px 0 rgba(48, 214, 255, .7); }
  100% { text-shadow: -2px 0 rgba(255, 46, 62, .8), 2px 0 rgba(48, 214, 255, .7); }
}

/* Screensaver: the daemon drifting across a dark screen after a while with
   nobody at the keyboard. An overlay, so the page underneath never moves. */
.saver {
  position: fixed;
  inset: 0;
  z-index: 2;
  background: var(--bg);
  cursor: none;
}
.saver pre {
  position: absolute;
  top: 0;
  left: 0;
  margin: 0;
  font-size: 11px;
  line-height: 1.16;
  color: var(--accent);
  white-space: pre;
  font-variant-ligatures: none;
  text-shadow: 0 0 8px rgba(171, 43, 40, .5);
  will-change: transform;
}
.signal {
  margin: .9rem 0 0;
  font-size: 1rem;
  color: var(--dim);
}
.prompt {
  margin: 2rem 0 0;
  font-size: 1rem;
  color: var(--accent);
}
/* ---- shell ------------------------------------------------------------ */
/* Output fills a fixed-height viewport from the bottom, like a terminal:
   the space is reserved from the start, so typing never moves the page. */
.shell {
  position: relative;
  /* A fixed width, not max-width: the copy column must be exactly as wide
     before the first command as after it, or the centred pair rebalances
     and the daemon slides sideways the moment output appears. 50ch fits
     the longest line the shell prints (ls -l); anything longer wraps. */
  width: 50ch;
  max-width: 100%;
  text-align: left;
}
.out {
  height: 8.4em;
  margin: 1.4rem 0 0;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  font-size: .95rem;
  line-height: 1.4;
}
.out p {
  margin: 0;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  /* Phosphor persistence: lines dim as they age up the screen. */
  transition: opacity .9s;
}
.out p:nth-last-child(n+3) { opacity: .72; }
.out p:nth-last-child(n+5) { opacity: .48; }
.out p:nth-last-child(n+7) { opacity: .28; }
.out .in { color: var(--accent); }
.out .in b { color: var(--text); font-weight: inherit; }
.out .txt { color: var(--dim); }
.shell .prompt {
  display: block;
  margin: .3rem 0 0;
  cursor: text;
  /* One line, always: the input's maxlength keeps commands short enough,
     and a wrapped prompt would grow the column and re-centre the art. */
  white-space: pre;
  overflow: hidden;
}
.prompt .typed { color: var(--text); }
/* Keystrokes land here; the prompt line mirrors them. Kept inside .shell
   (not offscreen) so a phone scrolls to the right place when focusing it. */
.cmd {
  position: absolute;
  bottom: 0;
  left: 0;
  width: 1px;
  height: 1px;
  padding: 0;
  border: 0;
  opacity: 0;
  pointer-events: none;
}

/* ---- boot ------------------------------------------------------------- */
.boot {
  position: fixed;
  top: 2.5rem;
  left: 2.75rem;
  right: 1.5rem;
  z-index: 1;
  margin: 0;
  font-size: .95rem;
  line-height: 1.5;
  color: var(--dim);
}
.boot p { margin: 0; white-space: pre-wrap; }
.boot .login { color: var(--text); }
.boot .cursor { vertical-align: -.2em; }
.booting main, .booting .stamp { visibility: hidden; }

/* The 404 page types a command at the prompt; it is the way back home. */
.prompt a {
  color: inherit;
  text-decoration: underline dotted;
  text-underline-offset: .2em;
}
.prompt a:hover,
.prompt a:focus-visible { color: var(--text); outline: none; }
.cursor {
  display: inline-block;
  width: .58em;
  height: 1.05em;
  margin-left: .1em;
  vertical-align: -.18em;
  background: var(--accent);
  box-shadow: 0 0 10px rgba(171, 43, 40, .8);
  animation: blink 1.06s steps(1) infinite;
}
@keyframes blink { 50% { opacity: 0; } }

/* Discoverability for the click interaction; nobody pokes an ASCII drawing
   unprompted. Fades in only once the reveal has finished. */
.hint {
  /* A real <button>, so keyboard users can trigger the poke too; the
     defaults are reset so it reads as the same quiet caption as before. */
  display: block;
  margin: .9rem 0 0;
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  text-align: inherit;
  font-size: .68rem;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--dim);
  cursor: pointer;
  opacity: 0;
  transition: opacity 1.2s;
}
.hint.on { opacity: .8; }
.hint:disabled { cursor: default; }
.hint:focus-visible {
  outline: 1px solid var(--accent);
  outline-offset: .3em;
}
.hint .t { display: none; }

.stamp {
  position: relative;
  z-index: 1;
  margin: 3rem 0 0;
  text-align: center;
  font-size: .68rem;
  letter-spacing: .2em;
  color: var(--dim);
  white-space: nowrap;
}

/* Typing-in effect. Hidden lines are laid out but invisible, so nothing
   reflows as they arrive. */
.typing pre.daemon .ln,
.typing .copy > * { visibility: hidden; }

/* Centre vertically on desktop only. A desktop window does not resize on
   its own, so centring costs none of the stability that top-anchoring buys
   on mobile -- where the browser toolbar changes the viewport height by
   itself and would slide the whole composition each time.
   `safe center` keeps the top of the content reachable when the window is
   shorter than the composition; plain `center` overflows both edges and
   clips it unreachably. The unprefixed value stays first as the fallback. */
@media (min-width: 821px) {
  body {
    justify-content: center;
    justify-content: safe center;
    padding: 2rem 1.25rem;
  }
  /* Twelve lines beside the art; a desktop has the room. daemon.js reads
     the height back, so the pager, top and vi fill whatever this is. */
  .out { height: 16.8em; }
}

/* Stack before the two columns get too narrow to read, rather than at a
   phone-shaped width -- flex-wrap alone would leave a cramped pair first. */
@media (max-width: 820px) {
  body { padding: 2.5rem 1.25rem; }
  main { flex-direction: column; text-align: center; gap: 2rem; }
  .reflect { display: none; }
  .shell { margin: 0 auto; }
  .out { height: 7em; }
  .boot { top: 1.5rem; left: 1.25rem; right: 1.25rem; }
  :root { --art: 16px; }
  h1 { font-size: 3rem; }
}
/* 36 columns at 0.6em: 13px keeps the art under 285px, which clears the
   narrowest phones still in use once padding is taken off. */
@media (max-width: 480px) {
  :root { --art: 13px; }
  h1 { font-size: 2.4rem; }
  .signal, .prompt { font-size: .95rem; }
}
@media (max-width: 359px) {
  :root { --art: 11px; }
  h1 { font-size: 2.1rem; }
}
/* Touch devices get no hover and no cursor to follow -- and no shell: no
   reserved output area, the prompt is decoration. The boot stays. */
@media (pointer: coarse) {
  pre.daemon { cursor: default; }
  .hint .m { display: none; }
  .hint .t { display: inline; }
  .out, .cmd { display: none; }
  .shell { width: auto; text-align: inherit; }
  .shell .prompt { margin: 2rem 0 0; cursor: default; }
}
@media (prefers-reduced-motion: reduce) {
  .cursor { animation: none; }
  .poweron::before, .poweron main, h1:hover { animation: none; }
  .out p { transition: none; }
}
