:root {
  color-scheme: dark;

  --black: #000;
  --line: #262420;
  --paper: #f2ede2;
  --body: #c9c2b4;
  --dim: #7f786c;
  --accent: #ff6b14;

  --display: "Roboto Serif", Georgia, "Times New Roman", serif;
  --sans: "Archivo", "Helvetica Neue", Arial, sans-serif;
  --mono: "IBM Plex Mono", ui-monospace, Menlo, monospace;
  --tracking: 0.04em;   /* letter-spacing for every line of mono interface type */

  --accent-tint: rgb(255 107 20 / 0.08);   /* hover fill behind pressed and listed items */
  --glow: 0 0 8px var(--accent);           /* lit rules, scan lines and status lights */
  --art-lit: brightness(1.35) drop-shadow(0 0 6px rgb(255 107 20 / 0.35));   /* targeted art */

  /* One type scale for the whole site; phones step each size down (see the 560px query). */
  --text-label: 12px;   /* uppercase terminal labels, counts, tags */
  --text-small: 13px;   /* secondary lines: hooks, bylines, details */
  --text-ui: 14px;      /* links, filters, list titles */
  --text-body: 15px;    /* descriptions */
  --text-lead: 17px;    /* the hook under a file title */
  --text-card: 20px;    /* project titles in lists */

  /* Motion curves: things glide to rest, windows sweep, targeting brackets overshoot as they lock. */
  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
  --ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);
  --ease-snap: cubic-bezier(0.34, 1.56, 0.64, 1);

  /* Durations: press feedback, hover color, small moves, things rising in, art scanning in,
     and a file's larger art decrypting on its own page. */
  --fast: 120ms;
  --quick: 200ms;
  --move: 300ms;
  --reveal: 700ms;
  --scan: 800ms;
  --scan-file: 1300ms;

  /* Buttons, fields and menus share one height; touch screens raise it to 44px. */
  --control: 32px;

  --gutter: clamp(16px, 2.4vw, 40px);
  --sidebar: clamp(280px, 24vw, 360px);
}

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

/* Elements styled with a display value still hide when marked hidden. */
[hidden] { display: none !important; }

html { -webkit-text-size-adjust: 100%; }

html, body { height: 100%; }

body {
  margin: 0;
  overflow: hidden;
  background: var(--black);
  color: var(--body);
  font: 400 var(--text-body)/1.5 var(--sans);
  -webkit-font-smoothing: antialiased;
}

/* Paragraphs avoid leaving one word alone on their last line. */
p { text-wrap: pretty; }

button, input, select { font: inherit; color: inherit; }
button { cursor: pointer; }

:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* Hidden until a keyboard user tabs to it. */
.skip-link {
  position: absolute;
  top: 8px;
  left: var(--gutter);
  z-index: 200;
  padding: 8px 12px;
  background: var(--accent);
  font: 500 var(--text-ui)/1 var(--sans);
  color: var(--black);
  text-decoration: none;
  transform: translateY(calc(-100% - 16px));
}
.skip-link:focus-visible { transform: none; outline-offset: 0; }

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

/* Small uppercase labels: counts and file numbers. */
.label {
  margin: 0;
  font: 500 var(--text-label)/1.4 var(--mono);
  letter-spacing: var(--tracking);
  text-transform: uppercase;
  color: var(--dim);
}

.label b { color: var(--paper); font-weight: 600; }

/* Text links and buttons: an accent underline draws across on hover, and the arrow leans forward. */
.link {
  padding: 0 0 1px;
  border: 0;
  background:
    linear-gradient(var(--accent), var(--accent)) left bottom / 0 1px no-repeat,
    linear-gradient(var(--dim), var(--dim)) left bottom / 100% 1px no-repeat;
  font: 500 var(--text-ui)/1.4 var(--sans);
  color: var(--paper);
  text-decoration: none;
  transition: color var(--quick), background-size var(--move) var(--ease-out), transform var(--fast);
}

.link:hover { color: var(--accent); background-size: 100% 1px, 100% 1px; }

.arrow { display: inline-block; transition: transform var(--move) var(--ease-out); }
:hover > .arrow { transform: translateX(3px); }
:hover > .arrow.is-back { transform: translateX(-3px); }

/* Everything pressable sinks a little under the pointer. */
.link:active, .ticker:active, .scan:active, .memo-link:active { transform: translateY(1px); }

/* ---------- Title card type ---------- */

.titlecard {
  margin: 0;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  font-family: var(--display);
  font-weight: 900;
  font-stretch: 62.5%;
  line-height: 0.84;
  text-transform: uppercase;
  color: var(--paper);
}

/* Each line is stretched to the full width by app.js, so lines land at different sizes. */
.fit { display: inline-block; white-space: nowrap; }
/* Sizes until app.js fits each line to its width. */
.masthead .fit { font-size: 72px; }
.masthead .fit[data-ratio] { font-size: 36px; }

/* ---------- One-screen layout ----------
   Nothing scrolls at page level: the sidebar holds the title and filters, and
   only the grid scrolls, loading more cards as it nears the end. */

.app {
  display: grid;
  grid-template-columns: var(--sidebar) minmax(0, 1fr);
  height: 100dvh;
}

.sidebar {
  position: relative;
  isolation: isolate;
  display: flex;
  flex-direction: column;
  gap: 16px;
  min-height: 0;
  padding: var(--gutter);
  border-right: 1px solid var(--line);
  overflow: hidden;
}

/* A faint hexagon field behind the title, like a command-center display. */
.sidebar::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='28' height='49' viewBox='0 0 28 49'%3E%3Cpath d='M13.99 9.25l13 7.5v15l-13 7.5L1 31.75v-15l12.99-7.5zM3 17.9v12.7l10.99 6.34 11-6.35V17.9l-11-6.34L3 17.9zM0 15l12.98-7.5V0h-2v6.35L0 12.69v2.3zm0 18.5L12.98 41v8h-2v-6.85L0 35.81v-2.3zM15 0v7.5L27.99 15H28v-2.31h-.01L17 6.35V0h-2zm0 49v-8l12.99-7.5H28v2.31h-.01L17 42.15V49h-2z' fill='%23ff6b14' fill-opacity='0.5'/%3E%3C/svg%3E");
  opacity: 0.07;
  -webkit-mask-image: linear-gradient(to bottom, #000, transparent 45%);
  mask-image: linear-gradient(to bottom, #000, transparent 45%);
}

.masthead { display: grid; grid-template-columns: minmax(0, 1fr); gap: 12px; justify-items: start; }
.masthead .titlecard { width: 100%; }

.lede { margin: 0; font-size: var(--text-small); line-height: 1.5; color: var(--paper); }

/* Readout that types out a different file every few seconds; click to open it. */
.ticker {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  min-height: var(--control);
  padding: 0 0 0 2px;
  border: 0;
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
  background: none;
  text-align: left;
  font: 500 var(--text-label)/1.3 var(--mono);
  letter-spacing: var(--tracking);
  text-transform: uppercase;
  color: var(--accent);
  transition: transform var(--fast);
}

.ticker:hover .ticker-text { color: var(--paper); }
.ticker:hover .ticker-light { box-shadow: 0 0 14px 2px var(--accent); }

.ticker-light {
  flex: none;
  width: 7px;
  height: 7px;
  background: var(--accent);
  box-shadow: var(--glow);
  transition: box-shadow var(--move);
}

.ticker-text { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; transition: color var(--quick); }

/* Filters: search, then the category menu, then the ticker's file and the status panel, and Sources at the bottom. */
.controls {
  flex: 1;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  grid-template-rows: auto auto minmax(0, 1fr) auto auto;
  gap: 8px 16px;
  min-height: 0;
}

/* The prompt's rule lights up from the left while typing. */
.search input {
  width: 100%;
  height: var(--control);
  padding: 0;
  border: 0;
  border-radius: 0;
  background:
    linear-gradient(var(--accent), var(--accent)) left bottom / 0 1px no-repeat,
    linear-gradient(var(--dim), var(--dim)) left bottom / 100% 1px no-repeat;
  font-size: var(--text-ui);
  color: var(--paper);
  transition: background-size var(--move) var(--ease-out);
}

.search input::placeholder { color: var(--dim); }
.search input:focus-visible { outline: none; background-size: 100% 1px, 100% 1px; }

/* Menus: the category picker, a cd path that opens a directory listing, and the sort order.
   Both open a listing below them and end in the same arrow, lit on hover. */
.menu { position: relative; min-width: 0; }

.menu-toggle {
  display: flex;
  align-items: center;
  gap: 8px;
  height: var(--control);
  border-bottom: 1px solid var(--dim);
  font: 500 var(--text-label)/1 var(--mono);
  letter-spacing: var(--tracking);
  color: var(--paper);
  white-space: nowrap;
  list-style: none;
  cursor: pointer;
  transition: border-color var(--quick);
}
.menu-toggle::-webkit-details-marker { display: none; }
.menu-toggle::after { content: "▾"; margin-left: auto; color: var(--paper); transition: transform var(--move) var(--ease-out), color var(--quick); }
.menu[open] .menu-toggle, .menu-toggle:hover, .menu-toggle:focus-visible { outline: none; border-color: var(--accent); }
.menu-toggle:hover::after { color: var(--accent); }
.menu[open] .menu-toggle::after { transform: rotate(180deg); color: var(--accent); }
#dir-current, #sort-current { color: var(--accent); }
/* They clip for the ellipsis, so they need room below the line to keep descenders like p and g whole;
   the padding covers fonts and browsers whose descenders reach past a 1.4 line. */
#dir-current, .dir-hint { min-width: 0; overflow: hidden; text-overflow: ellipsis; line-height: 1.4; padding-block: 0.2em; }
.dir-hint { color: var(--body); }
.dir-cmd { color: var(--dim); }

.menu-list {
  position: absolute;
  top: calc(100% + 4px);
  left: 0;
  z-index: 20;
  min-width: 100%;
  /* At most about seven rows, with the next one cut off to show there are more, and never past
     the room below the toggle (--menu-room, from app.js); past that the listing scrolls. */
  max-height: min(320px, var(--menu-room, 320px));
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-width: thin;
  scrollbar-color: var(--line) transparent;
  margin: 0;
  padding: 4px 0;
  list-style: none;
  border: 1px solid var(--line);
  background: var(--black);
  box-shadow: 0 8px 24px rgb(0 0 0 / 0.6);
}
.menu-sort .menu-list { left: auto; right: 0; }

.menu-item {
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
  padding: 8px 12px;
  border: 0;
  background: none;
  font: 500 var(--text-label)/1.4 var(--mono);
  letter-spacing: var(--tracking);
  color: var(--body);
  white-space: nowrap;
  text-align: left;
  transition: background-color var(--quick), color var(--quick);
}
.menu-item > span:first-child { transition: transform var(--move) var(--ease-out); }
.menu-item:hover, .menu-item:focus-visible { outline: none; background: var(--accent-tint); color: var(--paper); }
.menu-item:hover > span:first-child, .menu-item:focus-visible > span:first-child { transform: translateX(4px); }
.menu-item[aria-pressed="true"] { color: var(--accent); }
.menu-item[aria-pressed="true"] span:first-child::before { content: "> "; }
.menu-count { color: var(--dim); }

/* Desktop only: the ticker's file, drawn in whatever height the sidebar has left. */
.scan {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 8px;
  min-height: 0;
  padding: 0;
  border: 0;
  background: none;
  text-align: left;
  font-size: var(--text-small);
  line-height: 1.4;
  color: var(--body);
  transition: color var(--quick), transform var(--fast);
}
.scan:hover { color: var(--paper); }
.scan:hover .scan-art { filter: var(--art-lit); }
.scan-art-wrap { position: relative; display: block; flex: 0 1 auto; min-height: 48px; aspect-ratio: 4 / 3; }
.scan-art { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: contain; transition: filter var(--move); }
.scan-art:not([src]) { visibility: hidden; }

/* Desktop-only status panel (public/signals.js): link, carrier, clocks, files read. */
.signals {
  align-self: end;
  display: grid;
  gap: 4px;
  padding-top: 12px;
  border-top: 1px dashed var(--line);
  font: 500 var(--text-label)/1.6 var(--mono);
  letter-spacing: var(--tracking);
  text-transform: uppercase;
  color: var(--dim);
}
.signals div { display: grid; grid-template-columns: 9ch minmax(0, 1fr); gap: 8px; }
.signals b { display: flex; align-items: center; gap: 8px; min-width: 0; font-weight: 500; color: var(--paper); white-space: nowrap; overflow: hidden; }
.signal-bars { display: inline-flex; align-items: flex-end; gap: 2px; height: 12px; }
.signal-bars i { width: 3px; background: var(--line); }
.signal-bars i:nth-child(1) { height: 4px; }
.signal-bars i:nth-child(2) { height: 7px; }
.signal-bars i:nth-child(3) { height: 10px; }
.signal-bars i:nth-child(4) { height: 12px; }
.signal-bars i.on { background: var(--accent); box-shadow: 0 0 4px var(--accent); }
.signal-wave { display: block; width: 100%; height: 32px; margin: 4px 0; }

/* Desktop: Sources and the credit share the sidebar's last line. Below 960px the credit moves
   under the file list (.main-foot), and Sources sits beside the category menu. */
.sidebar-foot { display: flex; align-items: baseline; justify-content: space-between; gap: 16px; }
.credit { margin: 0; font-size: var(--text-small); color: var(--dim); white-space: nowrap; }
.credit .link { font-size: inherit; }
.main-foot { display: none; }

@media (max-height: 700px) {
  .signals { display: none; }
}
@media (max-height: 560px) {
  .scan { display: none; }
}

.main {
  display: flex;
  flex-direction: column;
  min-width: 0;
  min-height: 0;
  padding: var(--gutter) var(--gutter) 12px;
}

/* ---------- Card grid ---------- */

/* The path and count, with the sort menu at the right. It sits above the grid so the menu's
   listing drops over the cards (its entrance animation gives it a stacking context of its own). */
.results-head {
  position: relative;
  z-index: 3;
  flex: none;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  margin-bottom: 8px;
}
.menu-sort { flex: none; }

.results-head > .label { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.pager-btn {
  min-width: var(--control);
  height: var(--control);
  border: 1px solid var(--line);
  background: none;
  color: var(--paper);
  transition: color var(--quick), border-color var(--quick), background-color var(--quick), transform var(--fast);
}

.pager-btn:hover:not(:disabled) { border-color: var(--accent); color: var(--accent); background-color: var(--accent-tint); }
.pager-btn:active:not(:disabled) { transform: scale(0.94); }
.pager-btn:disabled { color: var(--line); cursor: default; }

.grid {
  flex: 1;
  min-height: 0;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 220px), 1fr));
  /* Rows take their full content height. With plain auto rows, an item that clips its own overflow
     (the status line, for its ellipsis) may shrink to nothing once the list scrolls, cutting its text. */
  grid-auto-rows: max-content;
  align-content: start;
  gap: 24px clamp(16px, 2vw, 32px);
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-width: thin;
  scrollbar-color: var(--line) transparent;
}

.card {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-width: 0;
  padding: 12px 0 0;
  border: 0;
  border-top: 1px solid var(--line);
  background: none;
  color: inherit;
  text-decoration: none;
  text-align: left;
}

/* The card's rule draws across in the accent as it is targeted. */
.card::before {
  content: "";
  position: absolute;
  top: -1px;
  left: 0;
  right: 0;
  height: 1px;
  background: var(--accent);
  box-shadow: var(--glow);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform var(--move) var(--ease-out);
}
.card:hover::before, .card.is-source::before { transform: scaleX(1); }

.card-title, .card-meta, .card-blurb { display: block; }

.card-art-wrap { position: relative; display: block; flex: none; aspect-ratio: 3 / 2; overflow: hidden; }

/* The renders have generous empty black space, so they are cropped to fill the cell. */
.card-art {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  background: var(--black);
  transition: filter var(--move), transform var(--reveal) var(--ease-out);
}

.card:hover .card-art, .card.is-source .card-art { filter: var(--art-lit); transform: scale(1.04); }
.card:active .card-art { transform: scale(1.01); transition-duration: var(--move), var(--quick); }

/* Targeting brackets lock onto the art on hover. */
.card-art-wrap::before, .scan-art-wrap::before {
  --b: linear-gradient(var(--accent), var(--accent));
  content: "";
  position: absolute;
  inset: 4px;
  z-index: 1;
  opacity: 0;
  transform: scale(1.06);
  pointer-events: none;
  background:
    var(--b) top left / 14px 1px no-repeat, var(--b) top left / 1px 14px no-repeat,
    var(--b) top right / 14px 1px no-repeat, var(--b) top right / 1px 14px no-repeat,
    var(--b) bottom left / 14px 1px no-repeat, var(--b) bottom left / 1px 14px no-repeat,
    var(--b) bottom right / 14px 1px no-repeat, var(--b) bottom right / 1px 14px no-repeat;
  transition: transform var(--move) var(--ease-snap), opacity var(--quick);
}

.card:hover .card-art-wrap::before, .card.is-source .card-art-wrap::before, .scan-art-wrap::before { transform: none; opacity: 1; }

.card-meta { font: var(--text-label)/1.4 var(--mono); letter-spacing: var(--tracking); color: var(--dim); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* Project titles: the display serif at normal width in title case, for easy reading.
   The condensed capitals are kept for the wordmark and masthead. */
.card-title, .file-title {
  font-family: var(--display);
  font-weight: 700;
  font-stretch: 100%;
  letter-spacing: -0.01em;
  color: var(--paper);
  text-wrap: balance;
}

.card-title {
  font-size: var(--text-card);
  line-height: 1.12;
  color: var(--paper);
  text-wrap: balance;
  overflow-wrap: break-word;
}

.card-title { transition: color var(--quick); }
.card:hover .card-title, .card.is-source .card-title { color: var(--accent); }

.card-blurb {
  font-size: var(--text-small);
  line-height: 1.4;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  overflow: hidden;
}

.empty { grid-column: 1 / -1; margin: 0; padding: 40px 0; color: var(--dim); }

/* The last line of the grid: how far through the list it is, then the batch decrypting as it loads. */
.grid-status {
  grid-column: 1 / -1;
  margin: 0;
  padding: 4px 0 8px;
  border-top: 1px solid var(--line);
  font: var(--text-label)/1.4 var(--mono);
  letter-spacing: var(--tracking);
  text-transform: uppercase;
  color: var(--dim);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.grid-status.is-loading { color: var(--accent); }

/* ---------- File page: one screen, only a long description scrolls ---------- */

.file {
  width: 100%;
  height: 100dvh;
  max-width: none;
  max-height: none;
  margin: 0;
  padding: 0;
  border: 0;
  background: var(--black);
  color: var(--body);
  overflow: hidden;
}

.file[open] { display: flex; flex-direction: column; }
/* A pre-rendered file page shows its file before the app takes over (and without JavaScript). */
.file[open]:not(:modal) { position: fixed; inset: 0; z-index: 10; }
/* The index stays in view, dimmed, around the file as its window opens and closes (app.js). */
.file::backdrop { background: none; }
.app { transition: opacity var(--reveal) var(--ease-in-out); }
.is-reading .app { opacity: 0.35; }

/* The glowing edge of the window as it opens and closes; app.js moves it. */
.file-frame {
  position: fixed;
  inset: 0;
  z-index: 3;
  border: 1px solid var(--accent);
  box-shadow: 0 0 12px rgb(255 107 20 / 0.6), inset 0 0 24px rgb(255 107 20 / 0.25);
  opacity: 0;
  pointer-events: none;
}
.file:focus-visible { outline: none; }

/* The top bar on file pages and the sources page: wordmark, terminal path, keys, and a
   progress line showing how far through the archive the open file is. */
.bar {
  position: relative;
  flex: none;
  display: flex;
  align-items: center;
  gap: 16px;
  min-width: 0;
  padding: 12px var(--gutter);
  border-bottom: 1px solid var(--line);
}
.bar::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: -1px;
  width: 100%;
  height: 1px;
  background: var(--accent);
  box-shadow: var(--glow);
  transform: scaleX(var(--progress, 0));
  transform-origin: left;
  transition: transform var(--reveal) var(--ease-out);
}

.bar-home {
  flex: none;
  font: 900 18px/1 var(--display);
  font-stretch: 62.5%;
  text-transform: uppercase;
  text-decoration: none;
  color: var(--paper);
}
.bar-home { transition: color var(--quick); }
.bar-home:hover { color: var(--accent); }

.bar-path { flex: 1 1 auto; min-width: 0; padding-left: 16px; border-left: 1px solid var(--line); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.bar-keys { flex: none; display: flex; align-items: center; gap: 8px; }
.bar-count { min-width: 9ch; text-align: center; }

.bar .pager-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 0 12px;
  font: 500 var(--text-label)/1 var(--mono);
  letter-spacing: var(--tracking);
  text-transform: uppercase;
  text-decoration: none;
}
.bar kbd { padding: 2px 4px; border: 1px solid var(--line); font: inherit; font-size: 0.85em; color: var(--dim); transition: border-color var(--quick), color var(--quick); }
.bar .pager-btn:hover kbd { border-color: var(--accent); color: var(--accent); }

.file-page {
  flex: 1;
  min-height: 0;
  display: grid;
  grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr);
  gap: clamp(24px, 4vw, 64px);
  padding: clamp(16px, 4vh, 48px) var(--gutter);
}

/* The whole text column scrolls, so the title moves out of the way of a long report. */
.file-text { display: flex; flex-direction: column; gap: 12px; min-height: 0; overflow-y: auto; scrollbar-gutter: stable; scrollbar-width: thin; scrollbar-color: var(--line) transparent; }
/* Whichever box scrolls the report (this column, or the whole page on phones) fades at its foot
   while more text waits below; app.js sets .has-more. */
.has-more { -webkit-mask-image: linear-gradient(to bottom, #000 calc(100% - 48px), transparent); mask-image: linear-gradient(to bottom, #000 calc(100% - 48px), transparent); }

.file-title { width: 100%; flex: none; margin: 0; font-size: clamp(28px, 3.6vw + 12px, 56px); line-height: 1.05; }

.file-hook { margin: 0; flex: none; font-size: var(--text-lead); line-height: 1.4; color: var(--paper); }

.file-about p { margin: 0 0 0.8em; font-size: var(--text-body); line-height: 1.55; }
.file-about p:last-child { margin-bottom: 0; }

/* Descriptions read as declassified reports: a typed header block and numbered sections. */
.memo-head {
  margin: 0 0 16px;
  padding: 8px 0 12px;
  border-top: 1px dashed var(--line);
  border-bottom: 1px dashed var(--line);
  font: 400 var(--text-label)/1.6 var(--mono);
  letter-spacing: var(--tracking);
  text-transform: uppercase;
  color: var(--dim);
}
.memo-head dl { margin: 0; display: grid; gap: 0; }
.memo-head dl div { display: grid; grid-template-columns: 9ch minmax(0, 1fr); gap: 8px; }
.memo-head dt::after { content: ":"; }
.memo-head dd { margin: 0; color: var(--body); overflow-wrap: anywhere; }
/* The page's main action: the archive holding the original, underlined in the accent; the arrow
   stays inline with the last word and leans out on hover. */
.memo-link { display: inline-block; color: var(--accent); text-decoration: underline 1px; text-underline-offset: 3px; transition: color var(--quick), transform var(--fast); }
.memo-link::after { content: "\00a0↗"; position: relative; left: 0; top: 0; transition: left var(--move) var(--ease-out), top var(--move) var(--ease-out); }
.memo-link:hover { color: var(--paper); }
.memo-link:hover::after { left: 2px; top: -2px; }

/* Each section opens on a numbered heading, in the accent, whose dashed rule runs to the edge,
   as on a typed form. Headings and report text keep one size at every width. */
.memo-sec { margin-bottom: 24px; }
.memo-label {
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 0 0 8px;
  font: 500 12px/1.4 var(--mono);
  letter-spacing: var(--tracking);
  text-transform: uppercase;
  color: var(--accent);
}
.memo-label::after { content: ""; flex: 1; border-top: 1px dashed var(--line); }
.memo-num { color: var(--dim); }
.file-about .memo-para { margin: 0; font-family: var(--mono); font-size: 13px; line-height: 1.7; }
.memo-end { margin: 0 0 16px; border: 0; border-top: 1px dashed var(--line); }

/* A list of outside links: learn-more on file pages. The sources page reuses its parts. */
.link-list { margin: 0; padding: 0; list-style: none; }
/* One line per link, with no rules between them: the kind, then the title and its source side by
   side (wrapping only when narrow). */
.link-list a {
  display: grid;
  grid-template-columns: 64px minmax(0, 1fr);
  align-items: baseline;
  column-gap: 12px;
  padding: 4px 0;
  color: var(--body);
  text-decoration: none;
}
.link-list a > * { transition: transform var(--move) var(--ease-out), color var(--quick); }
.link-list a:hover > * { transform: translateX(8px); }
.link-list .link-kind { grid-row: auto; line-height: 1.4; }
.link-text { display: flex; flex-wrap: wrap; align-items: baseline; column-gap: 12px; min-width: 0; }
.link-kind { grid-row: 1 / 3; font: 500 var(--text-label)/1.75 var(--mono); letter-spacing: var(--tracking); text-transform: uppercase; color: var(--dim); }
.link-title { font-size: var(--text-ui); line-height: 1.4; color: var(--paper); transition: color var(--quick); }
/* The arrow stays inline, held to the last word by the no-break space, so it never wraps onto a line
   of its own; it nudges by relative offsets because transforms need a box that could break apart. */
.link-title::after { content: "\00a0↗"; position: relative; left: 0; top: 0; color: var(--dim); transition: color var(--quick), left var(--move) var(--ease-out), top var(--move) var(--ease-out); }
.link-list a:hover .link-title::after { left: 2px; top: -2px; }
.link-by { font-size: var(--text-small); line-height: 1.4; color: var(--dim); }
.link-list a:hover .link-title, .link-list a:hover .link-title::after { color: var(--accent); }

.file-side { display: flex; flex-direction: column; justify-content: center; min-height: 0; }

.file-art-wrap { position: relative; display: block; flex: 1; min-height: 0; }
.file-art { display: block; width: 100%; height: 100%; object-fit: contain; background: var(--black); }


/* ---------- Sources page: the only page that scrolls ---------- */

.sources-page { display: flex; flex-direction: column; height: 100dvh; }

.sources { flex: 1; min-height: 0; overflow-y: auto; padding: clamp(16px, 4vh, 48px) var(--gutter) 64px; scrollbar-width: thin; scrollbar-color: var(--line) transparent; }

.sources-head { display: grid; gap: 12px; justify-items: start; margin-bottom: clamp(24px, 5vh, 48px); }
.sources-title { font-size: clamp(56px, 9vw, 112px); }
.sources-lede { margin: 0; font-size: var(--text-body); color: var(--paper); }

.sources-archive { margin-bottom: 32px; }
.sources-archive > .label { padding-bottom: 8px; color: var(--accent); }

.sources-docs { margin: 0; padding: 0; list-style: none; }
.sources-docs li {
  display: grid;
  grid-template-columns: 48px minmax(0, 1fr);
  column-gap: 12px;
  padding: 8px 0;
  border-top: 1px solid var(--line);
}
.sources-docs a { text-decoration: none; }
.sources-docs .link-by a { color: var(--body); }
.sources-docs a:hover, .sources-docs a.link-title:hover::after { color: var(--accent); }

/* ---------- Tablet and phone: everything stacks in one column ---------- */

@media (max-width: 960px) {
  .app { display: flex; flex-direction: column; }

  .sidebar {
    flex: none;
    gap: 12px;
    padding: 16px var(--gutter) 12px;
    border-right: 0;
    border-bottom: 1px solid var(--line);
  }

  .masthead { grid-template-columns: minmax(0, 0.9fr) minmax(0, 1fr); align-items: end; column-gap: 24px; }
  .masthead .titlecard { grid-row: 1 / 3; }

  /* The category listing drops over the grid, so the sidebar must not clip it. */
  .sidebar { z-index: 4; overflow: visible; }
  .controls { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 16px; }
  .search { flex: 1 1 100%; }
  .menu-dir { flex: 1 1 0; }

  .signals, .scan { display: none; }
  .sidebar-foot { order: 4; flex: none; margin: 0; padding-left: 12px; border-left: 1px solid var(--line); }
  .sidebar-foot .credit { display: none; }
  .main-foot { display: block; flex: none; padding-top: 8px; border-top: 1px solid var(--line); }

  .main { flex: 1; padding: 12px var(--gutter) 8px; }

  .file-page { grid-template-columns: 1fr; grid-template-rows: auto minmax(0, 1fr); gap: 16px; }
  .file-side { flex-direction: row; }
  .file-art-wrap { flex: 1 1 50%; }
}

@media (max-width: 560px) {
  :root { --text-label: 11px; --text-small: 12px; --text-ui: 13px; --text-body: 14px; --text-card: 18px; }

  .sidebar { gap: 8px; padding-top: 12px; }
  .masthead { grid-template-columns: minmax(0, 1fr) auto; column-gap: 16px; }
  .masthead .titlecard { grid-row: auto; width: 100%; }
  .lede { display: none; }

  /* Phones: the credit sits faded in the top-right corner, level with the title. */
  .main-foot { position: absolute; top: 12px; right: var(--gutter); z-index: 5; padding: 0; border: 0; }
  .main-foot .credit .link { color: var(--body); }
  .controls { gap: 4px 16px; }

  .main { padding-top: 8px; }
  .results-head { gap: 12px; margin-bottom: 4px; }

  .bar { gap: 8px; }
  .bar-path, .bar kbd { display: none; }
  .bar-keys { margin-left: auto; gap: 4px; }
  /* The counter would push Close off the edge of most phones; the progress line under the bar shows the place. */
  .bar-count { display: none; }
  .bar .pager-btn { padding: 0 8px; }

  /* Phone file page: tighter rows. */
  .file-about p { line-height: 1.5; }
  .link-list a { grid-template-columns: 56px minmax(0, 1fr); }

}

/* Phones and short landscape screens get compact rows: square thumbnail beside the date and title. */
@media (max-width: 560px), (max-width: 960px) and (max-height: 520px) and (orientation: landscape) {
  .grid { grid-template-columns: 1fr; gap: 0; }
  .card {
    display: grid;
    grid-template-columns: 64px minmax(0, 1fr);
    grid-template-rows: 1fr auto auto auto 1fr;
    gap: 2px 12px;
    padding: 8px 0;
  }
  .card-art-wrap { grid-column: 1; grid-row: 1 / 6; align-self: center; height: 64px; aspect-ratio: auto; }
  .card-meta { grid-column: 2; grid-row: 2; min-height: 1.4em; }
  .card-title { grid-column: 2; grid-row: 3; font-size: var(--text-lead); }
  .card-blurb { grid-column: 2; grid-row: 4; -webkit-line-clamp: 1; }
  .grid-status { padding-top: 8px; }
}

/* Phones and portrait tablets: art first, then title, description, details;
   only the description scrolls. */
@media (max-width: 560px), (max-width: 960px) and (orientation: portrait) {
  /* Here the whole page scrolls instead of a box inside it. */
  .file-page { display: flex; flex-direction: column; gap: 8px; padding: 12px var(--gutter) 24px; overflow-y: auto; }
  .file-text, .file-side { display: contents; }
  .file-art-wrap { order: 0; flex: none; height: clamp(100px, 40dvh - 120px, 320px); margin-bottom: 4px; }
  .file-decrypt { order: 1; }
  .file-title { order: 2; margin-bottom: 4px; }
  .file-hook { order: 3; }
  .file-about { order: 5; }
}

/* Narrowest phones: the wordmark shrinks so Close keeps its margin. */
@media (max-width: 380px) {
  .bar-home { font-size: 15px; }
}

/* Phones on their side: sidebar beside the grid again, file text beside its art. */
@media (max-width: 960px) and (max-height: 520px) and (orientation: landscape) {
  .app { display: grid; grid-template-columns: 250px minmax(0, 1fr); }
  .sidebar { gap: 8px; padding: 12px var(--gutter); border-right: 1px solid var(--line); border-bottom: 0; overflow-y: auto; scrollbar-width: none; }
  .masthead { grid-template-columns: minmax(0, 1fr); gap: 8px; }
  .masthead .titlecard { grid-row: auto; width: 75%; }
  .lede, .ticker { display: none; }
  .controls { display: grid; gap: 8px 12px; }
  .sidebar-foot { padding-left: 0; border-left: 0; }
  .sidebar-foot .credit { display: block; }
  .main-foot { display: none; }
  .main { padding-top: 8px; }
  .grid { grid-template-columns: 1fr 1fr; column-gap: 24px; }

  .file-page { grid-template-columns: minmax(0, 1.3fr) minmax(0, 1fr); grid-template-rows: none; gap: 24px; padding: 12px var(--gutter); }
  .file-text { gap: 8px; }
  .file-side { flex-direction: column; gap: 8px; }
}

/* ---------- Motion ----------
   Terminal motion: wireframe art scans in behind a scan line, readouts type out,
   and everything else glides to rest on --ease-out. index.html adds .motion to <html>
   only when the viewer allows motion, so without it everything is simply visible. */

/* Keyframes give only their starting point, so hover styles still apply once they finish. */
@keyframes scan-reveal { from { clip-path: inset(0 0 100% 0); filter: brightness(2); } }
@keyframes scan-down { 0% { transform: none; opacity: 1; } 99% { transform: translateY(100%); opacity: 1; } 100% { transform: translateY(100%); opacity: 0; } }
@keyframes type-in { from { clip-path: inset(0 100% 0 0); } to { clip-path: inset(0); } }
@keyframes rise { from { opacity: 0; transform: translateY(10px); } }
@keyframes focus-in { from { opacity: 0; transform: translateY(0.3em); filter: blur(8px); } }
@keyframes step-in { from { opacity: 0; transform: translateX(calc(var(--step, 0) * 28px)); } }
@keyframes blink { 50% { opacity: 0.2; } }
@keyframes hex-pulse { 50% { opacity: 0.12; } }
@keyframes flick { 0% { opacity: 1; } 30% { opacity: 0.2; } 60%, 100% { opacity: 1; } }
@keyframes flick-on { 0% { opacity: 0; } 34% { opacity: 1; } 67% { opacity: 0.2; } 100% { opacity: 1; } }
@keyframes drop-in { from { opacity: 0; transform: translateY(-6px); clip-path: inset(0 0 100% 0); } }

/* Touch screens: every control is at least 44px tall to the finger. Rows grow where they
   stand alone; inline links and the ticker get an invisible margin instead, so the layout holds. */
@media (pointer: coarse) {
  :root { --control: 44px; }
  .menu-item { min-height: 44px; }
  .link-list a { min-height: 44px; }
  .link, .bar-home, .memo-link { position: relative; }
  .link::before, .bar-home::before, .memo-link::before { content: ""; position: absolute; inset: min(0px, calc(50% - 22px)) -6px; }
}

/* Hover and press transitions settle at once for viewers who prefer less motion. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { transition-duration: 0.01ms !important; }
}

/* A scan line: a glowing 1px line at the top of a layer as tall as its box, which scan-down slides down. */
.scanline {
  position: absolute;
  top: -8px;
  left: 0;
  right: 0;
  height: 100%;
  background: linear-gradient(to bottom, transparent, rgb(255 107 20 / 0.35) 7px, var(--accent) 7px 8px, rgb(255 107 20 / 0.35) 8px, transparent 16px) top / 100% 16px no-repeat;
  opacity: 0;
  pointer-events: none;
}

/* Ambient: the status light blinks and the hexagon field breathes. */
.motion .ticker-light { animation: blink 1.4s steps(2, jump-none) infinite; }
.motion .ticker-text.is-typing { animation: type-in 900ms steps(36) both; }
.motion .sidebar::before { animation: hex-pulse 6s var(--ease-in-out) infinite; }

/* Opening sequence after the boot: the title lines come into focus, then the rest rises in. */
.motion .masthead .fit { animation: focus-in var(--reveal) var(--ease-out) both; }
.motion .masthead .fit:nth-child(1) { animation-delay: 200ms; }
.motion .masthead .fit:nth-child(2) { animation-delay: 340ms; }
.motion .masthead > :not(.titlecard) { animation: rise var(--reveal) var(--ease-out) 560ms both; }
.motion .ticker { animation: rise var(--reveal) var(--ease-out) 640ms both; }
.motion .controls { animation: rise var(--reveal) var(--ease-out) 720ms both; }
.motion .results-head { animation: rise var(--reveal) var(--ease-out) 600ms both; }
.motion .main-foot { animation: rise var(--reveal) var(--ease-out) 560ms both; }

/* Menu listings drop open. */
.motion .menu[open] .menu-list { animation: drop-in var(--move) var(--ease-out) both; }

/* Cards come in as they appear, a few at a time: the art scans down, then the words rise in. */
.motion .card:not(.is-in) { visibility: hidden; }
.motion .card.is-in .card-art { animation: scan-reveal var(--scan) var(--ease-out) var(--d, 0ms) both; }
.motion .card.is-in .scanline { animation: scan-down var(--scan) var(--ease-out) var(--d, 0ms) both; }
.motion .card.is-in .card-meta { animation: rise var(--reveal) var(--ease-out) calc(var(--d, 0ms) + 180ms) both; }
.motion .card.is-in .card-title { animation: rise var(--reveal) var(--ease-out) calc(var(--d, 0ms) + 240ms) both; }
.motion .card.is-in .card-blurb { animation: rise var(--reveal) var(--ease-out) calc(var(--d, 0ms) + 300ms) both; }
.motion .scan.is-scanning .scan-art { animation: scan-reveal var(--scan) var(--ease-out) both; }
.motion .scan.is-scanning .scanline { animation: scan-down var(--scan) var(--ease-out) both; }
.motion .grid-status.is-loading { animation: type-in 240ms steps(20) both; }

.motion .flick { animation: flick 180ms steps(3) both; }

/* Every time a file opens, by any route, it decrypts: the file name types out, the dots fill
   while the art scans down, and ok flickers on as the scan lands. Art that flew in from its card
   is already in place, so only the line plays. */
.file-decrypt > span { display: inline-block; }
.motion .file.is-decrypting .file-decrypt > :first-child { animation: type-in 400ms steps(24) both; }
.motion .file.is-decrypting .decrypt-dots { animation: type-in calc(var(--scan-file) - 100ms) steps(8) 300ms both; }
.motion .file.is-decrypting .decrypt-ok { animation: flick-on 240ms steps(1, end) calc(var(--scan-file) + 200ms) both; }
.motion .file.is-decrypting:not(.is-flying) .file-art { animation: scan-reveal var(--scan-file) var(--ease-out) 200ms both; }
.motion .file.is-decrypting:not(.is-flying) .file-art-wrap .scanline { animation: scan-down var(--scan-file) var(--ease-out) 200ms both; }

/* Opened from the index, the window grows out of whatever was clicked (app.js) with the page
   already inside it: the title comes into focus and the report rises in a paragraph at a time
   as the window reveals them, while the card's art flies into place. */
.motion .file.is-cutting .bar { animation: rise var(--reveal) var(--ease-out) 120ms both; }
.motion .file.is-cutting .file-title { animation: focus-in var(--reveal) var(--ease-out) 100ms both; }
.motion .file.is-cutting .file-hook { animation: rise var(--reveal) var(--ease-out) 180ms both; }
.motion .file.is-cutting .file-about > * { animation: rise var(--reveal) var(--ease-out) calc(240ms + var(--i, 0) * 50ms) both; }

/* The card's own thumbnail hides while its art is open on the file page, and shows again
   under the art as it flies back. */
.motion .is-reading .card.is-source .card-art { visibility: hidden; }

/* Stepping to the next or previous file slides the new one in from that side. */
.motion .file.is-stepping :is(.file-decrypt, .file-title, .file-hook, .file-about) { animation: step-in calc(var(--reveal) * 0.75) var(--ease-out) both; }
.motion .file.is-stepping .file-title { animation-delay: 30ms; }
.motion .file.is-stepping .file-hook { animation-delay: 60ms; }
.motion .file.is-stepping .file-about { animation-delay: 90ms; }
