/* 1bit — the whole stylesheet.
 *
 * docs/06-website.md ▸ Part 2 is the source of truth. The short version:
 *
 *   Two colours. One unit. Bitmap type at whole multiples of its own cell.
 *   Every shade is a checkerboard of the two colours, never a grey.
 *   A radius only ever makes a circle. A shadow never has a blur.
 *   Nothing moves except a marquee, a cursor, and the invert on touch.
 *
 * The page is still mostly paper, but the gaps are tighter than the first
 * sparse draft: ink on words and hairlines, without a manifesto's worth of
 * empty air between every section. Type is small and exact.
 *
 * `build.py check` enforces the mechanical half and fails the build on a grey.
 */

/* ── the faces ──────────────────────────────────────────────────────────
 * Subset to what the site actually sets, which is a couple of kilobytes.
 * `font-display: block` rather than `swap`: a bitmap page briefly set in
 * Helvetica reflows every measure, which is a worse first impression than a
 * beat of blank paper.
 */
@font-face {
  font-family: "unscii";
  src: url("../fonts/unscii-16.woff2") format("woff2");
  font-weight: normal; font-style: normal; font-display: block;
}

/* ── the constitution ───────────────────────────────────────────────────*/
:root {
  --ink: #000000;
  --paper: #ffffff;

  /* The virtual pixel. Every border, gap, offset and size below is a whole
     multiple of it, and there are no fractions anywhere in this file. */
  --px: 2px;

  /* Type. 16px is unscii-16's native cell and the only legal values are its
     whole multiples, because a bitmap face at 1.5x is a bitmap face with grey
     edges, which is the one thing this design cannot survive.

     Body stays at 16 at every width. The draft before this one stepped it to
     32 on desktop and the result read as shouting: a monospace face at 32px
     fills a screen with about forty words and nothing else. Small type with a
     lot of paper around it is the look; large type is a different product. */
  --body: 16px;
  --lead: 24px;

  /* The vertical rhythm, in whole leads. Everything vertical is one of these. */
  --gap-s: 16px;
  --gap-m: 32px;
  --gap-l: 64px;
  --gap-xl: 96px;

  --measure: 58ch;   /* exact rather than approximate: the face is monospace */
  --wide: 960px;     /* three 264px shots and two gaps, with room to spare */
  --h1: 32px;

  /* A section title is a whole cell above the body it introduces, and it moves
     when the body moves. It used to be `--chrome`, which is fixed at 16px, and
     the first reader to press A++ got 48px prose under 16px section names: the
     titles vanished, and a page of long sections with nothing announcing them
     reads as one undifferentiated run of words. A heading that does not scale
     with the text it heads is not a heading at the sizes that need one most. */
  --h2: 32px;

  /* Two different smalls, deliberately. `--small` is *content* set small by
     choice: a caption, an aside, a table, the aux column on a launcher row.
     It steps with the reader's control, because it is part of what they asked
     to be able to read. `--chrome` is furniture: the top bar, the page strip,
     the status line, the foot. Those stay put at every setting, the way a
     title bar does; growing them costs the page its whole top edge and gives
     nothing back. */
  --small: 16px;
  --chrome: 16px;
}

/* Day, night, auto. Auto follows the OS; the switch in the top bar has to beat
   it in both directions, which is why "light" is stamped as deliberately as
   "dark". Night is genuinely just these two values swapping. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) { --ink: #ffffff; --paper: #000000; }
}
:root[data-theme="dark"]  { --ink: #ffffff; --paper: #000000; }
:root[data-theme="light"] { --ink: #000000; --paper: #ffffff; }

/* The one thing that steps at a breakpoint is the headline, and it steps by a
   whole cell. No fluid type, no clamp(), no vw units anywhere: a monospace grid
   that reflows continuously is a monospace grid you have broken. */
@media (min-width: 720px) { :root { --h1: 48px; } }

/* The reader's own control. Three steps, all of them whole cells.

   The headings stop climbing at 48 because that is the largest whole cell the
   face has, and `--small` stops at 32 for the same reason from the other end:
   it has to stay a step under the body to still read as an aside. */
:root[data-size="2"] { --body: 32px; --lead: 48px; --measure: 40ch;
                       --h1: 48px; --h2: 48px; --small: 32px; }
:root[data-size="3"] { --body: 48px; --lead: 72px; --measure: 28ch;
                       --h1: 48px; --h2: 48px; --small: 32px; }

/* ── reset ──────────────────────────────────────────────────────────────
 * border-radius is zeroed on everything and only ever put back as 50%, on
 * something that genuinely is a disc. There is no softened rectangle here.
 */
*, *::before, *::after { box-sizing: border-box; border-radius: 0; }

html { background: var(--paper); }

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: "unscii", ui-monospace, "SF Mono", Menlo, monospace;
  font-size: var(--body);
  line-height: var(--lead);
  -webkit-font-smoothing: none;
  padding-bottom: var(--gap-l);
  text-rendering: optimizeSpeed;
}

/* The 50% shade, and the only one on the site: a checkerboard of the same two
   colours at whole virtual pixels. Zoom in on it; it is ink and paper. */
.dither {
  background-image: conic-gradient(
    var(--ink) 0 25%, var(--paper) 0 50%, var(--ink) 0 75%, var(--paper) 0);
  background-size: calc(var(--px) * 2) calc(var(--px) * 2);
}

.skip {
  position: absolute; left: var(--gap-s); top: -100px;
  background: var(--paper); color: var(--ink);
  border: var(--px) solid var(--ink);
  padding: calc(var(--px) * 2) calc(var(--px) * 3);
  font-size: var(--chrome); line-height: var(--chrome);
  text-transform: uppercase; z-index: 20;
}
.skip:focus { top: calc(var(--px) * 2); }

/* An inverted hover state with no focus state leaves a keyboard user with
   nowhere to be. An ink outline offset by one virtual pixel becomes a paper
   outline on an inverted row, so it stays visible either way. */
:where(a, button, summary, input, [tabindex]):focus-visible {
  outline: calc(var(--px) * 2) solid var(--ink);
  outline-offset: var(--px);
}

/* ── the top bar ────────────────────────────────────────────────────────
 * The app's own furniture: mark and wordmark on the left, the live clock on
 * the right. The clock is the visitor's, not ours, and it is the cheapest
 * possible proof that the page is alive.
 */
.topbar {
  position: sticky; top: 0; z-index: 10;
  display: flex; align-items: center; gap: var(--gap-s);
  padding: calc(var(--px) * 3) var(--gap-s);
  background: var(--paper);
  border-bottom: var(--px) solid var(--ink);
  font-size: var(--chrome); line-height: var(--chrome);
  text-transform: uppercase; letter-spacing: var(--px);
}
.topbar a { text-decoration: none; color: inherit; }
.wordmark { display: flex; align-items: center; gap: calc(var(--px) * 4); white-space: nowrap; }
/* The mark is a black tile with a white block on it. On black paper the tile
   and the page are the same colour, so it gets the hairline the pictures get
   and stays a square rather than becoming a floating white shape. */
.wordmark img { display: block; image-rendering: pixelated;
  outline: var(--px) solid var(--ink); }
.topbar .spacer { flex: 1; }
.topbar .tools { display: flex; align-items: center; gap: calc(var(--px) * 3); }
.clock { font-variant-numeric: tabular-nums; white-space: nowrap; }

/* The page nav is a strip of names, not a bar of buttons. One hairline under
   it and nothing else; the current page is the one wearing ink. */
.pages {
  position: sticky; top: 46px; z-index: 9;
  background: var(--paper);
  border-bottom: var(--px) solid var(--ink);
  overflow-x: auto;
}
.pages ul {
  display: flex; gap: 0; list-style: none; margin: 0 auto; padding: 0;
  max-width: var(--wide); width: max-content; min-width: 100%;
  justify-content: center;
}
.pages a {
  display: block; white-space: nowrap;
  padding: calc(var(--px) * 3) calc(var(--px) * 6);
  font-size: var(--chrome); line-height: var(--chrome);
  text-transform: uppercase; letter-spacing: var(--px);
  text-decoration: none; color: inherit;
}
.pages a:hover, .pages a[aria-current="page"] { background: var(--ink); color: var(--paper); }

/* ── the column ─────────────────────────────────────────────────────────
 * One column, centred, and the space either side is paper. Not a pattern, not
 * a texture, not a dithered fill. The app is mostly paper too.
 */
main { padding: 0 var(--gap-s); }
/* margin-inline, not the `margin: 0 auto` shorthand. These classes sit on the
   same elements as `section`, they win the specificity tie, and a shorthand
   there silently zeroes the vertical rhythm for the whole page. That is exactly
   what it did: every section butted against the next one. Horizontal centring
   is all these two are for, so horizontal centring is all they set. */
.col { max-width: var(--measure); margin-inline: auto; }
.wide { max-width: var(--wide); margin-inline: auto; }

/* Only the bottom margin is set here. A `margin: 0 0 x` shorthand on a section
   would beat `.col`'s `margin: 0 auto` on specificity ties and quietly shove
   every centred column to the left, which is exactly what it did. */
section { margin-bottom: var(--gap-l); }

/* The hero pitch centres the brand and the line under it. Body stays left:
   centred multi-line prose is hard to read, and the pictures already do the
   heavy lifting. */
.hero { padding-top: var(--gap-m); margin-bottom: var(--gap-m); }
.hero + section { margin-top: 0; }
.hero-pitch { text-align: center; }
.hero-pitch .brand-mark {
  display: block;
  margin: 0 auto var(--gap-s);
  image-rendering: pixelated;
  image-rendering: crisp-edges;
}
.hero-pitch .brand {
  margin: 0 0 var(--gap-s);
  font-size: var(--h1); line-height: var(--h1);
  text-transform: uppercase; letter-spacing: var(--px);
}
.hero-pitch h1 { margin-bottom: var(--gap-m); }
.hero-pitch .lead { text-align: left; }

/* ── type ───────────────────────────────────────────────────────────────*/
h1 {
  font-size: var(--h1); line-height: var(--h1);
  margin: 0 0 var(--gap-m);
  font-weight: normal; text-transform: uppercase; text-wrap: balance;
}

/* A section header is a title with a hairline under it, not a full width slab
   of ink. The app's inverted strip is right on a 390px screen and wrong across
   a monitor, where it reads as a bar of tape stuck over the page. The rule says
   the same thing and leaves the paper alone.

   Two things were wrong with the first version of this, and they were the same
   mistake twice. The title was set at `--chrome`, the top bar's size, which is
   fixed: a reader who pressed A++ got 48px prose under 16px section names,
   never saw them, and read the page as one undifferentiated run of words. And
   the hairline ran out of the *side* of the title as a flex `::after`, which is
   a fine device for a label of a few characters and falls over the moment the
   title is a sentence. "THREE · THE LIST MOVES TO YOUR HOME SCREEN" at a whole
   cell above the body is wider than the measure, so the words wrapped and the
   rule was squeezed to nothing. Underneath, it is the same one hairline and it
   is the same width whatever the title says.

   A whole cell above the body, and it steps with it. On a narrow screen that
   brings it level in size with `h1`; the two are told apart by the rule, the
   letterspacing, and the band of paper above the headline, which is the ranking
   a reader actually uses. */
h2 {
  font-size: var(--h2); line-height: var(--h2); font-weight: normal;
  text-transform: uppercase; letter-spacing: var(--px); text-wrap: balance;
  margin: 0 0 var(--gap-m);
  padding-bottom: calc(var(--px) * 4);
  border-bottom: var(--px) solid var(--ink);
}
h3 {
  font-size: var(--body); line-height: var(--lead); font-weight: normal;
  text-transform: uppercase; letter-spacing: var(--px);
  margin: var(--gap-m) 0 var(--gap-s);
}

p { margin: 0 0 var(--gap-s); }
p:last-child { margin-bottom: 0; }
ul, ol { margin: 0 0 var(--gap-s); padding-left: calc(var(--px) * 10); }
/* Only prose lists get air between their items. A blanket `li` margin reaches
   the nav strip, the launcher and every figure row and pads each of them by a
   line nobody asked for, which is what made the page nav twice as tall as its
   own type. */
li { margin: 0; }
ul:not([class]) > li, ol:not([class]) > li { margin-bottom: var(--gap-s); }
ul:not([class]) > li:last-child, ol:not([class]) > li:last-child { margin-bottom: 0; }

/* Lead paragraphs sit a normal line apart from each other, and the gap goes in
   front of whatever follows the last of them. Giving every .lead the big margin
   put a hole in the middle of the hero. */
.lead { margin-bottom: var(--gap-s); }

/* Emphasis inverts a few words rather than bolding them, because a bitmap face
   has no bold and a faked one is a blurred one. Used sparingly. */
strong { font-weight: normal; background: var(--ink); color: var(--paper); padding: 0 var(--px); }
em { font-style: normal; text-decoration: underline; text-underline-offset: calc(var(--px) * 2); }
a { color: inherit; text-underline-offset: calc(var(--px) * 2); }
code { font-family: inherit; border: var(--px) solid var(--ink); padding: 0 var(--px); }
hr { border: 0; border-top: var(--px) solid var(--ink); margin: var(--gap-l) 0; }

.quiet { text-transform: uppercase; font-size: var(--small); line-height: var(--lead); letter-spacing: var(--px); }
.centre { text-align: center; }
.note { margin-top: var(--gap-s); }

/* ── the launcher list ──────────────────────────────────────────────────
 * Rockbox's inverted bar, which is free, instant and unmistakable. Rows are
 * wide rather than tall and never below 44px whatever the type is doing. One
 * hairline between rows, none around the outside.
 */
.launcher { list-style: none; margin: 0; padding: 0; }
.launcher li { margin: 0; border-top: var(--px) solid var(--ink); }
.launcher li:last-child { border-bottom: var(--px) solid var(--ink); }
.launcher a {
  display: flex; align-items: baseline; gap: var(--gap-s);
  min-height: 44px; padding: calc(var(--px) * 5) calc(var(--px) * 4);
  text-decoration: none; text-transform: uppercase; letter-spacing: var(--px);
}
.launcher a:hover, .launcher a:active { background: var(--ink); color: var(--paper); }
.row-name { flex: 1; }
.row-aux { white-space: nowrap; font-size: var(--small); }
.row-mark { white-space: nowrap; }

/* ── pictures ───────────────────────────────────────────────────────────
 * Authored at the exact size they are drawn at, and at twice it for a 2x
 * screen, so one image pixel always lands on one device pixel. Nothing here
 * may resize one: a dithered picture at 0.87 scale is moire static.
 *
 * **A picture is never turned over at night.** This is the app's own rule
 * (`CLAUDE.md` ▸ "A picture that is already ink and paper"), and the site
 * spent a draft on the other side of it: every file was stored as a negative
 * with its frame drawn in, and `mix-blend-mode: difference` over paper handed
 * back the positive by day and left the negative standing by night. It works,
 * and what it looks like is a photocopy. A light iOS sheet came out black, a
 * capture of the app's own white paper came out black beside it, and a
 * screenshot of a real screen that no screen has ever looked like reads as
 * something cheaply made. So the files are the pictures, one appearance, at
 * every hour and in print.
 *
 * That leaves the job the baked-in frame was doing: a dark plate on dark paper
 * has no edge. An `outline` does it in ink, which follows the palette, sits
 * outside the box, costs no layout and cannot resample what is inside it. A
 * `border` could not: with `box-sizing: border-box` it eats two pixels off the
 * picture's own width, and two pixels off a bitmap screenshot is a resample.
 */
.shot {
  display: block; margin: 0 auto;
  image-rendering: pixelated;
  max-width: 100%; height: auto;
  outline: var(--px) solid var(--ink);
}

.plate { margin: 0 0 var(--gap-m); text-align: center; }
.plate figcaption {
  margin-top: calc(var(--px) * 5);
  font-size: var(--small); line-height: var(--lead);
  text-transform: uppercase; letter-spacing: var(--px);
}

/* A row of pictures that breaks out of the measure. Wraps rather than scrolls,
   and centres what is left over, so it works at any number of them. */
.row-of-shots {
  display: flex; flex-wrap: wrap; justify-content: center;
  gap: var(--gap-m); list-style: none; margin: 0 0 var(--gap-m); padding: 0;
}
.row-of-shots li { margin: 0; }
.row-of-shots figure { margin: 0; text-align: center; }
/* A caption must not be what decides how wide its picture is. A flex item is
   laid out at its max-content width, and a caption is nearly always longer than
   the shot above it, so three 264px pictures were being given 650px each and
   the row wrapped after two. Zero width takes the caption out of that
   measurement; min-width puts it back to whatever the picture settled on. */
.row-of-shots figcaption {
  width: 0; min-width: 100%;
  margin-top: calc(var(--px) * 5);
  font-size: var(--small); line-height: var(--lead);
  text-transform: uppercase; letter-spacing: var(--px);
}

/* ── the App Store badge ────────────────────────────────────────────────
 * Live link later: drop is-soon, wrap in <a href="…">. Until then it is a
 * dithered plate so it reads as present but not pressable. No grey: shade is
 * the house checker of ink and paper.
 */
.store {
  margin-top: var(--gap-m);
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
}
.store > .quiet { margin-bottom: calc(var(--px) * 3); }
.store-badge {
  display: inline-flex; align-items: center; gap: calc(var(--px) * 4);
  border: var(--px) solid var(--ink);
  padding: calc(var(--px) * 3) calc(var(--px) * 5);
  background: var(--ink); color: var(--paper);
  text-decoration: none; text-align: left;
  min-height: 48px;
}
.store-badge.is-soon {
  background-image: conic-gradient(
    var(--ink) 0 25%, var(--paper) 0 50%, var(--ink) 0 75%, var(--paper) 0);
  background-size: calc(var(--px) * 2) calc(var(--px) * 2);
  color: var(--ink);
  cursor: default; pointer-events: none;
  user-select: none;
}
.store-apple { display: block; flex: 0 0 auto; }
.store-copy { display: flex; flex-direction: column; gap: 0; line-height: 1.1; }
.store-line {
  font-size: var(--small); text-transform: uppercase; letter-spacing: var(--px);
}
.store-name {
  font-size: var(--h2); line-height: var(--h2);
  text-transform: uppercase; letter-spacing: var(--px);
}
button, .button {
  font: inherit; text-transform: uppercase; letter-spacing: var(--px);
  background: var(--paper); color: var(--ink);
  border: var(--px) solid var(--ink);
  padding: calc(var(--px) * 3) calc(var(--px) * 6);
  min-height: 48px; cursor: pointer; transition: none;
  text-decoration: none; display: inline-block; white-space: nowrap;
}
button:hover, button:active, .button:hover, .button:active,
button[aria-pressed="true"] { background: var(--ink); color: var(--paper); }
.chip {
  font-size: var(--chrome); line-height: var(--chrome); min-height: 0;
  padding: calc(var(--px) * 2) calc(var(--px) * 3);
}

/* ── the FAQ ────────────────────────────────────────────────────────────
 * Real <details>, so it works with the script off and search engines read the
 * answers. The marker is one of the app's own action marks.
 */
.faq { border-top: var(--px) solid var(--ink); }
.faq:last-of-type { border-bottom: var(--px) solid var(--ink); margin-bottom: var(--gap-m); }
.faq summary {
  display: flex; gap: var(--gap-s); align-items: baseline;
  list-style: none; cursor: pointer;
  padding: calc(var(--px) * 5) calc(var(--px) * 4);
  min-height: 44px;
}
.faq summary::-webkit-details-marker { display: none; }
.faq summary::after { content: "+"; margin-left: auto; }
.faq[open] summary::after { content: "\2212"; }
.faq summary:hover { background: var(--ink); color: var(--paper); }
.faq p { padding: 0 calc(var(--px) * 4); }
.faq p:last-child { padding-bottom: calc(var(--px) * 5); }

/* ── entries on /manual ─────────────────────────────────────────────────*/
.entry, .step { margin: 0 0 var(--gap-m); scroll-margin-top: calc(var(--gap-m) + var(--gap-s)); }
.entry-what { margin-bottom: var(--gap-s); }
.entry-built { font-size: var(--small); line-height: var(--lead); }
/* The step number was a flex item with a gap after it. The h2 is not a flex
   container any more, so the air is the span's own. */
.step-no { white-space: nowrap; margin-right: calc(var(--px) * 6); }
.step-no::after { content: " ·"; }

/* ── tables, for the reference pages ────────────────────────────────────*/
.scroller { overflow-x: auto; margin: 0 0 var(--gap-m); }
table { border-collapse: collapse; width: 100%; font-size: var(--small); line-height: var(--lead); }
th, td { text-align: left; vertical-align: top; padding: calc(var(--px) * 3) calc(var(--px) * 4) calc(var(--px) * 3) 0; border-bottom: var(--px) solid var(--ink); }
th { text-transform: uppercase; letter-spacing: var(--px); font-weight: normal; }
pre { margin: 0 0 var(--gap-s); padding: calc(var(--px) * 4); border: var(--px) solid var(--ink); overflow-x: auto; font-size: var(--small); line-height: var(--lead); }

/* ── the one modal shape ────────────────────────────────────────────────
 * The only permitted shadow on the site: a hard ink rectangle offset down and
 * right with no blur, which is how a two-colour desktop drew a dialog. It
 * appears on the 404 page and nowhere else.
 */
.confirm {
  border: var(--px) solid var(--ink);
  background: var(--paper);
  box-shadow: calc(var(--px) * 3) calc(var(--px) * 3) 0 0 var(--ink);
  margin: 0 auto var(--gap-l); max-width: 44ch;
}
/* The one box on the site wears its title as an ink strip, which is the app's
   own modal. It stays at chrome size and drops the section rule: a hairline
   under a band of ink is a hairline nobody can see. */
.confirm h2 {
  margin: 0; padding: calc(var(--px) * 3) calc(var(--px) * 4);
  font-size: var(--chrome); line-height: var(--chrome);
  border-bottom: 0;
  background: var(--ink); color: var(--paper);
}
.confirm-body { padding: var(--gap-s); }
.confirm .answers { display: flex; border-top: var(--px) solid var(--ink); }
.confirm .answers > * { flex: 1; border: 0; border-left: var(--px) solid var(--ink); text-align: center; }
.confirm .answers > *:first-child { border-left: 0; }

/* ── the foot ───────────────────────────────────────────────────────────
 * A band of blank paper with one round button in it, which goes home. The only
 * 50% radius in the file, on something that genuinely is a disc.
 */
.bezel {
  border-top: var(--px) solid var(--ink);
  padding: var(--gap-m) 0;
  display: flex; justify-content: center;
}
.home {
  width: 48px; height: 48px; padding: 0; min-height: 0;
  background: var(--paper);
  border: calc(var(--px) * 2) solid var(--ink);
  border-radius: 50%;
  display: grid; place-items: center;
}
.home span { display: block; width: 16px; height: 16px; border: var(--px) solid var(--ink); }
.home:hover, .home:active { background: var(--ink); }
.home:hover span, .home:active span { border-color: var(--paper); }

.foot { padding: 0 0 var(--gap-m); text-align: center; }
.foot ul {
  list-style: none; margin: 0 0 var(--gap-s); padding: 0;
  display: flex; flex-wrap: wrap; justify-content: center; gap: 0 var(--gap-s);
}
.foot li { margin: 0; }
.foot a { font-size: var(--chrome); line-height: var(--lead); text-transform: uppercase; letter-spacing: var(--px); }

/* ── motion ─────────────────────────────────────────────────────────────
 * Nearly none. The blinking cursor and the invert on hover, which is a state
 * change rather than a transition. The status-line marquee is gone: it read as
 * costume on a marketing page.
 */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation: none !important; transition: none !important; }
}

@media (max-width: 600px) {
  :root { --gap-l: 48px; --gap-xl: 64px; }
  .row-aux { display: none; }
  .pages ul { justify-content: flex-start; }
  .topbar { gap: calc(var(--px) * 6); }
}

/* Printed, this site is already black and white, which is the one format it
   was always going to be right for. The chrome is screen furniture. */
@media print {
  .topbar, .pages, .bezel { display: none; }
  body { padding: 0; }
}
