/* ============================================================================
   Moon Uniform — the design system.

   Everything two pages share lives here and nowhere else. A page's own
   stylesheet may hold only what exists on that page alone.

   UNITS. Sizes are in rem, so the page follows the text size the reader has
   set in their browser. That matters more here than on most sites: this is a
   page about an alphabet for people who cannot see well, and a hard-coded
   pixel size silently overrules the one accessibility setting every browser
   has. Pixels are used for two things only: hairlines, which stay one device
   pixel however large the text is, and shadow offsets.

   SCALE. Spacing steps from a quarter of a rem and doubles: 1, 2, 3, 4, 6, 8,
   12, 16. Type steps through a fixed set. Four breakpoints, no others. Nothing
   in this file is a number chosen on the spot; if a value is needed that the
   scale does not have, the scale is wrong and gets fixed here.

   COLOUR. Moon is printed by pressing shapes into heavy paper, and read with a
   fingertip. Two materials set the page: the warm stock, and the black type
   set beside it for sighted readers. One colour is added, a deep green, the
   colour these volumes were bound in, and it appears only where a reader can
   act. So colour here always means "something happens".
   ========================================================================== */

@font-face {
  font-family: "Moon Uniform";
  font-weight: 300;
  font-display: swap;
  src: url("../fonts/webfonts/MoonUniform-Light.woff2") format("woff2"),
       url("../fonts/otf/MoonUniform-Light.otf") format("opentype");
}

@font-face {
  font-family: "Moon Uniform";
  font-weight: 400;
  font-display: swap;
  src: url("../fonts/webfonts/MoonUniform-Regular.woff2") format("woff2"),
       url("../fonts/otf/MoonUniform-Regular.otf") format("opentype");
}

@font-face {
  font-family: "Moon Uniform";
  font-weight: 700;
  font-display: swap;
  src: url("../fonts/webfonts/MoonUniform-Bold.woff2") format("woff2"),
       url("../fonts/otf/MoonUniform-Bold.otf") format("opentype");
}


/* ---------------------------------------------------------------------------
   Tokens
   --------------------------------------------------------------------------- */

:root {
  /* Colour ---------------------------------------------------------------- */
  --paper:       #fbf7ef;   /* the sheet */
  --paper-sunk:  #f4eee1;   /* a panel set into it */
  --paper-press: #ece2ce;   /* pressed harder still */
  --ink:         #1f1a13;   /* 15.7:1 on the sheet */
  --ink-soft:    #5c5343;   /*  7.2:1 on the sheet */
  --rule:        #ddd0b6;
  --rule-strong: #b6a37f;
  --accent:      #1c5a49;   /*  7.3:1 on the sheet */
  --accent-dark: #123f33;
  --on-accent:   #fbf7ef;
  --focus:       #0b3fbe;

  /* Type ------------------------------------------------------------------ */
  --font-ui: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
             "Helvetica Neue", Arial, sans-serif;
  --font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas,
               "Liberation Mono", monospace;

  --text-2xs:  0.6875rem;   /* 11 px — the smallest label */
  --text-xs:   0.75rem;     /* 12 px */
  --text-sm:   0.875rem;    /* 14 px */
  --text-base: 1rem;
  --text-md:   1.0625rem;   /* 17 px — reading size on a narrow screen */
  --text-lg:   1.125rem;    /* 18 px — reading size */
  --text-xl:   1.5rem;
  --text-2xl:  2rem;
  --text-h2:   clamp(1.5rem, 1rem + 2vw, 2.25rem);
  --text-h1:   clamp(2rem, 1.25rem + 4vw, 4rem);
  --text-lead: clamp(var(--text-lg), 1rem + 0.5vw, var(--text-xl));

  --track-wide:  0.125em;   /* uppercase labels */
  --track-tight: -0.02em;   /* large headings */
  --leading-body: 1.6;
  --leading-tight: 1.15;

  /* Space — quarter-rem base, doubling ------------------------------------ */
  --space-1:  0.25rem;
  --space-2:  0.5rem;
  --space-3:  0.75rem;
  --space-4:  1rem;
  --space-6:  1.5rem;
  --space-8:  2rem;
  --space-12: 3rem;
  --space-16: 4rem;

  /* Structure ------------------------------------------------------------- */
  --hairline:    1px;
  --radius-sm:   0.25rem;
  --radius:      0.5rem;
  --radius-pill: 999px;
  --duration:    0.15s;

  /* The page is exactly as wide as what it holds: the rail, the gap between the two columns, and
     the measure. It used to be a round 80rem, which is wider than the content ever gets, so every
     page sat against the left of its own box with a band of empty stock down the right. Derived
     rather than chosen, so changing the measure or the rail moves the page with it. */
  --page-max: calc(var(--rail) + var(--gap) + var(--measure));
  --measure:  68ch;
  --measure-narrow: 44ch;   /* a note set beside something, not under it */
  --rail:     clamp(6rem, 11vw, 12rem);
  --rail-term: 12rem;       /* the term column of a definition list */
  --cell-min: 3.5rem;       /* the smallest a character cell may become */
  --gutter:   clamp(var(--space-4), 4vw, var(--space-16));
  --gap:      clamp(var(--space-6), 3.5vw, var(--space-12));

  /* A row's own horizontal room, and how far a list of rows is widened past the column. They are
     equal, which is what puts the text back on the column's edge while the rows and their rules
     run wider than it. Only a list in the column is widened; see the ROW rule. */
  --row-pad:   var(--space-3);
  --row-bleed: var(--space-3);

  /* The vertical rhythm of a section, and of a section that carries less. Written once: three
     different growth rates for the same idea is how a page stops feeling set. */
  --section-y:       clamp(var(--space-8), 5vw, var(--space-16));
  --section-y-tight: clamp(var(--space-8), 4vw, var(--space-12));

  --focus-width: 3px;

  /* Specimen heights. In rem, so a reader who enlarges their text enlarges
     the signs with it. */
  --sign-sm: 1.5rem;
  --sign-md: 2rem;
  --sign-lg: 2.5rem;
  --sign-xl: 3rem;

  color-scheme: light;
}

/* Breakpoints, and only these four. A media query in any stylesheet of this
   site uses one of them:

     40rem   640 px   large phone, small tablet
     48rem   768 px   tablet
     64rem  1024 px   laptop — the rail appears here
     80rem  1280 px   desktop
*/


/* ---------------------------------------------------------------------------
   Base
   --------------------------------------------------------------------------- */

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

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

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font-ui);
  font-size: var(--text-md);
  line-height: var(--leading-body);
  font-variant-numeric: tabular-nums;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

@media (min-width: 48rem) {
  body {
    font-size: var(--text-lg);
  }
}

img,
svg {
  max-width: 100%;
}

::selection {
  background: var(--accent);
  color: var(--on-accent);
}

a {
  color: var(--accent);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 0.2em;
}

a:hover {
  color: var(--accent-dark);
  text-decoration-thickness: 2px;
}

:focus-visible {
  outline: var(--focus-width) solid var(--focus);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}

/* Text for a screen reader only. Not display:none and not hidden: both remove it from the
   accessibility tree, which is the one place it has to stay. Clipped to nothing instead. */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.skip {
  position: absolute;
  left: -9999px;
}

.skip:focus {
  left: var(--space-3);
  top: var(--space-3);
  z-index: 60;
  padding: var(--space-3) var(--space-4);
  background: var(--ink);
  color: var(--paper);
  text-decoration: none;
}

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    transition-duration: 0.01ms !important;
    animation-duration: 0.01ms !important;
  }
}

/* ---------------------------------------------------------------------------
   Kinds

   Every element on this site is exactly one of five kinds, and its cursor, its
   selectability, its border and whether it answers the pointer all follow from
   that. Deciding the kind is the only decision; the treatment is not a matter
   of taste and not something to remember component by component.

     CONTROL   acts when it is used — a link, a button, a field, a switch.
               Pointer cursor, accent on hover, visible focus ring, never
               selectable.

     ROW       one item in a list of items — a table row, a specification line,
               a step, a claim, a character cell, a card. Lights up under the
               pointer, edge to edge and square, in line with the rule that
               separates it from the next one. Never inset: an inset widens the
               rule with it and leaves the line sticking out past the text.

     SURFACE   holds other things — a panel, a card grid, a figure, a code
               sample. Border and radius, and no reaction of its own: a surface
               is not a control, so it must not look like one.

     MARK      a picture, not text — a Moon specimen, a rail label, a state
               pill. Inert and unselectable, because selecting it copies
               nothing worth having.

     PROSE     running text. Selectable, and it does not react, which is
               information too: a paragraph is not hiding an action.
   --------------------------------------------------------------------------- */

/* CONTROL ------------------------------------------------------------------ */
/* Cursors say what a thing is: a hand for anything that acts, a beam for
   anything you type into, a barred circle for anything switched off. */
button,
summary,
select,
label:has(input[type="checkbox"]),
label:has(input[type="radio"]),
input[type="checkbox"],
input[type="radio"] {
  cursor: pointer;
}

textarea,
input[type="text"] {
  cursor: text;
}

[aria-disabled="true"] {
  cursor: not-allowed;
}

/* Every control says, before it is used, that it can be. One rule; a new
   control is added to the selector, not given a hover of its own. */
select,
textarea,
.seg label,
.code .copy {
  transition: border-color var(--duration) ease, background var(--duration) ease,
              color var(--duration) ease;
}

select:hover,
textarea:hover {
  border-color: var(--accent);
}

/* MARK --------------------------------------------------------------------
   A picture or a label, never text. Nothing here is worth putting on a
   clipboard, and a drag across a specimen should not paint half the grid. */
.label,
.px,
.who,
.pill,
.cta,
.bar nav,
.mn,
.ch__key {
  user-select: none;
}

.mn {
  -webkit-user-drag: none;
}

/* Scrollbars, including the window's own, so the bar down the side of the page belongs to the page
   rather than to the operating system. The track is the sunk paper and the thumb is a rule: the
   same two values the rest of the page is drawn with. */
html {
  scrollbar-width: thin;
  scrollbar-color: var(--rule-strong) var(--paper-sunk);
}

html::-webkit-scrollbar {
  width: var(--space-3);
  height: var(--space-3);
}

html::-webkit-scrollbar-track {
  background: var(--paper-sunk);
}

html::-webkit-scrollbar-thumb {
  background: var(--rule-strong);
  border: 3px solid var(--paper-sunk);
  border-radius: var(--radius-pill);
}

html::-webkit-scrollbar-thumb:hover {
  background: var(--ink-soft);
}
/* Scrollbars. Every box on this site that can scroll is listed here, so none of them is left with
   the operating system's own bar in the middle of the page. The track is the sunk paper and the
   thumb is a rule: the same two values the rest of the page is drawn with. */
.scroll {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}

.scroll,
.stage,
.rows,
.steps > div,
pre,
textarea {
  scrollbar-width: thin;
  scrollbar-color: var(--rule-strong) var(--paper-sunk);
}

.scroll::-webkit-scrollbar,
.stage::-webkit-scrollbar,
.rows::-webkit-scrollbar,
.steps > div::-webkit-scrollbar,
pre::-webkit-scrollbar,
textarea::-webkit-scrollbar {
  width: var(--space-2);
  height: var(--space-2);
}

.scroll::-webkit-scrollbar-track,
.stage::-webkit-scrollbar-track,
.rows::-webkit-scrollbar-track,
.steps > div::-webkit-scrollbar-track,
pre::-webkit-scrollbar-track,
textarea::-webkit-scrollbar-track {
  background: var(--paper-sunk);
  border-radius: var(--radius-pill);
}

.scroll::-webkit-scrollbar-thumb,
.stage::-webkit-scrollbar-thumb,
.rows::-webkit-scrollbar-thumb,
.steps > div::-webkit-scrollbar-thumb,
pre::-webkit-scrollbar-thumb,
textarea::-webkit-scrollbar-thumb {
  background: var(--rule-strong);
  border-radius: var(--radius-pill);
}

.scroll::-webkit-scrollbar-thumb:hover,
.stage::-webkit-scrollbar-thumb:hover,
.rows::-webkit-scrollbar-thumb:hover,
.steps > div::-webkit-scrollbar-thumb:hover,
pre::-webkit-scrollbar-thumb:hover,
textarea::-webkit-scrollbar-thumb:hover {
  background: var(--ink-soft);
}


/* ---------------------------------------------------------------------------
   Layout

   One grid and one measure. Every section sits on the same two columns — a
   rail for the label, a field for the content — and the measure governs
   headings, paragraphs, tables and grids alike, so nothing on the page ends on
   a line of its own.
   --------------------------------------------------------------------------- */

.wrap {
  width: min(100% - var(--gutter) * 2, var(--page-max));
  margin-inline: auto;
}

.grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-2) var(--gap);
}

/* Two columns, and the rail label sits on the heading's first baseline.
   Baseline alignment rather than a nudge in pixels: the headings are clamped to the viewport, so
   any fixed offset would be right at one width and wrong at every other. The browser measures. */
@media (min-width: 64rem) {
  .grid {
    grid-template-columns: var(--rail) minmax(0, 1fr);
    align-items: baseline;
  }
}

.field {
  min-width: 0;
  max-width: var(--measure);
}

.block {
  padding: var(--section-y) 0;
  border-top: var(--hairline) solid var(--rule);
}

/* An anchor stops below the bar, not under it. The height is measured by site.js and written into
   --bar-height, because a guess here is wrong the moment the bar wraps or the reader enlarges their
   text; the fallback is only for the first paint. */
[id] {
  scroll-margin-top: calc(var(--bar-height, var(--space-12)) + var(--space-4));
}

.prose p {
  margin: 0 0 var(--space-3);
}

.prose p:last-child {
  margin-bottom: 0;
}


/* ---------------------------------------------------------------------------
   Type
   --------------------------------------------------------------------------- */

h1,
h2,
h3 {
  text-wrap: pretty;
}

/* The page's own title, once, for every page. Without this rule an h1 fell back to the browser's
   default 2em, which is close enough to the h2 size that a page title and the section headings
   under it were the same size — and the two pages that did define their own h1 were then the odd
   ones out at twice that. */
h1 {
  margin: 0 0 var(--space-3);
  font-size: var(--text-h1);
  font-weight: 700;
  line-height: var(--leading-tight);
  letter-spacing: var(--track-tight);
  overflow-wrap: break-word;   /* a long title still has to fit an enlarged text size */
}

h2 {
  margin: 0 0 var(--space-4);
  font-size: var(--text-h2);
  font-weight: 700;
  line-height: var(--leading-tight);
  letter-spacing: var(--track-tight);
}

/* The first paragraph after a heading is set larger; the step down to the rest
   is what tells a reader where a section begins. The same size the opening
   sentence of the page uses, so a lead paragraph is one thing everywhere. */
.prose > h2 + p {
  font-size: var(--text-lead);
}

h3 {
  margin: var(--space-8) 0 var(--space-3);
  font-size: var(--text-xs);
  font-weight: 700;
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
  color: var(--ink-soft);
}

/* The rail number. Decoration, so it is hidden from the accessibility tree in the markup: a screen
   reader hears the section's name, not "zero one". */
.label .n {
  display: block;
  margin-bottom: var(--space-1);
  font-size: var(--text-xl);
  line-height: 1;
  letter-spacing: 0;
  color: var(--accent);
}

.label {
  margin: 0;
  font-size: var(--text-xs);
  font-weight: 700;
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
  color: var(--ink-soft);
}

.note {
  font-size: var(--text-sm);
  color: var(--ink-soft);
}

.px {
  font-size: var(--text-2xs);
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
  color: var(--ink-soft);
}


/* ---------------------------------------------------------------------------
   Specimens

   One rule for how a Moon specimen is sized, everywhere: height from the
   scale, width from the shaped aspect ratio the build wrote in, and never
   wider than the space it is given.
   --------------------------------------------------------------------------- */

/* The half-pixel stroke is stem darkening, and it is the difference between a drawn specimen and
   the same letters in the live font. A font rasteriser thickens a stem by a fraction of a pixel so
   its edges resolve; an SVG rasteriser draws the outline exactly as given, which at reading sizes
   comes out thinner and rougher than the text beside it. `non-scaling-stroke` keeps the amount at
   half a device pixel whatever size the specimen is drawn at, which is what a rasteriser does.
   Measured against the live webfont at 24, 32 and 64 px: 0.3 is too little, 0.8 too much. */
.mn {
  display: inline-block;
  height: var(--sign, var(--sign-sm));
  width: calc(var(--w, 4) * var(--sign, var(--sign-sm)));
  max-width: 100%;
  vertical-align: -0.22em;
  stroke: currentColor;
  stroke-width: 0.5;
  stroke-linejoin: round;
  vector-effect: non-scaling-stroke;
}

.mn--md { --sign: var(--sign-md); }
.mn--lg { --sign: var(--sign-lg); }
.mn--xl { --sign: var(--sign-xl); }

/* Where a specimen must not shrink — a comparison, a waterfall step — it keeps
   its size and the box around it scrolls instead. */
.mn--fixed {
  max-width: none;
  flex: 0 0 auto;
}

.live {
  font-family: "Moon Uniform", monospace;
  font-synthesis: none;
  line-height: 1.5;
  word-break: break-word;
}

.g1927 {
  -webkit-font-feature-settings: "calt" 0;
  font-feature-settings: "calt" 0;
}

.g1 {
  -webkit-font-feature-settings: "calt" 0, "clig" 0;
  font-feature-settings: "calt" 0, "clig" 0;
}

.ghalf {
  -webkit-font-feature-settings: "calt" 0, "clig" 0, "liga" 0;
  font-feature-settings: "calt" 0, "clig" 0, "liga" 0;
  font-variant-ligatures: none;
}


/* ---------------------------------------------------------------------------
   Components
   --------------------------------------------------------------------------- */

/* ---------------------------------------------------------------------------
   Hover

   One rule for the whole site, so there is no question about what answers the
   pointer and what does not.

   A ROW answers. Anything that is one item in a list of items — a table row, a
   specification line, a step, a card, a character cell, a claim — lifts its
   surface under the pointer, square and edge to edge, because an inset would
   widen the rule that separates one row from the next along with it.

   A CONTROL answers in the accent, because on this page the accent means
   something happens: links, buttons, the level switch, the select, the text
   area, the figure's border.

   EVERYTHING ELSE does not answer, and that is information too: running text
   is not hiding an action.
   --------------------------------------------------------------------------- */

/* ROW ---------------------------------------------------------------------
   One selector for every row on the site. A new list of things joins it by
   taking the class, and there is nothing else to decide.

   Edge to edge is only right if the row has an edge to spare. Without the
   padding below, the highlight stops at the first letter and sits against it.

   The room is taken back from the column by widening the LIST, not each row.
   A row that widened itself came out wider than the list holding it, so the
   rule under every row was longer than the rule the list draws above them, and
   two rules of different lengths in one stack is a broken edge. Widened once at
   the list, everything in it lines up by construction: the list's own rules,
   the rows' rules and the highlights are all the same width, and the padding
   inside each row puts the text back on the column's edge.

   A list that sits inside a box — the cards, the character grid — is not in the
   column and does not widen. Its rows keep the padding alone, and their inset is
   a value of --row-pad rather than a padding declared next to this one, so there
   is one way to change it. */
.hoverable,
.tbl tbody tr {
  transition: background var(--duration) ease;
}

.hoverable {
  padding-inline: var(--row-pad);
}

.claims,
.spec,
.steps {
  margin-inline: calc(var(--row-bleed) * -1);
}

.hoverable:hover,
.tbl tbody tr:hover {
  background: var(--paper-sunk);
}

/* SURFACE -----------------------------------------------------------------
   Border and radius, and no reaction: a surface is not a control, so it must
   not behave like one under the pointer. */
.panel {
  background: var(--paper-sunk);
  border: var(--hairline) solid var(--rule-strong);
  border-radius: var(--radius);
}

/* A small state marker. Solid means yes, filled outline means not yet. */
.pill {
  display: inline-block;
  padding: var(--space-1) var(--space-3);
  border: var(--hairline) solid var(--rule-strong);
  border-radius: var(--radius-pill);
  background: var(--paper);
  color: var(--ink-soft);
  font-size: var(--text-2xs);
  font-weight: 700;
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
  white-space: nowrap;
}

.pill--on {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--on-accent);
}

.pill--off {
  background: var(--paper-press);
  border-color: var(--rule-strong);
  color: var(--ink);
}

.cta {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-4);
  border: var(--hairline) solid var(--accent);
  border-radius: var(--radius-pill);
  background: var(--accent);
  color: var(--on-accent);
  font-size: var(--text-xs);
  font-weight: 700;
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
  text-decoration: none;
  white-space: nowrap;
}

.cta:hover {
  background: var(--accent-dark);
  border-color: var(--accent-dark);
  color: var(--on-accent);
  text-decoration: none;
}

/* Nothing to press yet. An outline on the sheet still reads as a button waiting to be used, so this
   one says otherwise in three ways at once: it is set into the paper rather than raised off it, its
   edge is broken, and the pointer refuses. The label keeps --ink-soft, 6.9:1 on the sunk paper, so
   "not available" is never spelled as "hard to read". */
.cta[aria-disabled="true"],
.cta[aria-disabled="true"]:hover {
  background: var(--paper-sunk);
  border-style: dashed;
  border-color: var(--rule-strong);
  color: var(--ink-soft);
  cursor: not-allowed;
}

/* An address into the repository, which is not public yet. It is not a link and must not look like
   one — no accent, no pointer — but it is not ordinary text either, so it keeps a link's broken
   underline to say that something belongs here and is not available. site.js turns these into real
   links as soon as the repository can be read. */
.held {
  color: var(--ink-soft);
  text-decoration: underline dotted var(--rule-strong);
  text-underline-offset: 0.2em;
  cursor: not-allowed;
}

/* The explanation behind a refused pointer.
 *
 * Anything that turns the cursor away has to say why, and the browser's own tooltip is the wrong
 * instrument: it takes a second to appear, it is drawn in the operating system's colours rather
 * than the page's, and it cannot be reached from the keyboard. This is one element, moved and
 * filled by site.js, so a component asks for an explanation by carrying data-tip and nothing else.
 *
 * It is placed in the viewport rather than beside the element, because several of the things that
 * need it sit inside surfaces that clip, and a tooltip drawn inside one of those would be cut in
 * half by the box it is explaining. */
.tip {
  position: fixed;
  z-index: 80;
  max-width: min(18rem, calc(100vw - var(--space-8)));
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-sm);
  background: var(--ink);
  color: var(--paper);
  font-size: var(--text-xs);
  line-height: 1.4;
  letter-spacing: 0;
  text-transform: none;
  text-align: left;
  box-shadow: 0 2px 8px rgba(31, 26, 19, 0.28);
  opacity: 0;
  transition: opacity var(--duration) ease;
  pointer-events: none;
}

.tip[data-shown] {
  opacity: 1;
}

/* A table. Specimens inside one shrink to fit rather than pushing a scrollbar
   under the row.

   A table row is a ROW, and it takes its room the same way: the cells carry the
   padding, and the table is pulled out by the same amount, so the first column
   starts on the column's edge and the highlight runs past it on both sides. */
.tbl {
  width: calc(100% + var(--row-bleed) * 2);
  margin-inline: calc(var(--row-bleed) * -1);
  border-collapse: collapse;
}

.tbl th,
.tbl td {
  padding: var(--space-3) var(--row-pad);
  border-bottom: var(--hairline) solid var(--rule);
  text-align: left;
  vertical-align: middle;
}

.tbl thead th {
  font-size: var(--text-2xs);
  font-weight: 700;
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
  color: var(--ink-soft);
}

.tbl .k {
  width: 1%;
  font-weight: 600;
  white-space: nowrap;
}

.tbl .n {
  width: 1%;
  text-align: right;
  white-space: nowrap;
  color: var(--ink-soft);
}

/* A column as wide as its own content, for a short value that must not wrap. `.k` and `.n` are
   this plus their own alignment; `.fit` is it on its own, for a column that is neither. */
.tbl .fit {
  width: 1%;
  white-space: nowrap;
}

.tbl .s {
  width: 99%;
}

.tbl .s .mn {
  height: auto;
}


/* A definition list read as a specification sheet. margin-block, not margin: the inline
   margin is the list's widening, set by the ROW rule above. */
.spec {
  margin-block: 0;
}

/* The line keeps its vertical padding and takes the inset from .hoverable. */
.spec > div {
  display: grid;
  gap: var(--space-1) var(--space-6);
  padding-block: var(--space-3);
  border-bottom: var(--hairline) solid var(--rule);
}

@media (min-width: 40rem) {
  .spec > div {
      grid-template-columns: var(--rail-term) minmax(0, 1fr);
  }
}

.spec dt {
  font-size: var(--text-xs);
  font-weight: 700;
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
  color: var(--ink-soft);
}

.spec dd {
  margin: 0;
  overflow-wrap: anywhere;
}


/* The character grid. Each cell copies its character, which is what a specimen
   is for. */
.chars {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(var(--cell-min), 1fr));
  border-top: var(--hairline) solid var(--rule);
  border-left: var(--hairline) solid var(--rule);
  border-radius: var(--radius);
  overflow: hidden;
}

/* A cell answers to the pointer, but it is not a control: there is nothing here to click, so the
   feedback is the surface changing, never the sign moving. A transform on the sign would also put
   it on its own compositing layer, and the browser would then scale a bitmap of it — which is what
   makes an outline look ragged. */
.ch {
  --row-pad: var(--space-1);  /* a cell is narrow, so it keeps the smallest inset on the scale */
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  aspect-ratio: 1 / 1;
  padding-block: var(--space-3);
  border-right: var(--hairline) solid var(--rule);
  border-bottom: var(--hairline) solid var(--rule);
  background: var(--paper);
  transition: background var(--duration) ease;
}

.ch .mn {
  --sign: var(--sign-md);
}

.ch__key {
  font-size: var(--text-xs);
  color: var(--ink-soft);
  transition: color var(--duration) ease;
}


.ch:hover .ch__key {
  color: var(--accent);
}

/* A code sample: the language it is in, the one control it needs, and highlighting done when the
   page was built.

   The colouring keeps the page's rule that hue means action. There is one hue, the green, and it
   marks the values a reader would change; everything structural is ink at a different weight, and
   what is only punctuation recedes. A rainbow would say five things where the page says one. */
.code {
  position: relative;
  margin: 0 0 var(--space-4);
}

.code pre {
  margin: 0;
  padding-top: var(--space-8);
}

.code::before {
  content: attr(data-language);
  position: absolute;
  top: var(--space-2);
  left: var(--space-4);
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  font-weight: 700;
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
  color: var(--ink-soft);
  pointer-events: none;
}

/* Pygments token classes, mapped onto the page's own two values.
   k  keyword or property   nt na nc  selector, tag, attribute
   s  string or value       mi  number      c  comment      p o  punctuation */
.code .k,
.code .nt,
.code .nc,
.code .nd {
  color: var(--ink);
  font-weight: 700;
}

.code .na {
  color: var(--ink);
}

.code .s,
.code .s1,
.code .s2,
.code .mi,
.code .m {
  color: var(--accent);
}

.code .c,
.code .c1,
.code .cm {
  color: var(--ink-soft);
  font-style: italic;
}

.code .p,
.code .o {
  color: var(--ink-soft);
}

.code .copy {
  position: absolute;
  top: var(--space-2);
  right: var(--space-2);
  padding: var(--space-1) var(--space-3);
  border: var(--hairline) solid var(--rule-strong);
  border-radius: var(--radius-pill);
  background: var(--paper);
  color: var(--ink-soft);
  font: inherit;
  font-size: var(--text-2xs);
  font-weight: 700;
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
  opacity: 0;
  transition: opacity var(--duration) ease;
}

.code:hover .copy,
.code .copy:focus-visible {
  opacity: 1;
}

.code .copy:hover {
  border-color: var(--accent);
  color: var(--accent);
}

.code .copy[data-copied] {
  opacity: 1;
  background: var(--accent);
  border-color: var(--accent);
  color: var(--on-accent);
}

pre {
  padding: var(--space-4);
  border: var(--hairline) solid var(--rule);
  border-radius: var(--radius);
  background: var(--paper-sunk);
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  line-height: var(--leading-body);
  overflow-x: auto;
}

code {
  font-family: var(--font-mono);
}

/* A figure drawn by this project's own tools, on the page's own colours. */
figure {
  margin: 0;
}

.figure {
  display: block;
  width: 100%;
  height: auto;
  padding: var(--space-2);
  border: var(--hairline) solid var(--rule-strong);
  border-radius: var(--radius);
  background: var(--paper-sunk);
  color: var(--ink);
}

figcaption {
  margin-top: var(--space-3);
  font-size: var(--text-sm);
  color: var(--ink-soft);
}



/* ---------------------------------------------------------------------------
   Bar and footer
   --------------------------------------------------------------------------- */

.bar {
  position: sticky;
  top: 0;
  z-index: 40;
  background: var(--paper-sunk);
  border-bottom: var(--hairline) solid var(--rule-strong);
}

/* The bar has to survive a reader who has enlarged their text: it wraps onto a second line rather
   than splitting a word down the middle or pushing itself off the side. */
.bar .row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2) var(--space-4);
  min-height: var(--space-12);
  padding-block: var(--space-2);
}

/* The sign is centred against the word, not sat on its baseline. `vertical-align` has no effect on
   a flex item, so the baseline the specimen carries is ignored here and the box would be aligned by
   its bottom edge — which is a fifth of its height below the sign's own baseline. */
.bar .mark {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin-right: auto;
  color: var(--ink);
  font-size: var(--text-base);
  font-weight: 700;
  text-decoration: none;
  white-space: nowrap;
}

.bar .mark:hover {
  color: var(--accent);
}

/* The specimen box keeps room for a descender the Moon M does not have, so its ink sits above the
   middle. The nudge puts the ink's centre on the word's, and it is a margin rather than a transform
   because a transform would give the outline its own layer to be rasterised in. */
.bar .mark .mn {
  --sign: 1.3em;
  margin-top: 0.18em;
  vertical-align: middle;
}

/* Every link, at every width. A menu folded behind a button is one more thing to find for a
   reader who cannot see it, and six short links wrap onto a second row perfectly well. */
.bar nav {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-6);
  font-size: var(--text-xs);
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
}

/* Narrow: the mark and the menu button share the first row, and the links — when the button opens
   them — take the next one as a column, so each is a full-width row rather than a word in a wrapped
   line. A row lights up edge to edge and square, as every other row on the site does. */
@media (max-width: 63.99rem) {
  .bar nav {
    flex-basis: 100%;
    flex-direction: column;
    gap: 0;
    margin-top: var(--space-2);
    border-top: var(--hairline) solid var(--rule);
  }

  .bar nav a {
    min-height: var(--space-8);
    padding-inline: var(--space-1);
    border-bottom: var(--hairline) solid var(--rule);
  }

  .bar nav a:last-child {
    border-bottom: 0;
  }
}

/* Hidden means hidden, whatever the display rules below say. An author's `display` beats the user
   agent's `[hidden] { display: none }` regardless of specificity, so anything given a display here
   has to say this too — the nav, and the button that folds it. */
.bar nav[hidden],
.menu[hidden] {
  display: none;
}

/* CONTROL: the menu button. It only exists on a narrow screen, and site.js reveals it there. */
.menu {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  min-height: var(--space-6);
  padding: var(--space-1) var(--space-2);
  border: var(--hairline) solid var(--rule-strong);
  border-radius: var(--radius-sm);
  background: var(--paper);
  color: var(--ink-soft);
  font: inherit;
  font-size: var(--text-xs);
  font-weight: 700;
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
}

.menu:hover {
  color: var(--accent);
  border-color: var(--accent);
}

/* Three bars, drawn rather than typed, so no icon font or image is needed for them. The middle one
   is the element's own box; the other two are its edges. */
.menu__bars {
  position: relative;
  width: 1em;
  height: var(--hairline);
  background: currentColor;
}

.menu__bars::before,
.menu__bars::after {
  content: "";
  position: absolute;
  left: 0;
  width: 100%;
  height: var(--hairline);
  background: currentColor;
}

.menu__bars::before { top: -0.32em; }
.menu__bars::after  { top:  0.32em; }

/* Open: the bars become a cross, so the button says which way it will go. */
.menu[aria-expanded="true"] .menu__bars {
  background: transparent;
}

.menu[aria-expanded="true"] .menu__bars::before {
  top: 0;
  transform: rotate(45deg);
}

.menu[aria-expanded="true"] .menu__bars::after {
  top: 0;
  transform: rotate(-45deg);
}

/* The link's text is 12px, which would leave a 19px target: under the 24px WCAG 2.2 asks for, and
   these are a list of links rather than words inside a sentence, so the exception does not apply.
   The padding is vertical only, so the row's rhythm is unchanged and the gap still separates them. */
.bar nav a {
  display: inline-flex;
  align-items: center;
  min-height: var(--space-6);
  color: var(--ink-soft);
  text-decoration: none;
  white-space: nowrap;
}

.bar nav a:hover {
  color: var(--accent);
  text-decoration: underline;
}

/* The page you are on. aria-current carries it to a screen reader; the underline and the colour
   carry it to everyone else, so the state is never colour alone. */
.bar nav a[aria-current="page"] {
  color: var(--accent);
  text-decoration: underline;
  text-decoration-thickness: 2px;
  text-underline-offset: 0.3em;
}

footer {
  padding: var(--section-y-tight) 0 var(--space-16);
  border-top: var(--hairline) solid var(--rule-strong);
  background: var(--paper-sunk);
  font-size: var(--text-sm);
  color: var(--ink-soft);
}

footer .cols {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-6) var(--space-8);
}

/* Two paragraphs and a short list of links. Three equal columns left the third one a quarter empty
   at the right, and a block that is full on the left and empty on the right reads as shifted rather
   than as a column that happens to be short. The links take only the room they need and sit against
   the right edge, so the colophon fills the measure it is set in. */
@media (min-width: 48rem) {
  footer .cols {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) max-content;
  }

  footer .cols > :last-child {
    text-align: right;
  }
}

footer p {
  margin: 0 0 var(--space-3);
}

footer a {
  color: var(--accent);
}


/* ===========================================================================
   Components that were on the single specimen page and are now shared
   ========================================================================== */

/* ---------------------------------------------------------------------------
   Three points, side by side
   --------------------------------------------------------------------------- */

/* The hairline between cards is drawn by each card, not by letting the container's colour show
   through the gaps: a row with fewer cards than columns would otherwise leave a block of rule
   colour where a card is missing. */
.cards {
  margin: 0;               /* a dl arrives with margins of its own; a div does not */
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--hairline);
  border: var(--hairline) solid var(--rule);
  border-radius: var(--radius);
  background: var(--paper);
  overflow: hidden;
}

/* Three cards or four, without a rule per count. Each card asks for 11rem, so a full-width field
   fits four; auto-fit then collapses the tracks a shorter row does not use, and three cards stretch
   to fill instead of leaving a gap. */
@media (min-width: 48rem) {
  .cards {
    grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
  }
}

.cards > div {
  --row-pad: var(--space-4);  /* a card is a room, not a line, so it is padded further in */
  padding-block: var(--space-4);
  background: var(--paper);
  box-shadow: 0 0 0 var(--hairline) var(--rule);
}


.cards h3 {
  margin: 0 0 var(--space-2);
  color: var(--accent);
}

.cards p {
  margin: 0;
  font-size: var(--text-base);
}


/* ---------------------------------------------------------------------------
   Stepped specimens

   The waterfall and the weights are the same object: a label, then one
   specimen at a set size.
   --------------------------------------------------------------------------- */

.steps {
  border-top: var(--hairline) solid var(--rule);
}

.steps > div {
  display: flex;
  align-items: baseline;
  gap: var(--space-4);
  padding-block: var(--space-4);
  border-bottom: var(--hairline) solid var(--rule);
  overflow-x: auto;
}


.steps .px {
  flex: 0 0 var(--space-16);
}

.steps--stacked > div {
  display: block;
}

.steps--stacked .px {
  display: block;
  margin-bottom: var(--space-2);
}


/* ---------------------------------------------------------------------------
   A list of plain statements, one per row
   --------------------------------------------------------------------------- */

/* ---------------------------------------------------------------------------
   The closing list
   --------------------------------------------------------------------------- */

/* margin-block, not margin: the inline margin is the list's widening, set by the ROW rule. */
.claims {
  margin-block: 0;
  padding: 0;
  list-style: none;
}

.claims li {
  padding-block: var(--space-4);
  border-bottom: var(--hairline) solid var(--rule);
}


.claims li:first-child {
  border-top: var(--hairline) solid var(--rule);
}

.claims strong {
  display: block;
}


/* ---------------------------------------------------------------------------
   The wanted list

   One block per material. A table of four prose columns cannot be made to fit
   a narrow screen without clipping or a sideways scroll, and these are short
   entries rather than rows of values.
   --------------------------------------------------------------------------- */

.wanted {
  margin: 0;
  padding: 0;
  list-style: none;
  counter-reset: wanted;
}

.wanted > li {
  padding-block: var(--space-4);
  border-bottom: var(--hairline) solid var(--rule);
}

.wanted > li:first-child {
  border-top: var(--hairline) solid var(--rule);
}

.wanted__marks {
  margin: 0;
  color: var(--accent);
  letter-spacing: 0.15em;
}

/* The circles are decoration; the word beside them is what a screen reader reads, and a sighted
   reader gets it too rather than having to decode a rating. */
.wanted__weight {
  margin-left: var(--space-2);
  color: var(--ink-soft);
  font-size: var(--text-2xs);
  font-weight: 700;
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
}

.wanted__name {
  margin: var(--space-2) 0 var(--space-1);
  font-size: var(--text-md);
  font-weight: 700;
  letter-spacing: normal;
  text-transform: none;
  color: var(--ink);
}

.wanted__settles {
  margin: 0;
}

.wanted__holder {
  margin: var(--space-2) 0 0;
  font-size: var(--text-sm);
  color: var(--ink-soft);
}

.wanted__key {
  font-weight: 700;
}

@media (min-width: 48rem) {
  .wanted > li {
    display: grid;
    grid-template-columns: 12rem minmax(0, 1fr);
    gap: var(--space-1) var(--space-6);
  }

  .wanted__marks,
  .wanted__holder {
    grid-column: 1;
    margin: 0;
  }

  .wanted__name,
  .wanted__settles {
    grid-column: 2;
  }

  .wanted__name {
    margin: 0 0 var(--space-1);
  }
}


/* ---------------------------------------------------------------------------
   Photographs

   The few third-party images this project stores. They are objects in a
   library, so they are given a mount and a caption rather than being bled to
   the edge of the page.
   --------------------------------------------------------------------------- */

.photo {
  margin: 0;
}

.photo img {
  display: block;
  width: 100%;
  height: auto;
  border: var(--hairline) solid var(--rule);
  border-radius: var(--radius-sm);
  background: var(--paper);
}

.photo figcaption {
  margin-top: var(--space-3);
  color: var(--ink-soft);
  font-size: var(--text-sm);
  line-height: var(--leading-body);
}

.photo .credit {
  display: block;
  margin-top: var(--space-1);
  color: var(--ink-soft);
}

.photos {
  display: grid;
  gap: var(--space-6);
}

@media (min-width: 48rem) {
  .photos {
    grid-template-columns: 1fr 1fr;
  }
}


/* A count and its label, inside a card. The number carries the weight; the label after it stays
   at reading size so the pair reads as one phrase. */
.cards--stats dt {
  font-size: var(--text-sm);
  color: var(--ink-soft);
}

.cards--stats .stat {
  display: block;
  color: var(--ink);
  font-size: var(--text-xl);
  font-variant-numeric: tabular-nums;
  letter-spacing: var(--track-tight);
}

.cards--stats dd {
  margin: var(--space-2) 0 0;
}


/* ---------------------------------------------------------------------------
   Utilities

   Last on purpose. A utility exists to override the component it is put on, and
   a component that zeroes its own margin is written after the utility in this
   file — so with equal specificity the component would win and the utility
   would silently do nothing. Keeping these at the end is what makes them work.
   --------------------------------------------------------------------------- */

/* Air between a block and whatever the prose above it ended with. */
.stack-top {
  margin-top: var(--space-6);
}


/* A card whose heading is a link: the whole card is the target, not the fifteen pixels of the
   heading itself. The pseudo-element covers the card, so the accessible name stays the heading's
   text and the markup keeps one link rather than wrapping the whole block in an anchor. */
.cards > div:has(h3 a) {
  position: relative;
}

.cards h3 a::after {
  content: "";
  position: absolute;
  inset: 0;
}

/* The ring has to be drawn round the card it now covers, not round the words. */
.cards h3 a:focus-visible {
  outline: none;
}

.cards > div:has(h3 a:focus-visible) {
  outline: var(--focus-width) solid var(--focus);
  outline-offset: calc(var(--focus-width) * -1);
  border-radius: var(--radius-sm);
}


/* ---------------------------------------------------------------------------
   Pairs — a term and its Moon sign

   A word and one or two Moon cells is a narrow thing. Set one to a line and the
   list runs down the page with the measure empty beside it, so the list flows
   into as many columns as the measure holds. Multi-column rather than grid,
   because a grid fills across and this has to fill down: an alphabetical list
   is read one column at a time.

   Each pair is a ROW and takes the ROW treatment — edge to edge, square, in
   line with the rule that separates it from the next.
   --------------------------------------------------------------------------- */

/* Two limits, and the browser takes the smaller: the macro's --cols, so a short list is not split
   into columns that cannot fill, and a column width, so a narrow screen drops to fewer. */
.pairs {
  columns: var(--cols, 1) 13rem;
  column-gap: var(--space-8);
  /* As wide as the columns it holds, not as wide as the measure. A two-entry list stretched over
     the full measure put its signs half a page from their words with nothing in between. */
  max-width: calc(var(--cols, 1) * 24rem);
  margin: 0;
}

.pairs__head {
  margin: 0 0 var(--space-2);
  font-size: var(--text-2xs);
  font-weight: 700;
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
  color: var(--ink-soft);
}

/* One phrase, not two column headings: the list flows into several columns, so a label positioned
   over the first one would be describing only that column. */
.pairs__head span::before {
  content: " · ";
}

/* The word takes a share of the column and the sign starts straight after it. Pushed apart to the
   two edges instead, a sign in a wide column ended up a long way from the word it belongs to, and
   the signs in one column did not line up with each other. */
.pair {
  --row-pad: var(--space-2);
  display: grid;
  /* A share of the column, but never more than 12rem: at 45% of a wide column the sign started so
     far from its word that the two stopped reading as a pair. */
  grid-template-columns: clamp(5rem, 45%, 12rem) minmax(0, 1fr);
  align-items: center;
  gap: var(--space-3);
  min-height: var(--space-8);
  padding-block: var(--space-2);
  border-bottom: var(--hairline) solid var(--rule);
  break-inside: avoid;          /* a pair split across two columns is unreadable */
}

/* No rule above the first pair: only the first column has a first child, so a top rule appears on
   one column and not the others, which reads as a column that started in the wrong place. */

.pair dt {
  font-weight: 600;
}

.pair dd {
  margin: 0;
  min-width: 0;
}
