/* Cheops — the site's design system.

   Two grounds, one site. The header and the board sit in shade; everything you
   read sits on stone. Scrolling out of the board is meant to feel like stepping
   out of shadow into daylight.

   The measurements are the board's own. A cell is 66px across and a level is
   66 * sqrt(3)/2 = 57.16px tall, so the page's vertical rhythm is the height of
   a level of the board rather than an arbitrary 8px grid. The faint horizontal
   lines behind the board are the board's levels continued out to the edge of
   the screen.

   Green and orange appear on the board and nowhere else. They are the two
   houses, and they only mean something if nothing else competes with them. */

:root {
  /* Grounds. Cool limestone rather than warm cream: the board's cells are warm
     greys, and a warm page underneath them makes the whole thing muddy. */
  --stone:       #E4E2DB;
  --stone-sunk:  #D9D6CD;
  --shade:       #191D1C;
  --shade-lift:  #212625;

  /* Ink on stone, chalk on shade. */
  --ink:         #23262A;
  --ink-mute:    #6B6F6A;
  --chalk:       #E7E6E0;
  --chalk-mute:  #949890;

  --rule:        #C7C4B9;
  --rule-shade:  #2C3231;

  /* The two houses. Deeper and redder than the pair every measurement in the
     project was run with (#1D7A4F / #C4571A) -- Atreides green toward pine,
     Harkonnen orange toward rust. `harness/cvdcheck.py` prices the change:
     worst-case difference across the three dichromacies falls slightly, 11.7 to
     10.1, while the white glyph's contrast against them rises from 5.3:1 and
     4.4:1 to 7.6:1 and 6.3:1. Neither pair is safe on its own, which is what
     the colourblind mode in board.css is for.

     They appear on the board, and on one other thing: the underline under the
     page you are on. */
  --atreides:    #16603D;
  --harkonnen:   #AA3A18;
  /* The same two, lifted. A key swatch sits directly on the shade instead of
     inside a pale-rimmed piece, and the house colours are too dark to read
     there. */
  --atreides-key:  #3E8F66;
  --harkonnen-key: #D2653A;

  /* The board's geometry, used as the page's measure. */
  --cell:  66px;
  --level: 57.16px;

  --serif: Spectral, "Iowan Old Style", Palatino, Georgia, serif;
  --sans:  Archivo, ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
}

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

/* Read by a screen reader and by a search engine, seen by nobody. The board is
   the top of this page and a board has no heading, so without this the first
   heading in the document was "Moves" and nothing said what the page was.
   `clip-path` rather than `display:none`, which removes it from both. */
.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* The `hidden` attribute is a UA rule of the lowest possible weight, so any
   `display` we set -- `.row { display: flex }`, say -- silently beats it and
   the element stays on screen. Every control the board hides goes through here. */
[hidden] { display: none !important; }

html {
  -webkit-text-size-adjust: 100%;
  /* The bar is sticky, so anything the browser scrolls to -- a link to an
     anchor, a focused control, a form field jumped to on submit -- otherwise
     arrives underneath it. This is the bar's height plus a little air. */
  scroll-padding-top: 5.5rem;
}

body {
  margin: 0;
  background: var(--stone);
  color: var(--ink);
  font: 400 1.0625rem/1.65 var(--serif);
  font-variant-numeric: oldstyle-num;
  text-rendering: optimizeLegibility;
}

/* ---------------------------------------------------------------- shade --- */

.shade {
  background: var(--shade);
  color: var(--chalk);
}

/* Lighting was tried here on 8 September 2026 and taken out again: a soft
   ellipse of light through the band and a shadow under the board's base. The
   shadow read as a smudge rather than as a board standing on something, and the
   wash was either invisible or, once it was strong enough to see, a patch. The
   band stays flat. */
/* There were faint horizontal lines here: the board's own levels continued out
   to the edge of the screen. The idea does not survive contact with the board,
   because the board is resizable — a level is 57.16px only while a cell is
   66px, so the lines drift out of register the moment anybody moves the size
   slider, and a rule that nearly lines up with something reads as a printing
   fault rather than as structure. The level still sets the page's vertical
   rhythm, where nothing has to align with anything. */

/* ------------------------------------------------------------------ nav --- */

/* The bar stays with you. It is a full-width element so that it has something
   to paint across when it sticks; the nav inside it keeps the page's measure. */
.topbar {
  position: sticky;
  top: 0;
  z-index: 20;
  background: var(--shade);
  border-bottom: 1px solid transparent;
}
.topbar--stone { background: var(--stone); border-bottom-color: var(--rule); }

.nav {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 2rem;
  max-width: 82rem;
  margin: 0 auto;
  padding: 1.6rem 1.5rem 1.4rem;
}

/* `.nav a.wordmark`, not `.wordmark`: the bar styles its links with `.nav a`,
   which is more specific than a bare class and was therefore painting the
   wordmark in the muted link colour whatever this rule said. It has been the
   wrong colour since the day the bar was built. */
.nav a.wordmark {
  font-family: var(--sans);
  font-weight: 300;
  font-stretch: 125%;
  font-size: 1.62rem;
  letter-spacing: .34em;
  text-indent: .34em;         /* tracking adds a trailing gap; balance it */
  text-transform: uppercase;
  color: #fff;
  text-decoration: none;
  white-space: nowrap;
}

.nav ul {
  display: flex;
  /* Six links at a phone's width, and a reader who has turned their text up.
     Without this the list is one unbreakable line and runs off the side. */
  flex-wrap: wrap;
  gap: 1.7rem;
  margin: 0;
  padding: 0;
  list-style: none;
  font-family: var(--sans);
  font-size: 1rem;
  font-weight: 400;
}
.nav a {
  color: var(--chalk-mute);
  text-decoration: none;
  padding-bottom: .2rem;
  border-bottom: 1px solid transparent;
}
.nav a:hover { color: var(--chalk); }
.nav a[aria-current] { color: var(--chalk); border-bottom-color: var(--harkonnen); }


/* ----------------------------------------------------------------- type --- */

h1, h2, h3 { font-weight: 600; line-height: 1.15; margin: 0; }

h1.h1--small { font-size: clamp(1.6rem, 2.6vw, 2.1rem); }

h1 {
  font-size: clamp(2rem, 4.6vw, 3.1rem);
  letter-spacing: -.018em;
  margin-bottom: .6rem;
}
h2 {
  font-size: 1.55rem;
  letter-spacing: -.012em;
  margin: calc(var(--level) * .9) 0 .7rem;
}
h3 { font-size: 1.12rem; margin: 2rem 0 .4rem; }

p { margin: 0 0 1.05em; }
a { color: inherit; text-decoration-thickness: 1px; text-underline-offset: .18em; }

.lede {
  font-size: 1.28rem;
  line-height: 1.5;
  color: var(--ink);
}
.quiet { color: var(--ink-mute); }

/* Reading measure. Serif takes a longer line than sans, but not much longer. */
.prose { max-width: 36rem; }

/* A section that needs more room than prose does: the interactive board sits
   in one. */
.wide { max-width: 62rem; margin-inline: auto; }
.wide > h2 { margin-top: 0; }


/* ------------------------------------------------------------- the table --- */

.table {
  max-width: 82rem;
  margin: 0 auto;
  padding: calc(var(--level) * .55) 1.5rem calc(var(--level) * 1.2);
}


/* The same quotation, set on stone as the opening of the About page. It is the
   whole of what was borrowed, so it is quoted once, where the page around it
   says what came from it. */
.pull { margin: 0 0 1.6rem; padding-left: 1.1rem; border-left: 2px solid var(--harkonnen); }
.pull p { font-style: italic; font-size: 1.15rem; line-height: 1.5; margin: 0 0 .5rem; }
.pull cite { font: 400 .8125rem/1.4 var(--sans); font-style: normal; color: var(--ink-mute); }
.pull cite em { font-style: italic; }

/* The newsletter form, until Kit replaces it. */
.signup label { color: var(--ink-mute); }
.signup input[type=email] {
  flex: 1;
  min-width: 0;
  font: inherit;
  font-family: var(--sans);
  font-size: .95rem;
  padding: .5rem .6rem;
  color: var(--ink);
  background: var(--stone-sunk);
  border: 1px solid var(--rule);
  border-radius: 2px;
}
.signup ::placeholder { color: var(--ink-mute); opacity: 1; }
.signup .primary { background: var(--ink); color: var(--stone); border-color: var(--ink); }
/* The generic `button.primary:hover` goes to #fff, which is right on the shade
   panel and wrong here: this button is dark ink on a pale band, so hovering it
   turned it white with pale text on it -- invisible. Nobody had seen it because
   the placeholder form is disabled. It hovers to the house green instead, which
   is the site's one accent and is already the underline under the page you
   are on. */
.signup .primary:hover:not(:disabled) {
  background: var(--atreides);
  border-color: var(--atreides);
  color: var(--stone);
}
.signup :disabled { opacity: .55; cursor: not-allowed; }

/* Three columns, aligned to the TOP of the board box. The board is a triangle
   inside a rectangle, so its upper corners are empty; starting the flanking
   columns at the same height puts them beside that emptiness instead of
   leaving two holes in the layout. */
.table__grid {
  display: grid;
  grid-template-columns: minmax(11rem, 1fr) auto minmax(13.5rem, 1fr);
  gap: 0 2.2rem;
  align-items: start;
  justify-content: center;
}

/* `min-width: 0` because a grid item will not shrink below its own content
   otherwise, and at the top of the size slider the board is wider than the
   three columns have to give it on any screen. */
.board-wrap { grid-column: 2; min-width: 0; }
#board svg { display: block; height: auto; max-width: 100%; margin-inline: auto; }

/* Panels in the shade are groups of type, not boxes. A rule above each one is
   enough to separate them and costs nothing in weight. */
.wing { font-family: var(--sans); font-size: .875rem; }
.wing--left  { grid-column: 1; }
.wing--right { grid-column: 3; }

.wing h2 {
  font: 400 .8125rem/1.3 var(--sans);
  letter-spacing: .04em;
  color: var(--chalk-mute);
  margin: 0 0 .55rem;
}

.group { padding: 0 0 1.05rem; margin: 0 0 1.05rem; border-bottom: 1px solid var(--rule-shade); }
.group:last-child { border-bottom: 0; margin-bottom: 0; }

/* The one lifted block on the page. What is happening and what to do about it
   belong together and above everything else; the settings and the key are
   reference and sit back. They used to be interleaved, and the column read as
   a pile of controls rather than as somewhere to act. */
.group--now {
  background: var(--shade-lift);
  border: 1px solid var(--rule-shade);
  border-radius: 3px;
  padding: .95rem 1rem;
  margin-bottom: 1.4rem;
}
.group--now .row { margin-top: .85rem; }
.group--now label { margin-top: 1rem; }

/* Section headings inside the wing, so the three reference blocks are labelled
   the same way and read as siblings. */
.wing h2 + label { margin-top: 0; }

.status { font-size: 1.18rem; font-weight: 500; margin: 0 0 .25rem; color: var(--chalk); }
.detail { font-size: .85rem; color: var(--chalk-mute); margin: 0; }

label { display: block; font-size: .8125rem; color: var(--chalk-mute); margin: 0 0 .3rem; }
.row { display: flex; gap: .5rem; align-items: center; }
.row + label { margin-top: .8rem; }

select, button, input[type=range] { font: inherit; font-family: var(--sans); }
select:disabled { opacity: .55; cursor: not-allowed; }

/* The range slider defaults to the browser's blue, which is the one colour on
   the page nobody chose. */
input[type=range] { accent-color: var(--chalk-mute); }
input[type=range] { height: 1.1rem; }

select {
  flex: 1;
  min-width: 0;
  padding: .42rem .55rem;
  color: var(--chalk);
  background: var(--shade-lift);
  border: 1px solid var(--rule-shade);
  border-radius: 2px;
}
/* One focus ring for every control on the site. The textarea was missing from
   this list and fell back to the browser's own, which is a heavy black box in
   the middle of a stone panel. */
select:focus-visible, button:focus-visible, input:focus-visible,
textarea:focus-visible, a:focus-visible {
  outline: 2px solid var(--harkonnen);
  outline-offset: 2px;
}

button {
  padding: .42rem .8rem;
  color: var(--chalk);
  background: transparent;
  border: 1px solid var(--rule-shade);
  border-radius: 2px;
  cursor: pointer;
}
button:hover { border-color: var(--chalk-mute); }
button.primary { background: var(--chalk); color: var(--shade); border-color: var(--chalk); }
button.primary:hover { background: #fff; }
button.wide { flex: 1; padding-block: .55rem; }
button:disabled { opacity: .4; cursor: not-allowed; }
/* Three buttons across a narrow column. Equal widths broke "Take back" and
   "New game" across two lines each; sizing them to their words and letting the
   row wrap as a whole keeps them readable at any column width. */
#gamerow { gap: .4rem; flex-wrap: wrap; }
#gamerow button { flex: 1 1 auto; white-space: nowrap; padding-inline: .6rem; }

/* A checkbox and its words are one target, so they are one element. */
label.check {
  display: flex;
  gap: .55rem;
  align-items: baseline;
  margin: 0 0 .45rem;
  cursor: pointer;
  line-height: 1.4;
}
label.check:last-child { margin-bottom: 0; }
label.check input { margin: 0; flex: none; }
.row + label.check { margin-top: .9rem; }

/* The box is drawn here rather than left to the browser. Native, an unticked
   box is bright white -- the lightest thing on a dark panel, and louder than
   the ticked ones beside it. Both states now take the field and the rule of the
   selects and buttons in the same column, and the tick is the only difference
   between them. Each ground sets the four colours; the shade is the one that
   was wrong, and the feedback form on stone gets the same treatment. */
:root {
  --box-field: var(--stone-sunk);
  --box-rule:  var(--rule);
  --box-on:    var(--ink);
  --box-tick:  var(--stone);
}
.wing {
  --box-field: var(--shade-lift);
  --box-rule:  var(--rule-shade);
  --box-on:    var(--chalk-mute);
  --box-tick:  var(--shade);
}

label.check input[type=checkbox] {
  appearance: none;
  -webkit-appearance: none;
  position: relative;
  width: .95rem;
  height: .95rem;
  border: 1px solid var(--box-rule);
  border-radius: 2px;
  background: var(--box-field);
  cursor: pointer;
}
label.check input[type=checkbox]:checked {
  background: var(--box-on);
  border-color: var(--box-on);
}
/* The tick is two borders of a small box, turned. */
label.check input[type=checkbox]:checked::after {
  content: "";
  position: absolute;
  left: .3rem;
  top: .16rem;
  width: .22rem;
  height: .46rem;
  border: solid var(--box-tick);
  border-width: 0 2px 2px 0;
  transform: rotate(45deg);
}

.moves {
  font: .8rem/1.7 var(--sans);
  font-variant-numeric: tabular-nums;
  color: var(--chalk-mute);
  white-space: pre;
  max-height: calc(var(--level) * 6);
  overflow-y: auto;
  overflow-x: hidden;
  /* Scrolls, but without the rail: a scrollbar appearing beside the moves put a
     bright vertical bar down the middle of the shade the moment the game got
     past six moves. The list is short, adjacent to the board, and nobody needs
     a handle to drag. */
  scrollbar-width: none;
}
.moves::-webkit-scrollbar { width: 0; height: 0; }

.legend { font-size: .8rem; color: var(--chalk-mute); line-height: 1.5; }
.legend svg { display: inline-block; vertical-align: -3px; }
/* The key is generated as one run of spans with no whitespace between them, so
   there is nothing for the line to break at and it ran off the side of a phone.
   A wrapping flex box breaks between items regardless of what separates them. */
#key { display: flex; flex-wrap: wrap; gap: .1rem .7rem; margin-top: .3rem; }
#key span { white-space: nowrap; }

/* ---------------------------------------------------------------- stone --- */

main { max-width: 82rem; margin: 0 auto; padding: 0 1.5rem; }

section.band { padding: calc(var(--level) * 1.4) 0; border-bottom: 1px solid var(--rule); }
section.band:last-of-type { border-bottom: 0; }

/* Two columns where a heading wants to sit beside its text rather than above
   it — it keeps the reading measure short without stranding the heading. */
/* Centred rather than left-aligned. The board needs the full 82rem and the
   reading column must not exceed about 36rem, so left-aligning the text left a
   third of a wide screen empty on one side only, which reads as a page that has
   lost something rather than as a margin. */
.split {
  display: grid;
  grid-template-columns: minmax(0, 15rem) minmax(0, 36rem);
  gap: 0 3.5rem;
  max-width: 54.5rem;
  margin-inline: auto;
}
.split > h2 { margin-top: .3rem; }

.terms { margin: 0; }
.terms dt {
  font-weight: 600;
  margin: 1.6rem 0 .25rem;
  padding-top: 1.6rem;
  border-top: 1px solid var(--rule);
}
.terms dt:first-child { margin-top: 0; padding-top: 0; border-top: 0; }
.terms dd { margin: 0; max-width: 36rem; }

/* --------------------------------------------------------------- footer --- */

footer {
  background: var(--stone-sunk);
  border-top: 1px solid var(--rule);
  margin-top: calc(var(--level) * 1.4);
  font-size: .875rem;
  color: var(--ink-mute);
}
footer .inner {
  max-width: 82rem;
  margin: 0 auto;
  padding: calc(var(--level) * .7) 1.5rem;
  display: flex;
  justify-content: center;
}
footer ul {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: .9rem 1.8rem;
  margin: 0;
  padding: 0;
  list-style: none;
  font-family: var(--sans);
}
footer a { text-decoration: none; }
footer a:hover { text-decoration: underline; text-underline-offset: .2em; }

/* ------------------------------------------------------------ responsive --- */

/* Three columns need 1148px: the board is 638 wide at the default cell, the
   wings will not go below 11rem and 13.5rem, there are two 2.2rem gaps between
   them and the page's own 1.5rem of padding on each side. The breakpoint was
   63rem, which handed three columns to a 1024px screen -- an iPad Pro held
   upright -- and left the board 514px to stand in. 74rem is 1148 plus room for
   a classic scrollbar, which a media query counts as part of the viewport and
   the layout does not, so the board is never squeezed to make room for a
   settings column. */
@media (max-width: 74rem) {
  .table__grid { grid-template-columns: minmax(0, 1fr); gap: calc(var(--level) * .6) 0; }
  .board-wrap, .wing--left, .wing--right { grid-column: 1; }
  /* One column, so the board no longer sizes its own column; shrink-wrapping it
     keeps it under the middle of the page with the wings rather than pinned to
     the left of a full-width row. */
  .board-wrap { order: 1; width: fit-content; max-width: 100%; margin-inline: auto; }
  .wing--right { order: 2; }
  .wing--left  { order: 3; }
  .wing { max-width: 32rem; margin-inline: auto; width: 100%; }
}

/* The reading columns have their own measure and hold up far below that. */
@media (max-width: 63rem) {
  .split { grid-template-columns: minmax(0, 1fr); gap: 0; }
  .fig--trio { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 40rem) {
  body { font-size: 1rem; }
  /* The board is the one thing worth the full width of a phone: every cell is a
     tap target and the triangles interleave, so a near-miss picks the
     neighbour. It takes the screen edge to edge and the page's padding is
     cancelled for it alone. */
  .board-wrap { width: auto; margin-inline: -1rem; }
  #board svg { width: 100%; }
  .nav { padding: 1.1rem 1rem; flex-wrap: wrap; gap: .8rem 1.2rem; }
  .nav ul { gap: 1.1rem; font-size: .8125rem; }
  .table, main, footer .inner { padding-inline: 1rem; }
  .nav a.wordmark { font-size: 1rem; letter-spacing: .3em; text-indent: .3em; }
  /* Diagrams stop being diagrams below a certain size, so on a phone they get
     the full width one at a time rather than sharing a row. */
  .fig--pair, .fig--trio { grid-template-columns: minmax(0, 1fr); }
  .piece { grid-template-columns: minmax(0, 1fr); gap: 1rem 0; }
  .piece svg { max-width: 18rem; }
  /* The consent strip covers the bottom of the board while somebody decides, so
     on a phone it wants to be as short as it can be: tighter, and the two
     answers share a full-width row where they are easy to hit. */
  .consent { padding: .7rem 1rem; gap: .55rem 1rem; }
  .consent p { font-size: .85rem; }
  .consent__row { width: 100%; }
  .consent__row button { flex: 1; padding-block: .55rem; }
}

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

/* --------------------------------------------------- the interactive board --- */

/* Contents down one side, the subject on the other. The list is the agenda: it
   says how much there is, where you are in it, and lets you jump. */
/* Explainer, board, contents. The board is in the middle because it is the
   subject; the words sit where reading starts and the list where you go next. */
.ib {
  display: grid;
  grid-template-columns: minmax(0, 17rem) minmax(0, 1fr) minmax(0, 11rem);
  gap: 0 2.2rem;
  align-items: start;
}
/* Heading and one line, stacked and left, then straight into the board. There
   was a rule between them and the thing they introduce, which put a division
   where the reading does not stop. */
.ib__head { margin: 0 0 calc(var(--level) * .55); }
.ib__head h1 { margin-bottom: .25rem; }
.ib__head .lede { color: var(--ink-mute); margin: 0; max-width: 34rem; }

.ib__text h3 { margin: 0 0 .5rem; font-size: 1.25rem; outline: none; }

/* Which corner the Pawn belongs to. Only the Pawn has one, because only the
   Pawn behaves differently depending on which army it is in. */
.ib__side { display: flex; gap: .3rem; margin-top: 1rem; }
.ib__side button {
  font-family: var(--sans);
  font-size: .82rem;
  padding: .28rem .7rem;
  border: 1px solid var(--rule);
  border-radius: 2px;
  background: none;
  color: var(--ink-mute);
  cursor: pointer;
}
.ib__side button[aria-current="true"] {
  background: var(--ink);
  color: var(--stone);
  border-color: var(--ink);
}
.ib__text p { color: var(--ink-mute); font-size: .95rem; }

.ib__menu { display: flex; flex-direction: column; align-items: stretch; }
.ib__group {
  font-family: var(--sans);
  font-size: .72rem;
  color: var(--ink-mute);
  margin: .7rem 0 .15rem;
  padding-left: .6rem;
}
.ib__group:first-child { margin-top: 0; }
.ib__menu button {
  font-family: var(--sans);
  font-size: .82rem;
  line-height: 1.25;
  text-align: left;
  padding: .16rem .6rem;
  border: 0;
  border-left: 2px solid var(--rule);
  border-radius: 0;
  background: none;
  color: var(--ink-mute);
  cursor: pointer;
}
.ib__menu button:hover { color: var(--ink); background: var(--stone-sunk); }
.ib__menu button[aria-current="true"] {
  color: var(--ink);
  border-left-color: var(--harkonnen);
  background: var(--stone-sunk);
}

.ib__board { margin: 0 0 1rem; }
.ib__board svg { display: block; width: 100%; height: auto; }
polygon.ic { cursor: pointer; }
polygon.ic:hover { stroke: #3A7CA5; stroke-width: 2.5; }
polygon.ic.on { stroke: #D85A30; stroke-width: 3; }
/* A cell this topic's piece can never occupy. It keeps its colour -- greying
   out half the board made one topic look like a different board -- and simply
   does not respond. */
polygon.ic.barred { cursor: not-allowed; }
polygon.ic.barred:hover { stroke: #8F8878; stroke-width: .6; }
/* A topic that is a picture rather than something to try. */
polygon.ic.still { cursor: default; }
polygon.ic.still:hover { stroke: #8F8878; stroke-width: .6; }

.ib__foot { display: flex; align-items: center; gap: .8rem; flex-wrap: wrap; }
.ib__foot button {
  font-family: var(--sans);
  font-size: .9rem;
  padding: .42rem .9rem;
  border: 1px solid var(--rule);
  border-radius: 2px;
  background: none;
  color: var(--ink);
  cursor: pointer;
}
.ib__foot button:hover:not(:disabled) { border-color: var(--ink-mute); }
.ib__foot button:disabled { opacity: .4; cursor: not-allowed; }
.ib__foot button.primary { background: var(--ink); color: var(--stone); border-color: var(--ink); }
.ib__cell {
  font-family: var(--sans);
  font-size: .9rem;
  color: var(--ink-mute);
  font-variant-numeric: tabular-nums;
}
.ib__step { margin-left: auto; font-family: var(--sans); font-size: .82rem; color: var(--ink-mute); }

@media (max-width: 58rem) {
  /* Three columns will not fit, and the board is the point -- so it goes first
     and full width, with the words above it and the list as a strip. */
  .ib { grid-template-columns: minmax(0, 1fr); gap: 1.2rem 0; }
  .ib__text { order: 1; }
  .ib__menu { order: 2; }
  .ib__stage { order: 3; }
  .ib__menu { flex-direction: row; flex-wrap: wrap; gap: .3rem; align-items: baseline; }
  .ib__group { width: 100%; margin: .5rem 0 0; padding-left: 0; }
  .ib__menu button { border-left: 0; border-bottom: 2px solid var(--rule); padding: .3rem .5rem; }
  .ib__menu button[aria-current="true"] { border-bottom-color: var(--harkonnen); }
}

/* ------------------------------------------------------------- feedback --- */

/* The invitation is the first thing read, so it is set in the body colour
   rather than the muted one used for asides. */
.fb__intro {
  margin: 0 2rem .9rem 0;
  color: var(--ink);
  max-width: 32rem;
}

.fb label { display: block; color: var(--ink-mute); margin: 1rem 0 .3rem; }
.fb label:first-child { margin-top: 0; }
.fb textarea { margin-top: 0; }

/* Name and email side by side: two short optional fields stacked make the box
   taller than the thing it is actually asking for. */
.fb__two { display: grid; grid-template-columns: 1fr 1fr; gap: 0 1rem; }
.fb__two input { width: 100%; }
@media (max-width: 34rem) { .fb__two { grid-template-columns: 1fr; } }
.fb .opt { font-style: italic; }
.fb textarea, .fb input[type=email], .fb input[name=name] {
  width: 100%;
  font: inherit;
  font-family: var(--sans);
  font-size: .95rem;
  padding: .55rem .65rem;
  color: var(--ink);
  background: var(--stone-sunk);
  border: 1px solid var(--rule);
  border-radius: 2px;
}
.fb textarea { resize: vertical; min-height: 6rem; }
.fb .check { color: var(--ink); margin: 1rem 0 1.2rem; }
.fb .primary { background: var(--ink); color: var(--stone); border-color: var(--ink); }
.fb .row { align-items: center; gap: .9rem; }
.fb__note { font-family: var(--sans); font-size: .9rem; color: var(--ink-mute); }
.fb__note.bad { color: var(--harkonnen); }

/* The honeypot is removed from sight and from the tab order without using
   `display:none`, which some bots check for and skip. */
.hp { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }

/* ---------------------------------------------------------------- modal --- */

dialog {
  padding: 0;
  border: 1px solid var(--rule);
  border-radius: 3px;
  background: var(--stone);
  color: var(--ink);
  max-width: min(34rem, calc(100vw - 2rem));
  width: 100%;
  box-shadow: 0 24px 60px -20px rgba(0, 0, 0, .5);
}
dialog::backdrop { background: rgba(12, 15, 14, .62); }
.modal { padding: 1.5rem; position: relative; }
/* No heading over the form: the first line of it says what the box is for, so a
   title above that would say it twice. The dialog keeps an accessible name
   through aria-label, which is for screen readers rather than for the page. */
.modal__x {
  position: absolute;
  top: .7rem;
  right: .8rem;
  font: 1.5rem/1 var(--sans);
  padding: 0 .3rem;
  border: 0;
  border-radius: 2px;
  background: none;
  color: var(--ink-mute);
  cursor: pointer;
}
.modal__x:hover { color: var(--ink); }
.modal .quiet { font-size: .95rem; }
.modal .lede { margin-bottom: .4rem; }
.modal .row { margin-top: 1.2rem; }

@media (max-width: 40rem) {
  .modal { padding: 1.1rem 1.1rem 1.3rem; }
}

/* -------------------------------------------------------------- consent --- */

/* Along the bottom, over whatever is there. It asks before anything is stored,
   so it has to be answerable without going anywhere, and it must not cover the
   board while somebody is deciding — hence a strip rather than a curtain. */
.consent {
  position: fixed;
  inset: auto 0 0 0;
  z-index: 40;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: .8rem 1.6rem;
  padding: .9rem 1.5rem;
  background: var(--shade);
  color: var(--chalk);
  border-top: 1px solid var(--rule-shade);
}
.consent p {
  margin: 0;
  /* 32rem, not 46, so the sentence fills two lines rather than one long one
     and the buttons sit where the reading stops. 139 characters at .92rem in
     Archivo is ~1020px, so half of it is ~32rem. Computed, not seen: check it
     in a browser before trusting it. */
  max-width: 32rem;
  font-size: .92rem;
  line-height: 1.5;
  color: var(--chalk-mute);
}
.consent a { color: var(--chalk); }
.consent__row { display: flex; gap: .5rem; flex: none; }
.consent button {
  font-family: var(--sans);
  font-size: .9rem;
  padding: .42rem .9rem;
  border: 1px solid var(--rule-shade);
  border-radius: 2px;
  background: transparent;
  color: var(--chalk);
  cursor: pointer;
}
.consent button:hover { border-color: var(--chalk-mute); }
.consent button.primary { background: var(--chalk); color: var(--shade); border-color: var(--chalk); }

/* The side key inside the action box, where it is read. Centred, because it is
   a statement about the game rather than another labelled setting.

   The swatch is the house colour behind a pale rim -- exactly how a piece is
   drawn on the board -- rather than a lightened version of it. Lifting the
   colours made them readable on the shade and made them not the colours the
   player is looking at, which is the one thing this has to get right. */
.group--now .sides {
  margin: .8rem 0 0;
  font-size: .9rem;
  text-align: center;
  color: var(--chalk);
}
.sides b, .sides i { font-weight: 400; font-style: normal; color: var(--chalk); }
.sides b { margin-right: 1.1rem; }
.sides b::before, .sides i::before {
  content: '';
  display: inline-block;
  width: .8em;
  height: .8em;
  margin-right: .45em;
  vertical-align: -.05em;
  border: 1px solid rgba(255, 255, 255, .88);
}
.sides b::before { background: var(--atreides); }
.sides i::before { background: var(--harkonnen); }
