/* Film Groups — site styles. Math-paper aesthetic, light-first. */

:root {
  --bg: #faf9f6;
  --panel: #ffffff;
  --ink: #1f2430;
  --muted: #6b7280;
  --accent: #3b6ea5;
  --accent2: #c0392b;
  --gold: #e0913c;
  --rule: #e3e0d8;
  /* the forward-time run of a wallpaper section's tabs (css .tabbtn.fwd):
     a green quiet enough to sit beside the grey run without competing with
     the animation, and dark enough on it to pass as body-weight text */
  --fwd-bg: #e6f0e6;
  --fwd-rule: #c3dbc7;
  --fwd-ink: #2f6b46;
  --code-bg: #f1efe9;
  --shadow: 0 1px 3px rgba(30, 30, 60, 0.08), 0 4px 16px rgba(30, 30, 60, 0.05);
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #14161c;
    --panel: #1c1f28;
    --ink: #e8e6e1;
    --muted: #9aa0ab;
    --accent: #7ba7d7;
    --accent2: #e07460;
    --gold: #e0b23c;
    --rule: #2c303c;
    --fwd-bg: #1e2a24;
    --fwd-rule: #33473b;
    --fwd-ink: #86c39d;
    --code-bg: #232733;
    --shadow: 0 1px 3px rgba(0, 0, 0, 0.4);
  }
}

* { box-sizing: border-box; }

html { scroll-behavior: smooth; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font: 17px/1.65 Charter, "Bitstream Charter", Georgia, "Times New Roman", serif;
}

header.site {
  border-bottom: 1px solid var(--rule);
  background: var(--panel);
  position: sticky;
  top: 0;
  z-index: 50;
  box-shadow: var(--shadow);
}

header.site .inner {
  max-width: 1060px;
  margin: 0 auto;
  padding: 0.6rem 1.2rem;
  display: flex;
  align-items: baseline;
  gap: 1.4rem;
  flex-wrap: wrap;
}

header.site .title {
  font-weight: bold;
  font-size: 1.05rem;
  color: var(--ink);
  text-decoration: none;
  letter-spacing: 0.01em;
}

header.site nav { display: flex; gap: 1.1rem; flex-wrap: wrap; }

header.site nav a {
  color: var(--muted);
  text-decoration: none;
  font-size: 0.95rem;
  font-family: system-ui, -apple-system, sans-serif;
}
header.site nav a:hover { color: var(--accent); }
header.site nav a.here { color: var(--accent); font-weight: 600; }

main {
  max-width: 1060px;
  margin: 0 auto;
  padding: 1.6rem 1.2rem 4rem;
}

main.narrow { max-width: 780px; }

h1 { font-size: 1.9rem; line-height: 1.25; margin: 1.2rem 0 0.4rem; }
h2 { font-size: 1.45rem; margin: 2.4rem 0 0.6rem; }
h3 { font-size: 1.15rem; margin: 1.8rem 0 0.4rem; }
h1 + .subtitle { color: var(--muted); margin-top: 0; font-size: 1.05rem; }

.hint {
  color: var(--muted);
  font-family: system-ui, -apple-system, sans-serif;
  font-size: 0.88rem;
}
kbd {
  font-family: system-ui, sans-serif;
  font-size: 0.85em;
  border: 1px solid var(--rule);
  border-bottom-width: 2px;
  border-radius: 4px;
  padding: 0 0.35em;
  background: var(--code-bg);
  color: var(--ink);
}

h2 { border-bottom: 1px solid var(--rule); padding-bottom: 0.25rem; }

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

p { margin: 0.7rem 0; }

.thm, .aside {
  border-left: 3px solid var(--accent);
  background: var(--panel);
  padding: 0.7rem 1.1rem;
  margin: 1.1rem 0;
  box-shadow: var(--shadow);
  border-radius: 0 6px 6px 0;
}
.aside { border-left-color: var(--gold); }
.aside .label, .thm .label {
  font-family: system-ui, sans-serif;
  font-size: 0.8rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--muted);
  display: block;
  margin-bottom: 0.2rem;
}

code, .mono {
  font-family: "SF Mono", Menlo, Consolas, monospace;
  font-size: 0.86em;
  background: var(--code-bg);
  padding: 0.08em 0.35em;
  border-radius: 4px;
}

.sym {
  font-family: Charter, Georgia, serif;
  font-weight: 600;
  white-space: nowrap;
}
.sym sub { font-size: 0.7em; }

table.counts {
  border-collapse: collapse;
  margin: 1rem 0;
  font-size: 0.94rem;
  font-family: system-ui, sans-serif;
}
table.counts th, table.counts td {
  border: 1px solid var(--rule);
  padding: 0.35rem 0.7rem;
  text-align: right;
}
table.counts th { background: var(--code-bg); text-align: center; }
table.counts td:first-child { text-align: left; }


/* ------- animation cards ------- */
.cardgrid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
  gap: 1.1rem;
  margin: 1.4rem 0;
}

.gcard {
  background: var(--panel);
  border: 1px solid var(--rule);
  border-radius: 10px;
  overflow: hidden;
  box-shadow: var(--shadow);
  display: flex;
  flex-direction: column;
}

.gcard canvas {
  width: 100%;
  height: 210px;
  display: block;
  background: #faf9f6;
}

.gcard .meta {
  padding: 0.55rem 0.8rem 0.7rem;
  font-family: system-ui, sans-serif;
  font-size: 0.86rem;
  cursor: pointer;   /* opens the detail modal; the canvas plays/pauses */
}
.gcard .meta:hover .sym { color: var(--accent); }

.gcard .meta .sym { font-size: 1.12rem; }
.gcard .meta .hm { color: var(--muted); margin-left: 0.5em; }
.gcard .meta .tags { margin-top: 0.25rem; display: flex; gap: 0.4rem; flex-wrap: wrap; }

.tag {
  font-size: 0.72rem;
  padding: 0.06rem 0.5rem;
  border-radius: 99px;
  background: var(--code-bg);
  color: var(--muted);
  font-family: system-ui, sans-serif;
}
/* Which colouring of its projection a group induces — the partition the
   hierarchy page draws, and the one thing about a group worth saying on a
   card. The four are mutually exclusive and cover all 275, so every card
   carries exactly one. Only the two ends of the range are coloured: the
   clockwork groups in the green the wallpaper atlas already uses for its
   forward run, so one idea keeps one colour, and the groups whose clock does
   not descend at all in a muted red, because "there is no colouring here" is
   the exceptional answer. The two middle classes are the ordinary case and
   take the plain tag — a badge on almost every card carries no information. */
.tag.kind-cyclic { background: var(--fwd-bg); color: var(--fwd-ink);
                   box-shadow: inset 0 0 0 1px var(--fwd-rule); }
.tag.kind-none { background: transparent; color: var(--accent2);
                 box-shadow: inset 0 0 0 1px currentColor; }
.tag.product { }

/* featured big demo */
.demo {
  background: var(--panel);
  border: 1px solid var(--rule);
  border-radius: 12px;
  box-shadow: var(--shadow);
  margin: 1.6rem 0;
  overflow: hidden;
}
.demo canvas { width: 100%; height: 340px; display: block; background: #faf9f6; }
.demo .caption {
  padding: 0.8rem 1.1rem;
  font-size: 0.95rem;
  border-top: 1px solid var(--rule);
}
.demo .caption .sym { font-size: 1.25rem; }

/* filter bar */
.filters {
  display: flex;
  gap: 0.7rem;
  flex-wrap: wrap;
  align-items: center;
  font-family: system-ui, sans-serif;
  font-size: 0.9rem;
  background: var(--panel);
  padding: 0.7rem 0.9rem;
  border: 1px solid var(--rule);
  border-radius: 10px;
  margin: 1rem 0;
}
.filters select, .filters label { font-size: 0.9rem; }
.filters .count { color: var(--muted); margin-left: auto; }

button.linkish {
  border: 1px solid var(--rule);
  background: var(--code-bg);
  border-radius: 6px;
  padding: 0.25rem 0.7rem;
  cursor: pointer;
  font-size: 0.85rem;
  color: var(--ink);
}
button.linkish:hover { border-color: var(--accent); color: var(--accent); }

/* ------- animation stage: paused by default, the picture is the button -----
 * Every animation canvas is wrapped in .anim-stage (js/stage.js); clicking
 * anywhere in it toggles playback, so nothing is painted over the pattern. */
.anim-stage {
  position: relative;
  display: block;
  outline: none;
  cursor: pointer;
}
.anim-stage canvas { display: block; }
/* The ring must be an outline with a NEGATIVE offset: the canvas fills the
 * stage box opaquely (so an inset box-shadow is painted under it), and
 * .demo / .gcard clip with overflow:hidden (so an outward ring is cut off).
 * Outlines paint on top of descendants, hence this is the one that shows. */
.anim-stage:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }

/* ------- animation playback controls ------- */
.anim-controls {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  padding: 0.3rem 0.6rem;
  border-top: 1px solid var(--rule);
  background: var(--panel);
  font-family: system-ui, sans-serif;
}
.anim-controls .ac-btn {
  border: 1px solid var(--rule);
  background: var(--code-bg);
  color: var(--ink);
  border-radius: 6px;
  width: 2rem;
  height: 1.5rem;
  line-height: 1;
  font-size: 0.7rem;
  cursor: pointer;
  flex: none;
}
.anim-controls .ac-btn:hover { border-color: var(--accent); color: var(--accent); }
.anim-controls .ac-col { flex: 1; min-width: 0; }
.anim-controls .ac-slider { width: 100%; display: block; accent-color: var(--accent); }
.anim-controls .ac-label {
  color: var(--muted);
  font-size: 0.72rem;
  font-variant-numeric: tabular-nums;
  flex: none;
  min-width: 4.2em;
  text-align: right;
}

/* ------- the time-symmetry ruler under the scrub bar (js/controls.js) -----
 * One tick per distinguished instant of the loop: a labelled bar at the beats
 * t = k/N, a lozenge at the palindrome pivots of a time reversal. Clicking a
 * tick seeks to it. Marks are positioned along the THUMB'S TRAVEL (the inline
 * calc() controls.js writes), so a tick lines up with the thumb that sits on
 * it. The row keeps its own height so the ticks never overlap the track. */
.anim-controls .ac-marks {
  position: relative;
  height: 0.95rem;
  margin-top: 1px;
}
.anim-controls .ac-mark {
  position: absolute;
  top: 0;
  transform: translateX(-50%);
  width: 9px;
  height: 100%;
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
  display: flex;
  flex-direction: column;
  align-items: center;
  line-height: 1;
}
/* the tick itself, drawn with ::before so the label can sit under it */
.anim-controls .ac-mark::before {
  content: "";
  width: 1px;
  height: 4px;
  background: var(--muted);
  opacity: 0.7;
}
.anim-controls .ac-mark-mirror::before {
  /* palindrome pivot: a lozenge, in the reversal colour used by the
     symmetry-element overlay */
  width: 5px;
  height: 5px;
  background: #8e44ad;
  opacity: 0.85;
  transform: rotate(45deg) scale(0.8);
}
.anim-controls .ac-mark-both::before { background: #8e44ad; opacity: 0.9; width: 2px; }
.anim-controls .ac-mark:hover::before,
.anim-controls .ac-mark:focus-visible::before {
  background: var(--accent2);
  opacity: 1;
  height: 6px;
}
.anim-controls .ac-mark-label {
  font-size: 8.5px;
  color: var(--muted);
  font-family: system-ui, sans-serif;
  font-variant-numeric: tabular-nums;
  margin-top: 1px;
  white-space: nowrap;
  pointer-events: none;
}
.anim-controls .ac-mark:hover .ac-mark-label { color: var(--accent2); }

figure { margin: 1.4rem 0; }
figcaption { font-size: 0.9rem; color: var(--muted); margin-top: 0.4rem; }

footer.site {
  border-top: 1px solid var(--rule);
  color: var(--muted);
  font-size: 0.85rem;
  font-family: system-ui, sans-serif;
  padding: 1.2rem;
  text-align: center;
}

/* generator display */
.genbox {
  font-family: "SF Mono", Menlo, monospace;
  font-size: 0.8rem;
  background: var(--code-bg);
  border-radius: 8px;
  padding: 0.6rem 0.9rem;
  overflow-x: auto;
  white-space: pre;
  line-height: 1.5;
}

.two-col { display: grid; grid-template-columns: 1fr 1fr; gap: 1.2rem; }
@media (max-width: 720px) { .two-col { grid-template-columns: 1fr; } }

/* ------- tabbed notation demos ------- */
.tabdemo { margin: 1.5rem 0; }
.tabbar { display: flex; gap: 0.35rem; flex-wrap: wrap; position: relative; z-index: 1; }
.tabbtn {
  font-family: Charter, Georgia, serif;
  font-weight: 600;
  font-size: 0.98rem;
  padding: 0.28rem 0.85rem;
  border: 1px solid var(--rule);
  border-bottom: none;
  border-radius: 8px 8px 0 0;
  background: var(--code-bg);
  color: var(--muted);
  cursor: pointer;
}
.tabbtn.active { background: var(--panel); color: var(--ink); }
/* The trivial clock — the film group identical to its wallpaper group, sorted
 * last by sectionSort — is greyed back: it belongs in the row as the baseline
 * the others depart from, not as one more case to work through. Opacity rather
 * than a colour so it stays lighter in both themes and in both states, and it
 * comes up on hover so it never reads as disabled. */
.tabbtn.trivial { opacity: 0.55; font-weight: 500; }
.tabbtn.trivial:hover { opacity: 0.9; }

/* Two runs in the wallpaper atlas (tabs.js `split`): the film groups with NO
 * time reversal, tinted green, then the ones that reverse time, in the row's
 * usual grey. `.tabsplit` is a zero-height full-width flex item, so the
 * reversal run always begins on a line of its own however the row wraps —
 * which matters most in the big sections, where 36 tabs wrap several times
 * and colour alone would not read as a grouping. The active tab keeps its
 * run's ink colour over the pane background, so the group being shown still
 * says which run it came from. */
.tabsplit { flex-basis: 100%; height: 0.3rem; }
.tabbtn.fwd {
  background: var(--fwd-bg);
  border-color: var(--fwd-rule);
  color: var(--fwd-ink);
}
.tabbtn.fwd.active { background: var(--panel); color: var(--fwd-ink); }
/* after .tabbtn.fwd.active on purpose: same specificity, so source order
 * decides, and a selected trivial tab must stay muted rather than pick up its
 * run's ink — greyed is the whole point of it */
.tabbtn.trivial.active { opacity: 0.8; color: var(--muted); }
.tabpane {
  display: none;
  background: var(--panel);
  border: 1px solid var(--rule);
  border-radius: 0 10px 10px 10px;
  overflow: hidden;
  box-shadow: var(--shadow);
}
.tabpane.active { display: block; }
.tabpane canvas { width: 100%; height: 300px; display: block; background: #faf9f6; }
.tabpane .caption { padding: 0.7rem 1rem; font-size: 0.93rem; border-top: 1px solid var(--rule); }
.tabpane .caption .sym { font-size: 1.15rem; }
.sym sup { font-size: 0.7em; }

/* ------- wallpaper atlas ------- */
.toc { display: flex; gap: 0.45rem; flex-wrap: wrap; margin: 1.1rem 0 1.8rem; }
.toc .chip {
  font-family: system-ui, sans-serif;
  font-size: 0.85rem;
  padding: 0.18rem 0.7rem;
  border: 1px solid var(--rule);
  border-radius: 99px;
  background: var(--panel);
  color: var(--ink);
  text-decoration: none;
  box-shadow: var(--shadow);
}
.toc .chip:hover { border-color: var(--accent); }
.toc .chip .n, section.wp h2 .n { color: var(--muted); font-weight: normal; }
.toc .chip .n { margin-left: 0.25em; }
section.wp { margin: 2.6rem 0; }
section.wp h2 {
  border-bottom: 1px solid var(--rule);
  padding-bottom: 0.3rem;
}
section.wp h2 .n { font-size: 0.95rem; margin-left: 0.4em; }
section.wp .demo canvas { height: 280px; }

/* atlas overview: one or two example demos side by side */
.wp-pair {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  gap: 1.1rem;
}
.wp-pair .demo { margin: 0.4rem 0; }
.wp-pair .demo canvas { height: 250px; }
.alllink { font-size: 1.02rem; font-weight: 600; }

/* detail-level tab widget: larger animation pane */
.tabdemo.big .tabpane canvas { height: 360px; }

/* comparison table: scrollable on narrow screens, muted rotation-free rows */
.tablewrap { overflow-x: auto; margin: 1rem 0; }
.tablewrap table.counts { margin: 0; }
table.counts tr.rotfree td { background: var(--code-bg); }
table.counts td .sym { margin-right: 0.35em; }

/* --------------------------------------------------------------- billiards
 * The page is the animation: no prose, so the film takes the height the text
 * would have had, and the canvas is sized by the viewport rather than by the
 * measure. What has to be legible here is a ball's fill level, not the
 * outline of a glyph, so it wants the room. */
main.billiards {
  max-width: 1080px;
  padding-top: 1rem;
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
}

.demo canvas.ballfilm {
  height: clamp(360px, 76vh, 760px);
  cursor: pointer;
}

/* one muted line under the picture: what the film is, and where it came from */
.credit {
  margin: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem 1.1rem;
  font: 0.85rem/1.5 system-ui, -apple-system, sans-serif;
  color: var(--muted);
}
.credit .sym { font-size: 0.95rem; }

.film-opts {
  display: flex;
  gap: 1.4rem;
  flex-wrap: wrap;
  padding: 0.55rem 0.9rem 0.75rem;
  border-top: 1px solid var(--rule);
  font: 0.88rem/1.4 system-ui, -apple-system, sans-serif;
  color: var(--muted);
}
.film-opts label {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  cursor: pointer;
}
.film-opts label:hover { color: var(--accent); }
.film-opts input { accent-color: var(--accent); cursor: pointer; }

/* Progressive hints. Closed by default and quiet enough to skip past — the
 * page is a puzzle, so a hint has to be something you choose to open. */
details {
  border: 1px solid var(--rule);
  border-radius: 6px;
  background: var(--panel);
  padding: 0.15rem 1rem;
  margin: 0.7rem 0;
  box-shadow: var(--shadow);
}
details[open] { padding-bottom: 0.7rem; }
summary {
  cursor: pointer;
  padding: 0.6rem 0;
  font-family: system-ui, -apple-system, sans-serif;
  font-size: 0.94rem;
  color: var(--accent);
  list-style-position: outside;
}
summary:hover { color: var(--accent2); }
details[open] summary { border-bottom: 1px solid var(--rule); margin-bottom: 0.4rem; }
details > p:last-child { margin-bottom: 0; }

dl.facts {
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: 0.4rem 1.2rem;
  margin: 1.2rem 0;
  font: 0.9rem/1.5 system-ui, -apple-system, sans-serif;
  font-variant-numeric: tabular-nums;
}
dl.facts dt { color: var(--muted); }
dl.facts dd { margin: 0; }

@media (max-width: 520px) {
  dl.facts { grid-template-columns: 1fr; gap: 0 0; }
  dl.facts dd { margin-bottom: 0.5rem; }
}

/* ------- hierarchy page -------
 * The shared .counts rule right-aligns every column but the first, which is
 * right for a table of numbers and wrong for the hierarchy page's prose
 * columns ("an object is", "same when", the crystal-system names). Those
 * columns are marked .txt by js/hierarchy.js. A .total row is a column sum
 * and is ruled off from the rows it adds up.
 */
table.counts th.txt, table.counts td.txt { text-align: left; }
table.counts tr.total td { border-top-width: 2px; }
table.counts tbody tr:hover { background: var(--code-bg); }

/* ------------------------------------------------------------------ motifs
 * A prototyping board: many small films at once, each with its measurements
 * under it. Cards are uniform on purpose — the whole page is a comparison, so
 * nothing may look better by being bigger. */
.mgrid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(270px, 1fr));
  gap: 1.1rem;
  margin: 1.4rem 0 2rem;
}
.mcard {
  background: var(--panel);
  border: 1px solid var(--rule);
  border-radius: 8px;
  box-shadow: var(--shadow);
  overflow: hidden;
  display: flex;
  flex-direction: column;
}
.mcard h3 {
  margin: 0;
  padding: 0.6rem 0.85rem 0.5rem;
  font-size: 0.95rem;
  font-family: system-ui, -apple-system, sans-serif;
  font-weight: 600;
  border-bottom: 1px solid var(--rule);
}
.mcard canvas {
  width: 100%;
  height: 210px;
  display: block;
  background: #faf9f6;
  cursor: pointer;
}
.mcard .mnote {
  margin: 0;
  padding: 0.2rem 0.85rem 0.9rem;
  font-size: 0.86rem;
  line-height: 1.5;
  color: var(--muted);
}

/* the measurements. A bar is easier to scan across fourteen cards than a
   number, but the number stays: this is a page about being precise. */
.scores {
  padding: 0.55rem 0.85rem 0.3rem;
  display: grid;
  gap: 0.28rem;
  font: 0.74rem/1.2 system-ui, -apple-system, sans-serif;
  font-variant-numeric: tabular-nums;
}
.score { display: grid; grid-template-columns: 5.6em 1fr 2.4em; align-items: center;
         gap: 0.45rem; color: var(--muted); }
.score .track { background: var(--code-bg); border-radius: 3px; height: 6px; overflow: hidden; }
.score .track > span { display: block; height: 100%; background: var(--muted); }
.score.ok .track > span { background: var(--fwd-ink); }
.score.bad .track > span { background: var(--accent2); }
.score.bad .num { color: var(--accent2); font-weight: 600; }
.score .num { text-align: right; }

/* the group under test */
.gpicks { display: flex; flex-wrap: wrap; gap: 0.5rem; margin: 0.8rem 0 0.4rem; }
.gpick {
  display: flex; flex-direction: column; gap: 0.15rem; text-align: left;
  padding: 0.45rem 0.7rem; cursor: pointer;
  background: var(--panel); color: var(--ink);
  border: 1px solid var(--rule); border-radius: 6px;
  font: 0.8rem/1.3 system-ui, -apple-system, sans-serif;
  max-width: 15rem;
}
.gpick .sym { font-size: 1.05rem; }
.gpick .why { color: var(--muted); font-size: 0.74rem; }
.gpick:hover { border-color: var(--accent); }
.gpick.on { border-color: var(--accent); box-shadow: inset 0 0 0 1px var(--accent); }

/* rows the reader is asked to compare — on the hierarchy page, the four pairs
   of clockwork groups that share one colour signature. Marked rather than
   merely adjacent, because the pair is the point being made. */
table.counts tr.pair td {
  background: var(--code-bg);
  background: color-mix(in srgb, var(--gold) 13%, transparent);
}
table.counts tr.pair td:first-child { box-shadow: inset 3px 0 0 var(--gold); }
table.counts tr.pair:hover td {
  background: color-mix(in srgb, var(--gold) 20%, transparent);
}

/* a hotlinkable heading: the "#" appears on hover or keyboard focus, and is
   always reachable, never a layout shift */
h2 > a.anchor, h3 > a.anchor {
  margin-left: 0.4em;
  font-size: 0.72em;
  font-weight: normal;
  color: var(--rule);
  text-decoration: none;
  opacity: 0;
  transition: opacity 0.12s ease;
}
h2:hover > a.anchor, h3:hover > a.anchor,
h2 > a.anchor:focus-visible, h3 > a.anchor:focus-visible {
  opacity: 1;
  color: var(--accent);
}

/* ------------------------------------------------------- dihedral example
 * Twelve motifs on a hexagon, once per subgroup. Cards are uniform: the page
 * is a comparison, so no colouring may look more important by being bigger. */
.dfig { margin: 1.1rem 0; display: flex; justify-content: center; }
.dfig canvas {
  width: min(320px, 70vw); height: min(320px, 70vw);
  background: #faf9f6; border: 1px solid var(--rule); border-radius: 8px;
}
.dgrid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
  gap: 1rem; margin: 1.3rem 0 2rem;
}
.dcard {
  background: var(--panel); border: 1px solid var(--rule); border-radius: 8px;
  box-shadow: var(--shadow); overflow: hidden;
}
.dcard canvas { width: 100%; aspect-ratio: 1; display: block; background: #faf9f6; }
.dmeta { padding: 0.55rem 0.8rem 0.75rem; border-top: 1px solid var(--rule); }
.dhead {
  display: flex; align-items: baseline; gap: 0.4rem; flex-wrap: wrap;
  font: 0.9rem/1.4 system-ui, -apple-system, sans-serif; margin-bottom: 0.4rem;
}
.dfacts {
  display: grid; grid-template-columns: max-content 1fr; gap: 0.2rem 0.6rem;
  margin: 0; font: 0.76rem/1.45 system-ui, -apple-system, sans-serif;
}
.dfacts dt { color: var(--muted); }
.dfacts dd { margin: 0; }
.dfacts .mono { background: none; padding: 0; font-size: 0.95em; }

#colourings h3 { margin: 1.6rem 0 0.2rem; font-size: 1.05rem;
                 font-family: system-ui, -apple-system, sans-serif; }
#colourings h3:first-child { margin-top: 0.4rem; }

/* the two conventions, side by side */
.dpair { display: grid; grid-template-columns: 1fr 1fr; }
.dpair figure { margin: 0; position: relative; }
.dpair figure + figure { border-left: 1px solid var(--rule); }
.dpair canvas { width: 100%; aspect-ratio: 1; display: block; background: #faf9f6; }
.dpair span {
  position: absolute; left: 0.35rem; top: 0.25rem;
  font: 0.72rem/1 "SF Mono", Menlo, monospace; color: var(--muted);
  background: rgba(250,249,246,0.85); padding: 0.1rem 0.25rem; border-radius: 3px;
}
.dside { margin: 0.45rem 0 0; font-size: 0.76rem; line-height: 1.45; color: var(--muted); }
