/* ===========================================================================
   Terminal Velocity — newsletter landing page
   A faithful-ish recreation of a dialog(1) / ncurses configuration screen.

   Everything is laid out on a character grid:
     · line-height is exactly 1, so one text row == 1em
     · one column == 1ch == 0.6667em (BigBlueTerminal advance is 800/1200 upm)
     · dialog frames are real box-drawing characters on real rows; the
       connecting rules use the font's own stroke weights and centrelines
       (see --stroke-h / --stroke-v / --rule-y) so they fuse seamlessly.
   Keep vertical rhythm in whole `em` and horizontal in whole `ch`.
   ======================================================================== */

/* ── font ────────────────────────────────────────────────────────────── */
/* BigBlueTerminal by VileR (CC BY-SA 4.0), Nerd Fonts patch by Ryan McIntyre.
   Subset to latin + box drawing + blocks. See fonts/LICENSE-BigBlueTerminal.txt */
@font-face {
  font-family: "BigBlue Terminal";
  src: url("fonts/BigBlueTerminal.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: block; /* never flash a proportional fallback — it breaks the grid */
}

/* ── palette: the 16 IBM VGA text-mode colours ───────────────────────── */
:root {
  --black:    #000000;
  --blue:     #0000aa;
  --green:    #00aa00;
  --cyan:     #00aaaa;
  --red:      #aa0000;
  --magenta:  #aa00aa;
  --brown:    #aa5500;
  --lgray:    #aaaaaa;
  --dgray:    #555555;
  --lblue:    #5555ff;
  --lgreen:   #55ff55;
  --lcyan:    #55ffff;
  --lred:     #ff5555;
  --lmagenta: #ff55ff;
  --yellow:   #ffff55;
  --white:    #ffffff;

  /* grid */
  --row: 1em;
  --stroke: max(1px, 0.07em);

  /* Frame geometry, taken straight from the font's box-drawing glyphs so CSS
     rules and real ┌ ┐ └ ┘ characters fuse without a seam:
       ─ stroke 84/1200upm, centred 0.5025em down a row
       │ stroke 104/1200upm, centred 0.5ch across a cell                     */
  --stroke-h: max(1px, 0.07em);
  --stroke-v: max(1px, 0.0867em);
  --rule-y: 0.5025em;

  /* phosphor bloom for light-on-dark text */
  --glow: 0 0 0.42em rgba(190, 205, 255, 0.30);
}

/* ── reset ───────────────────────────────────────────────────────────── */
*, *::before, *::after { box-sizing: border-box; }

html, body {
  min-height: 100%;
  margin: 0;
}

body {
  background: var(--black);
  color: var(--white);
  font-family: "BigBlue Terminal", "BigBlueTerm437 Nerd Font Mono",
               ui-monospace, "Cascadia Mono", Menlo, Consolas, monospace;
  font-size: clamp(16px, 2.2vw, 19px);
  line-height: 1;
  font-variant-ligatures: none;
  font-kerning: none;
  text-rendering: optimizeSpeed;
  -webkit-text-size-adjust: 100%;
}

.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* ===========================================================================
   The screen
   ======================================================================== */
.tty {
  position: relative;
  height: 100dvh;
  overflow: hidden;
}

.screen {
  position: relative;
  height: 100%;
  display: flex;
  flex-direction: column;
  background-color: var(--blue);
  /* whisper of the dithered ▒ backdrop the real installer draws */
  background-image:
    repeating-conic-gradient(rgba(255, 255, 255, 0.05) 0 25%, transparent 0 50%);
  background-size: 2px 2px;
  animation: flicker 5.5s steps(60) infinite;
}

@keyframes flicker {
  0%, 100% { opacity: 1; }
  47%      { opacity: 0.985; }
  49%      { opacity: 1; }
  92%      { opacity: 0.99; }
}

/* ── top / bottom chrome ─────────────────────────────────────────────── */
.titlebar,
.statusbar {
  flex: none;
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 2ch;
  padding: var(--row) 2ch;
  color: var(--white);
  text-shadow: var(--glow);
}

.statusbar__left { color: var(--lgray); }
.statusbar__right { margin-left: auto; }

.statusbar { color: var(--lcyan); }

.link {
  color: var(--lcyan);
  text-decoration: none;
  text-underline-offset: 0.3em;
}
.link:hover { text-decoration: underline; }
.link:focus-visible {
  outline: none;
  background: var(--lcyan);
  color: var(--blue);
}

/* ── stage: centres the dialog ───────────────────────────────────────── */
.stage {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--row) 3ch;
  overflow-y: auto;
  overflow-x: hidden;
}

/* ===========================================================================
   Dialog box

   The frame is drawn the way curses draws it: as real characters in real
   cells. The top and bottom frame rows are text rows exactly one cell tall,
   so the title physically occupies the border row and cannot escape the box.
   Only the long horizontal runs and the two vertical sides are CSS rules
   instead of repeated ─ / │ characters — positioned on the font's own
   centrelines, they are indistinguishable from the real thing and don't need
   to know the box's size in cells.
   ======================================================================== */
.dialog {
  position: relative;
  width: min(76ch, 100%);
  background: var(--lgray);
  color: var(--black);
  /* the drop shadow dialog(1) paints: exactly one cell right, one row down */
  box-shadow: 1ch var(--row) 0 rgba(0, 0, 0, 0.45);
}

/* the two vertical sides, running corner centre to corner centre */
.dialog::before,
.dialog::after {
  content: "";
  position: absolute;
  top: var(--rule-y);
  bottom: calc(var(--row) - var(--rule-y));
  width: var(--stroke-v);
  background: var(--black);
}
.dialog::before { left:  calc(0.5ch - var(--stroke-v) / 2); }
.dialog::after  { right: calc(0.5ch - var(--stroke-v) / 2); }

/* ── frame rows ──────────────────────────────────────────────────────── */
.frame {
  position: relative;
  z-index: 1;                /* corner glyphs paint over the vertical rules */
  display: flex;
  align-items: center;
  height: var(--row);
  color: var(--black);
  white-space: nowrap;
}

.frame__corner { flex: none; }

.frame__rule {
  flex: 1 1 auto;
  align-self: stretch;
  position: relative;
  min-width: 1ch;
}
.frame__rule::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: var(--rule-y);
  height: var(--stroke-h);
  transform: translateY(-50%);
  background: var(--black);
}

/* the title lives in the border row: `┌───┤ Newsletter Subscription ├───┐` */
.frame__title {
  flex: 0 1 auto;
  min-width: 0;
  margin: 0;
  padding: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  font-size: 1em;
  font-weight: 400;
  line-height: 1;
  color: var(--red);
}
.frame__tick { color: var(--black); }

.dialog__body { padding: var(--row) 3ch calc(var(--row) * 1.5); }

/* ── copy ────────────────────────────────────────────────────────────── */
.prose,
.caption {
  margin: 0 0 var(--row);
  line-height: 1.5;      /* prose gets 1.5 rows so it stays readable */
  text-wrap: pretty;
}
.caption { margin-top: calc(var(--row) * 2); margin-bottom: var(--row); }

/* ── the blue selection list ─────────────────────────────────────────── */
.listbox {
  width: fit-content;
  max-width: 100%;
  margin: 0 0 calc(var(--row) * 2);
  padding: var(--row) 2ch;
  list-style: none;
  background: var(--blue);
  color: var(--white);
  text-shadow: var(--glow);
}

.listbox__item {
  display: flex;
  gap: 1ch;
  padding: calc(var(--row) * 0.25) 0;
  line-height: 1.35;
}
.listbox__mark { flex: none; }
.listbox__item.is-on  .listbox__mark { color: var(--lgreen); }
.listbox__item.is-off { color: var(--lgray); }

/* ===========================================================================
   Form
   ======================================================================== */
.form { margin: 0; }

.field {
  display: flex;
  align-items: center;
  gap: 2ch;
  margin-bottom: var(--row);
}

.field__label {
  flex: none;
  width: 12ch;
  cursor: pointer;
}

.field__slot {
  position: relative;
  flex: 1 1 auto;
  max-width: 44ch;
  height: calc(var(--row) * 2);   /* two rows: on-grid, but a real tap target */
  background: var(--blue);
  color: var(--white);
  text-shadow: var(--glow);
  outline: var(--stroke) solid transparent;
}
.field__slot:focus-within { outline-color: var(--white); }

.field__input {
  width: 100%;
  height: 100%;
  margin: 0;
  padding: 0 1ch;
  border: 0;
  outline: none;
  background: transparent;
  color: inherit;
  font: inherit;
  line-height: calc(var(--row) * 2);
  caret-color: transparent;         /* we draw our own block caret */
}
.field__input::placeholder { color: rgba(255, 255, 255, 0.45); }
.field__input:-webkit-autofill {
  -webkit-text-fill-color: var(--white);
  -webkit-box-shadow: 0 0 0 100px var(--blue) inset;
}

/* block caret, positioned from JS (--caret-x) */
.field__caret {
  position: absolute;
  top: 50%;
  left: var(--caret-x, 1ch);
  transform: translateY(-50%);
  color: var(--white);
  pointer-events: none;
  opacity: 0;
}
.field__slot:focus-within .field__caret {
  animation: blink 1.06s steps(1) infinite;
}
@keyframes blink { 0%, 50% { opacity: 1; } 50.01%, 100% { opacity: 0; } }

/* invalid state, shown only after a submit attempt */
.field.is-invalid .field__slot { outline-color: var(--lred); }
.field.is-invalid .field__label { color: var(--red); }

/* ── button: `<Subscribe>` ───────────────────────────────────────────── */
.actions {
  display: flex;
  justify-content: center;
  gap: 3ch;
  margin-top: calc(var(--row) * 2);
}

.btn {
  margin: 0;
  padding: 0 1ch;
  border: 0;
  background: transparent;
  color: var(--red);
  font: inherit;
  line-height: calc(var(--row) * 2);
  cursor: pointer;
}
.btn::before { content: "<"; }
.btn::after  { content: ">"; }
.btn:hover { text-decoration: underline; text-underline-offset: 0.3em; }
.btn[disabled] { color: var(--dgray); cursor: default; }
.btn[disabled]:hover { text-decoration: none; }
.btn:focus-visible,
.btn:active {
  outline: none;
  background: var(--red);
  color: var(--white);
  text-decoration: none;
}

.finequote {
  margin: var(--row) 0 0;
  text-align: center;
  color: var(--dgray);
  line-height: 1.5;
}

/* ===========================================================================
   Result modal
   ======================================================================== */
.modal {
  position: fixed;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--row) 3ch;
  z-index: 20;
}
.modal[hidden] { display: none; }

.modal__scrim {
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, 0.45);
}

.dialog--modal {
  position: relative;
  width: min(52ch, 100%);
  text-align: center;
}
.dialog--modal .prose { margin-bottom: 0; }
.modal.is-error .frame__title { color: var(--red); }

/* ===========================================================================
   CRT overlay
   ======================================================================== */
.crt {
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 30;
}
.crt::before,
.crt::after {
  content: "";
  position: absolute;
  inset: 0;
}
/* scanlines — in px, they're a property of the tube, not the text size */
.crt::before {
  background: repeating-linear-gradient(
    to bottom,
    rgba(0, 0, 0, 0.16) 0 1px,
    transparent 1px 3px
  );
}
/* vignette */
.crt::after {
  background: radial-gradient(
    ellipse at center,
    transparent 52%,
    rgba(0, 0, 0, 0.38) 100%
  );
}

/* ===========================================================================
   Thank-you page
   ======================================================================== */
/* no form to fill in, so the box doesn't need the full 76 columns */
.dialog--narrow { width: min(60ch, 100%); }

.prose--note { color: var(--dgray); }

/* the step the reader is actually on */
.listbox__item.is-now { color: var(--yellow); }
.listbox__item.is-now .listbox__mark { color: var(--yellow); }
.listbox__here { flex: none; margin-left: 1ch; }

/* 404: the path that missed */
.pathbox {
  margin: 0 0 calc(var(--row) * 2);
  padding: var(--row) 1ch;
  background: var(--blue);
  color: var(--white);
  text-shadow: var(--glow);
  white-space: nowrap;
  overflow-x: auto;
}

/* the call to action is a link here, not a submit button */
a.btn { display: inline-block; text-decoration: none; }
a.btn:hover { text-decoration: underline; text-underline-offset: 0.3em; }

/* ===========================================================================
   Responsive + preferences
   ======================================================================== */
@media (max-width: 42em) {
  /* 3.8vw keeps the type growing across 320 → 421px instead of sitting on a
     floor; the 16px cap is exactly where the desktop ramp's floor starts, so
     type size is continuous through the breakpoint. */
  body { font-size: clamp(13px, 3.8vw, 16px); }

  /* taller type means the dialog can exceed the screen — scroll the document
     rather than a nested box, which behaves badly on phones */
  .tty { height: auto; min-height: 100dvh; overflow: visible; }
  .screen { min-height: 100dvh; }

  .dialog__body { padding: var(--row) 2ch calc(var(--row) * 1.5); }
  .stage { padding: var(--row) 2ch; align-items: flex-start; overflow: visible; }

  .field { display: block; }
  .field__label { width: auto; display: block; margin-bottom: calc(var(--row) * 0.5); }
  .field__slot { max-width: none; }

  .titlebar, .statusbar { padding: var(--row) 1ch; font-size: 0.85em; }
  .statusbar__left { display: none; }
  .listbox { padding: var(--row) 1ch; }
}

@media (prefers-reduced-motion: reduce) {
  .screen { animation: none; }
  .field__slot:focus-within .field__caret { animation: none; opacity: 1; }
}
