/* The explorer's root-owned visual system. Generated pages supply markup and behaviour only;
 * tokens.css supplies the shared AI 2040 palette and type families. */

:root {
  /* Fluid explorer scale: compact enough for records, open enough for ET Book headings. */
  --step--1: clamp(0.8125rem, 0.7887rem + 0.1087vi, 0.875rem) !important;
  --step-0: clamp(1.0625rem, 1.0397rem + 0.1041vi, 1.125rem) !important;
  --step-1: clamp(1.1875rem, 1.1418rem + 0.2083vi, 1.3125rem) !important;
  --step-2: clamp(1.375rem, 1.284rem + 0.4149vi, 1.625rem) !important;
  --step-3: clamp(1.625rem, 1.4658rem + 0.7256vi, 2.0625rem) !important;
  --step-6: clamp(2.375rem, 1.8286rem + 2.4913vi, 3.875rem) !important;

  --space-s: clamp(0.75rem, 0.6902rem + 0.2717vi, 0.9375rem) !important;
  --space-m: clamp(1.125rem, 1.0353rem + 0.4076vi, 1.4063rem) !important;
  --space-l: clamp(1.5rem, 1.3804rem + 0.5435vi, 1.875rem) !important;
  --space-xl: clamp(2.25rem, 2.0707rem + 0.8152vi, 2.8125rem) !important;
  --space-2xl: clamp(3.25rem, 2.9076rem + 1.5217vi, 4.25rem) !important;

  --panel: color-mix(in srgb, var(--accent) 3%, var(--vivid-background)) !important;
  --panel-2: color-mix(in srgb, var(--outside) 8%, var(--vivid-background)) !important;
  --line: var(--hair) !important;
  --line-2: var(--rule) !important;
  --line-strong: var(--rule) !important;
  --screen-1: color-mix(in srgb, var(--accent) 10%, var(--vivid-background)) !important;
  --screen-2: color-mix(in srgb, var(--accent) 19%, var(--vivid-background)) !important;
  --screen-3: color-mix(in srgb, var(--accent) 28%, var(--vivid-background)) !important;
  --screen-4: color-mix(in srgb, var(--accent) 39%, var(--vivid-background)) !important;
  --screen-5: color-mix(in srgb, var(--accent) 50%, var(--vivid-background)) !important;
  --screen-6: color-mix(in srgb, var(--accent) 62%, var(--vivid-background)) !important;
  --screen-7: color-mix(in srgb, var(--accent) 76%, var(--vivid-background)) !important;

  /* analysis.html names its generated internal roles in an inline style. These aliases make their
     values root-owned; important keeps them authoritative even though that style comes later. The
     agentwatch refresh in 767c16e78 stopped shipping that page, so nothing consumes these today —
     they are kept, and kept correct, so a refresh that brings it back lands on the right palette
     rather than on whatever these last happened to say. The roles named for interface parts
     (blue, focus, office) take the structural slate; only --coral and --linked are provenance. */
  --ground: var(--vivid-background) !important;
  --paper: var(--panel) !important;
  --bg: var(--vivid-background) !important;
  --ink-2: var(--dull-foreground) !important;
  --ink-3: color-mix(in srgb, var(--dull-foreground) 72%, var(--vivid-background)) !important;
  --blue: var(--ui) !important;
  --blue-press: color-mix(in srgb, var(--ui) 84%, var(--ink)) !important;
  --coral: var(--outside) !important;
  --focus: var(--ui) !important;
  --registered: var(--accent) !important;
  --office: var(--ui) !important;
  --linked: var(--outside) !important;
  --unknown: var(--dull-foreground) !important;
}

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

html {
  background: var(--vivid-background);
  color: var(--ink);
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  scroll-padding-top: calc(4rem + 34px); /* chrome.css sets the shared value */ /* the masthead is sticky; a bare #anchor jump must clear it */
}

body {
  margin: 0;
  background: var(--vivid-background);
  font-family: var(--serif);
  font-size: var(--step-0);
  line-height: 1.5;
}

/* The explorer colours its links and the write-up does not, and that is deliberate rather than a
   drift between the two halves: a record page is hundreds of links deep, where Tufte's black-and-
   underline would be a wall of rules, while the essay is prose, where it is exactly right. What
   the two halves must agree on is that the colour is structural — it says "this is a control",
   not "this came from the dumps". Hence slate here, and green nowhere in this block. */
a { color: var(--ui); text-decoration-thickness: 1px; text-underline-offset: 2px; }
a:focus-visible, button:focus-visible, input:focus-visible, select:focus-visible, summary:focus-visible {
  outline: 2px solid var(--ui);
  outline-offset: 2px;
}
button, input, select { font: inherit; color: inherit; }
/* No record page emits either; they are for the prose on download.html, where a field name has to
   be legible as a field name.  0.9em because the mono faces run large beside ET Book. */
code { font-family: var(--mono); font-size: 0.9em; }
pre { font-family: var(--mono); font-size: var(--step--1); }

.skip {
  position: absolute;
  left: -9999px;
}
.skip:focus {
  left: 0.5rem;
  top: 0.5rem;
  z-index: 20;
  padding: 0.6rem 0.9rem;
  background: var(--panel);
  border: 1px solid var(--ui);
  border-radius: 0;
}

/* ---------------------------------------------------------------- chrome */


/* No `overflow` here, at any width: the caret menu is an absolutely positioned child, and a
   scroll container clips it to the bar's own 40px box.  The bar is four labels and a caret and
   fits 320px, so it never needed to scroll. */

/* The bar is the same four destinations everywhere. The two data views hang off Analysis behind
   this caret, one click further in. */
.nav-more { position: relative; display: flex; }
.nav-more > summary {
  display: flex;
  align-items: center;
  min-height: 40px;
  padding: 0 0.4rem;
  border-bottom: 2px solid transparent;
  color: var(--dull-foreground);
  font-family: var(--sans);
  font-size: 13px;
  list-style: none;
  cursor: pointer;
}
.nav-more > summary::-webkit-details-marker { display: none; }
.nav-more[data-active="true"] > summary,
.nav-more[open] > summary {
  color: var(--ui);
  border-bottom-color: var(--ui);
}
.nav-more-menu {
  position: absolute;
  top: 100%;
  right: 0;
  z-index: 20;
  display: flex;
  flex-direction: column;
  min-width: 10rem;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 0;
}
/* Menu rows are bar items: same font, ink, height, and the same navy rule under the one you are
   pointing at. */
.nav-more-menu a, .nav-more-menu button { justify-content: flex-start; padding: 0 0.75rem; }
.nav-more-menu a:hover, .nav-more-menu a:focus-visible {
  color: var(--ui);
  border-bottom-color: var(--ui);
}

/* Distribute all four destinations across a 390 px phone. `Analysis` reaches the other half of
   the site, so it must not be clipped or scrolled away. */
@media (max-width: 30rem) {
  .nav { width: 100%; gap: 0; justify-content: space-between; }
  .nav a, .nav button { padding: 0 0.3rem; }
  .nav-more > summary { padding: 0 0.25rem; }
}

.shell {
  /* width and side padding come from .framed-column (chrome.css), shared with the essay */
  padding-top: var(--space-m);
  padding-bottom: var(--space-2xl);
}

/* ---------------------------------------------------------- headline row */

.counts {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 0.4rem;
  margin: 0 0 var(--space-m);
  padding: 0;
}
.counts div { margin: 0; }
.counts dt {
  order: 2;
  color: var(--dull-foreground);
  font-size: var(--step--1);
}
.counts dd {
  order: 1;
  margin: 0;
  font-family: var(--serif);
  font-size: var(--step-3);
  font-weight: 400;
  font-variant-numeric: tabular-nums;
  line-height: 1.05;
  color: var(--ink);
}
.counts div { display: flex; flex-direction: column; }

/* ------------------------------------------------------------- searchbox */

/* `display: flex` beats the `hidden` attribute's UA `display: none`, and the Forum search form
 * ships hidden until Pagefind loads.  Without this it is visible and dead on
 * `file://`, which is exactly the case the fallback exists for. */
[hidden] { display: none !important; }

.searchbox {
  display: flex;
  gap: 0.4rem;
  margin: 0 0 var(--space-l);
}
.searchbox input {
  flex: 1 1 auto;
  min-width: 0;
  min-height: 44px;
  padding: 0 0.6rem;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 0;
  font-family: var(--mono);
  font-size: var(--step--1);
}
.searchbox button {
  min-height: 44px;
  padding: 0 0.9rem;
  background: var(--ui);
  color: var(--vivid-background);
  border: 1px solid var(--ui);
  border-radius: 0;
  font-size: var(--step--1);
  cursor: pointer;
}

.index-tools {
  display: flex;
  gap: 0.4rem;
  margin: 0 0 var(--space-s);
}
.index-tools input, .index-tools select {
  min-height: 40px;
  padding: 0 0.6rem;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 0;
  font-family: var(--mono);
  font-size: var(--step--1);
}
.index-tools input { flex: 1 1 auto; min-width: 0; }
.index-tools select { flex: 0 0 auto; }

/* ----------------------------------------------------------- the day bars
 * 28 days, and the largest (6,543 edits) is 165 times the median (39).  Vertical bars would make
 * 27 of them invisible slivers on a phone, so the chart is rotated: one row per day, a linear
 * stacked bar, and the count printed at the end of it.  Linear and labelled beats a log axis
 * nobody can read the rule for on a site with no sentences to state it.
 */

.days { margin: 0; padding: 0; list-style: none; border-top: 1px solid var(--line); }
.day-row {
  display: grid;
  grid-template-columns: 4.4rem minmax(0, 1fr) 3.6rem;
  align-items: center;
  gap: 0.5rem;
  min-height: 42px;
  padding: 0.2rem 0;
  border-bottom: 1px solid var(--line);
  color: inherit;
  text-decoration: none;
}
.day-row:hover .day-date, .day-row:focus-visible .day-date { color: var(--ui); }
.day-date {
  font-family: var(--mono);
  font-size: var(--step--1);
  color: var(--dull-foreground);
}
/* A hairline the full width of the plot, so a 2 px bar reads as "almost nothing out of that" and
 * not as a stray mark.  18 June is 6,543 edits against a median day of 39, so most bars are the
 * minimum width and the track is the only thing that gives them a scale. */
.day-bar {
  display: flex;
  align-items: flex-end;
  height: 14px;
  gap: 1px;
  background: linear-gradient(var(--line), var(--line)) no-repeat 0 100% / 100% 1px;
}
.day-bar span { display: block; min-width: 2px; height: 14px; }
.day-n {
  font-family: var(--mono);
  font-size: var(--step--1);
  font-variant-numeric: tabular-nums;
  text-align: right;
}

.legend {
  display: flex;
  flex-wrap: wrap;
  gap: 0.15rem 0.9rem;
  margin: var(--space-s) 0 0;
  padding: 0;
  list-style: none;
  font-size: var(--step--1);
  color: var(--dull-foreground);
}
.legend li { display: flex; align-items: center; gap: 0.35rem; }
.legend i { width: 10px; height: 10px; display: block; }
.legend b { font-family: var(--mono); font-weight: 400; color: var(--ink); }

/* --------------------------------------------------------- generic lists */

.rows { margin: 0; padding: 0; list-style: none; border-top: 1px solid var(--line); }
.rows > li { border-bottom: 1px solid var(--line); }
.row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  gap: 0.3rem 0.6rem;
  min-height: 44px;
  padding: 0.35rem 0;
  color: inherit;
  text-decoration: none;
}
.row-name {
  font-family: var(--mono);
  font-size: var(--step--1);
  overflow-wrap: anywhere;
}
.row:hover .row-name, .row:focus-visible .row-name { color: var(--ui); }
.row-n {
  font-family: var(--mono);
  font-size: var(--step--1);
  font-variant-numeric: tabular-nums;
  color: var(--dull-foreground);
  white-space: nowrap;
}
.row-meta {
  grid-column: 1 / -1;
  display: flex;
  flex-wrap: wrap;
  gap: 0.15rem 0.5rem;
  font-size: var(--step--1);
  color: var(--dull-foreground);
}
.row-meta span { font-family: var(--mono); }
.row-wiki {
  display: inline-block;
  padding: 0 0.3rem;
  border-left: 3px solid var(--line);
  font-family: var(--mono);
  font-size: var(--step--1);
  color: var(--dull-foreground);
}

.pager {
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem;
  margin: var(--space-m) 0 0;
}
.pager a, .pager span {
  display: flex;
  align-items: center;
  justify-content: center;
  min-width: 40px;
  min-height: 40px;
  padding: 0 0.4rem;
  border: 1px solid var(--line);
  border-radius: 0;
  font-family: var(--mono);
  font-size: var(--step--1);
  text-decoration: none;
}
.pager span { background: var(--ui); color: var(--vivid-background); border-color: var(--ui); }

/* ------------------------------------------------------------ page header */

.section-title {
  margin: 0;
  font-family: var(--serif);
  font-size: var(--step-0);
  font-weight: 600;
  line-height: 1.1;
  letter-spacing: -0.01em;
}

.head { margin: 0 0 var(--space-m); }
.head h1 {
  margin: 0 0 0.2rem;
  font-family: var(--serif);
  font-size: var(--step-2);
  font-weight: 400;
  line-height: 1.2;
  overflow-wrap: anywhere;
}
.head .meta {
  display: flex;
  flex-wrap: wrap;
  gap: 0.1rem 0.6rem;
  margin: 0;
  font-family: var(--mono);
  font-size: var(--step--1);
  color: var(--dull-foreground);
}
.head-labels {
  margin: 0.4rem 0 0;
  font-family: var(--mono);
  font-size: var(--step--1);
}
.head-labels summary {
  min-height: 40px;
  display: flex;
  align-items: center;
  cursor: pointer;
  color: var(--dull-foreground);
}
.head-labels p { margin: 0 0 0.4rem; overflow-wrap: anywhere; }

/* ---------------------------------------------------------- the thread
 * The core of the site.  An append renders as flowing page text with a slim gutter marker, so a
 * page that only ever grew reads top to bottom as it grew.  Anything that removed or replaced a
 * line renders as a diff block at the same place in the chronology.
 */

/* The rail is fixed to the right edge, so on a phone the thread has to leave room for it or the
 * record's own text runs underneath it and is clipped.  Above 56rem the centred 48rem column
 * already clears the rail by more than its width, so the reservation goes away. */
.thread { margin: 0; padding: 0 38px 0 0; list-style: none; }
@media (min-width: 40rem) { .thread { padding-right: 48px; } }
@media (min-width: 56rem) { .thread { padding-right: 0; } }

.entry {
  position: relative;
  margin: 0 0 var(--space-m);
  padding-left: 0.7rem;
  border-left: 3px solid transparent;
  scroll-margin-block: 30vh;
  transition: border-color 300ms ease, opacity 300ms ease;
}
.entry.current { border-left-color: var(--ui); }
.entry:not(.current) { opacity: 0.82; }

.marker {
  display: flex;
  flex-wrap: wrap;
  gap: 0.1rem 0.5rem;
  margin: 0 0 0.25rem;
  font-family: var(--mono);
  font-size: var(--step--1);
  line-height: 1.4;
  color: var(--dull-foreground);
  overflow-wrap: anywhere;
}
.marker a { color: var(--dull-foreground); text-decoration: none; }
.marker a:hover { color: var(--ui); text-decoration: underline; }
.marker .seq { color: var(--ui); }
.event-label {
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.text {
  margin: 0;
  font-family: var(--mono);
  font-size: var(--step--1);
  line-height: 1.55;
  white-space: pre-wrap;
  /* `break-word`, not `anywhere`: the record is full of URLs long enough to overflow a phone and
   * those still break, but an ordinary word stops being cut in half mid-line. */
  overflow-wrap: break-word;
}

.diff {
  margin: 0;
  padding: 0.2rem 0;
  border: 1px solid var(--line);
  border-radius: 0;
  background: var(--panel);
}
.line {
  display: grid;
  grid-template-columns: 1.3rem minmax(0, 1fr);
  font-family: var(--mono);
  font-size: var(--step--1);
  line-height: 1.5;
  min-height: 1.5em;
}
.line .g { text-align: center; user-select: none; color: var(--dull-foreground); }
.line .t { white-space: pre-wrap; overflow-wrap: break-word; padding-right: 0.4rem; }
.line.add { background: var(--screen-1); }
.line.rm { background: var(--panel-2); }
.line.rm .t { text-decoration: line-through; text-decoration-thickness: 1px; }
.line.ctx .t, .line.skip .t { color: var(--dull-foreground); }
.line.skip { opacity: 0.6; }

.event {
  display: block;
  padding: 0.35rem 0.5rem;
  border: 1px solid var(--line);
  border-radius: 0;
  font-family: var(--mono);
  font-size: var(--step--1);
  color: var(--dull-foreground);
  overflow-wrap: anywhere;
}

/* ------------------------------------------------------------- the rail */

.rail {
  position: fixed;
  z-index: 5;
  right: max(2px, env(safe-area-inset-right));
  top: 50%;
  transform: translateY(-50%);
  width: 34px;
  max-height: 66vh;
  display: flex;
  flex-direction: column;
  justify-content: center;
}
.rail::before {
  content: "";
  position: absolute;
  z-index: -1;
  right: 16px;
  top: 4px;
  bottom: 4px;
  width: 1px;
  background: var(--line);
}
.tick {
  position: relative;
  width: 34px;
  padding: 0;
  border: 0;
  background: transparent;
  cursor: pointer;
  flex: 0 0 auto;
}
.tick::before {
  content: "";
  position: absolute;
  right: 13px;
  top: calc(50% - 3px);
  width: 6px;
  height: 6px;
  border: 1px solid var(--line);
  border-radius: 50%;
  background: var(--vivid-background);
  transition: width 300ms ease, height 300ms ease, background 300ms ease, border-color 300ms ease;
}
.tick.on::before {
  right: 12px;
  top: calc(50% - 4px);
  width: 8px;
  height: 8px;
  border-color: var(--ui);
  background: var(--ui);
}
.tick:focus-visible { outline: 2px solid var(--ui); outline-offset: -3px; }

/* --------------------------------------------------------------- search */

.search-results { margin: var(--space-m) 0 0; padding: 0; list-style: none; }
.search-results li { border-bottom: 1px solid var(--line); padding: 0.5rem 0; }
.search-results a {
  font-family: var(--mono);
  font-size: var(--step--1);
  overflow-wrap: anywhere;
}
.search-results p {
  margin: 0.2rem 0 0;
  font-family: var(--mono);
  font-size: var(--step--1);
  color: var(--dull-foreground);
  overflow-wrap: anywhere;
}
.search-results .result-meta { display: flex; gap: 0.5rem; }
.search-results mark { background: var(--screen-2); color: inherit; }
/* The two sentences the browser is allowed to say (CONTRACT.md §2): why a thread is empty, and
   what an edit-less row in the index is.  Styled as chrome, never as record: soft ink, no rule on
   the left, so it cannot be mistaken for the coral-edged deletion event beside it. */
.notice {
  margin: 0 0 var(--space-m);
  font-family: var(--mono);
  font-size: var(--step--1);
  line-height: 1.5;
  color: var(--dull-foreground);
}
.rows + .notice, .notice + .rows { margin-top: var(--space-s); }

.state {
  margin: var(--space-s) 0 0;
  font-family: var(--mono);
  font-size: var(--step--1);
  color: var(--dull-foreground);
}

/* ------------------------------------------------------- the download page
 * The blurb is the page's own voice, so it is body serif, not the mono the records use.  The
 * checksums are folded away because five 64-character hashes shout louder than anything else on
 * the page, and they are only wanted once, at the moment someone verifies a file.
 */

.blurb { margin: 0 0 var(--space-m); }
.blurb:last-of-type { margin-bottom: var(--space-l); }

/* .section-title is margin:0 because the label pages set it against a .counts grid that already
   carries the gap.  Here it follows the file list, which does not, so the air is added on the
   pairing rather than the class — the same way .rows and .notice space themselves above. */
.rows + .section-title { margin-top: var(--space-l); }

/* These row-meta items are phrases, not the self-labelling atoms ("342 labels", a date) the index
   rows carry, so gap alone lets them read as one run-on line.  Scoped to .downloads: the index
   rows separate with .row-wiki's rule instead, and must not gain a second divider. */
.downloads .row-meta span + span::before {
  content: "·";
  padding-right: 0.5rem;   /* matches .row-meta's column gap, so the dot sits centred between items */
}

/* A third column ahead of the name for the glyph.  The meta line starts at column 2 so it hangs
   under the filename rather than under the icon. */
.downloads .row { grid-template-columns: auto minmax(0, 1fr) auto; }
.downloads .row-meta {
  grid-column: 2 / -1;
  font-size: 11.5px;   /* fixed, not --step--1: these are descriptions, and stay well under the
                          filename at every width.  Only here — the index rows' meta is data. */
}
.downloads .row-icon {
  grid-row: 1 / span 2;    /* spans the name line and the meta line; .row's align-items centres it
                              over both.  Not 1 / -1: with no explicit rows, -1 is still line 1. */
  width: 20px;             /* reads against the whole two-line row, not just the filename beside it */
  height: 20px;
  color: var(--dull-foreground);
}

/* These rows are the point of the page, so they take a fill as well as the ink the rest of the
   explorer uses: the name already goes accent (.row, above) and the glyph and size follow it.  The
   fill is the column's own width, so it lines up with the rules between rows instead of bleeding
   past their ends. */
.downloads .row:hover, .downloads .row:focus-visible { background: var(--panel); }
.downloads .row:hover .row-icon, .downloads .row:focus-visible .row-icon,
.downloads .row:hover .row-n, .downloads .row:focus-visible .row-n { color: var(--ui); }

/* Holds the symbol only; never painted. */
.sprite { position: absolute; width: 0; height: 0; overflow: hidden; }

.checksums {
  margin: var(--space-l) 0 0;
  font-family: var(--mono);
  font-size: var(--step--1);
}
.checksums summary {
  min-height: 40px;
  display: flex;
  align-items: center;
  cursor: pointer;
  color: var(--dull-foreground);
}
.checksums p { margin: 0 0 0.6rem; color: var(--dull-foreground); }
.checksums pre {
  margin: 0;
  padding-bottom: 0.4rem;
  overflow-x: auto;      /* 64 hex + a filename does not fit a phone, and must not be re-wrapped */
  line-height: 1.7;
}

@media (min-width: 40rem) {
  .day-row { grid-template-columns: 5.5rem minmax(0, 1fr) 4.5rem; }
  .rail { width: 44px; right: max(6px, env(safe-area-inset-right)); }
  .tick { width: 44px; }
  .tick::before { right: 18px; }
  .tick.on::before { right: 17px; }
  .rail::before { right: 21px; }
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .entry, .tick::before { transition: none; }
}
