/* ===========================================================================
   board.css — the blackboard itself.

   The whole illusion rests on four things, in order of importance:
     1. the board is never flat black and never evenly lit
     2. chalk strokes have rough, patchy edges (SVG displacement)
     3. earlier work is imperfectly erased and still faintly there
     4. chalk dust catches light around every stroke
   =========================================================================== */

/* ---------------------------------------------------------------- the board */
.board {
  position: relative;
  min-block-size: 100svh;
  overflow: hidden;
  isolation: isolate;
  background:
    /* chalk haze — broad smears where the board was wiped. these are what
       actually sell it; without them the surface reads as a dark div. */
    radial-gradient(64% 42% at 24% 30%, rgb(214 226 218 / .085), transparent 66%),
    radial-gradient(52% 34% at 74% 20%, rgb(214 226 218 / .062), transparent 68%),
    radial-gradient(78% 50% at 58% 74%, rgb(214 226 218 / .055), transparent 70%),
    radial-gradient(36% 27% at 10% 72%, rgb(214 226 218 / .045), transparent 68%),
    radial-gradient(30% 40% at 92% 58%, rgb(214 226 218 / .038), transparent 70%),
    /* vignette, kept light so the haze survives */
    radial-gradient(132% 104% at 48% 44%, transparent 52%, rgb(0 0 0 / .38) 100%),
    linear-gradient(168deg, var(--board-lift), var(--board) 46%, var(--board-deep));
}

/* eraser streaks: long horizontal wipes, barely there, at a slight angle */
.board::before {
  content: '';
  position: absolute; inset: -12%;
  pointer-events: none;
  opacity: 1;
  background: repeating-linear-gradient(
    177.5deg,
    transparent 0 3px,
    rgb(214 226 218 / .030) 3px 6px,
    transparent 6px 15px,
    rgb(214 226 218 / .020) 15px 17px,
    transparent 17px 36px);
  mask-image:
    radial-gradient(60% 40% at 30% 30%, #000 0%, transparent 70%),
    radial-gradient(70% 45% at 72% 66%, #000 0%, transparent 72%);
  mask-composite: add;
}

/* the grain of the slate */
.board::after {
  content: '';
  position: absolute; inset: 0;
  pointer-events: none;
  z-index: 5;
  opacity: .32;
  mix-blend-mode: overlay;
  background-image: var(--grain);
  background-size: 220px 220px;
}

/* ghosts of earlier work, still faintly on the board under everything */
.ghosts { position: absolute; inset: 0; z-index: 0; pointer-events: none; overflow: hidden; }
.ghost {
  position: absolute;
  color: var(--chalk);
  opacity: .052;
  filter: url(#chalk-line) blur(.7px);
  translate: -50% -50%;
}
.ghost svg { display: block; inline-size: 100%; block-size: 100%; overflow: visible; }

/* ------------------------------------------------------- the writing
   Equations being written, sitting, and being wiped off again. The layer sits
   above the ghosts and below everything that can be clicked, and never takes
   the pointer: it is the board's own hand, not part of the navigation.

   Stroke width is in px, so a fourteen-pixel equation
   and a thirty-pixel one are written with the same stick of chalk.
   ------------------------------------------------------------------- */
.chalk {
  position: absolute; inset: 0; z-index: 1;
  pointer-events: none; overflow: hidden;
  /* its own layer: the figures move every frame above it, and without this
     each of those frames repaints several hundred static chalk strokes */
  will-change: transform;
}

.chalk-note {
  position: absolute;
  /* which stick it was written with. chalk.js sets --ink per note; the two
     other weights each note needs are mixed from it, so adding a colour to
     the box is one token in base.css and nothing here. */
  --ink: var(--stick-white);
  --accent: var(--ink);        /* the second stick, if one was picked up */
  color: var(--ink);
  opacity: .74;
  /* the layer lets the pointer through; a written equation does not, because
     pointing at one lights the article it came from. Its box is the ink's own
     bounding box, so there is no invisible margin to catch the cursor. */
  pointer-events: auto;
  transition: opacity .5s var(--ease-out), color .5s var(--ease-out);
}
.chalk-note.is-lit { color: color-mix(in oklab, var(--ink) 50%, var(--chalk)); opacity: 1; }
.chalk-note.is-lit .gloss { color: color-mix(in oklab, var(--ink) 84%, var(--chalk)); }

/* The accent: the arrows, ticks, marked vertex and labels of a diagram, the
   note under an equation, the line under a result. Everything else stays in
   the body stick, which is nearly always white. */
.chalk-note path.ac { stroke: var(--accent); }
.chalk-note.is-lit path.ac { stroke: color-mix(in oklab, var(--accent) 50%, var(--chalk)); }
.chalk-note.has-accent .gloss { color: color-mix(in oklab, var(--accent) 80%, var(--board)); }
.chalk-note.has-accent.is-lit .gloss { color: color-mix(in oklab, var(--accent) 70%, var(--chalk)); }
.chalk-note svg { display: block; overflow: visible; }
.chalk-note path {
  fill: none;
  stroke: currentColor;
  /* THE WIDTHS BELOW ARE IN PIXELS, converted here rather than by
     vector-effect: non-scaling-stroke. That property looked like the right
     tool and was the bug — with it on, the browser measures stroke-dasharray
     in SCREEN pixels while chalk.js computes stroke lengths in the SVG's own
     units (1/1000 em). Those differ by the viewBox scale, about 1:75, so the
     dash offset finished sweeping in the first ~1.2% of every transition:
     each stroke appeared whole in about two milliseconds and then sat there
     for the rest of its time. `--u` is the note's user-units-per-pixel and
     `--s` a glyph's own scale, both set by chalk.js, so the chalk stays the
     same thickness at every size with everything in one coordinate system. */
  stroke-width: calc(1.45px * var(--u, 1) / var(--s, 1));
  stroke-linecap: round;
  stroke-linejoin: round;
  transition-property: stroke-dashoffset;
  transition-timing-function: linear;
  transition-duration: 0s;
}
/* the texture goes on once the writing has stopped moving: a displacement
   filter over a stroke that is redrawing every frame is the one thing on this
   board expensive enough to drop frames */
.chalk-note.is-written { filter: url(#chalk-written); }
.chalk-note .gloss { color: color-mix(in oklab, var(--ink) 72%, var(--board)); }
.chalk-note .gloss path { stroke-width: calc(1.2px * var(--u, 1) / var(--s, 1)); }
/* a drawn line carries a touch more chalk than a written one: at the same
   weight a diagram reads as a wireframe rather than as something drawn */
.chalk-note .figure path { stroke-width: calc(1.7px * var(--u, 1) / var(--s, 1)); }

/* the eraser: one pass left to right. What is behind it is gone, what is in
   front of it is not, and the bloom rides the edge between. */
.chalk-note.is-wiping {
  -webkit-mask-image: linear-gradient(93deg, #0000 var(--wipe, 0%), #000 calc(var(--wipe, 0%) + 16%));
          mask-image: linear-gradient(93deg, #0000 var(--wipe, 0%), #000 calc(var(--wipe, 0%) + 16%));
}
.chalk-note.is-wiping::after {
  content: '';
  position: absolute; inset: -14% -3%;
  background: linear-gradient(93deg,
    transparent calc(var(--wipe, 0%) - 4%),
    rgb(214 226 218 / .10) var(--wipe, 0%),
    transparent calc(var(--wipe, 0%) + 14%));
}

/* and what it leaves behind, which is the point of erasing rather than
   fading: the board keeps a record of what was on it */
.chalk-note.is-smudge {
  pointer-events: none;                 /* what is erased is no longer a thing
                                           you can point at */
  filter: url(#chalk-line) blur(.7px);
  animation: smudge-fade var(--life, 75s) linear forwards;
}
@keyframes smudge-fade {
  from { opacity: var(--residue, .055) }
  72%  { opacity: calc(var(--residue, .055) * .55) }
  to   { opacity: 0 }
}

/* the chalk itself, at the point it touches the board */
.chalk-tip {
  position: absolute; inset-block-start: 0; inset-inline-start: 0;
  inline-size: 10px; block-size: 10px; margin: -5px;
  border-radius: 50%;
  background: radial-gradient(circle, rgb(230 227 216 / .85), rgb(230 227 216 / .12) 45%, transparent 72%);
}

/* ------------------------------------------------------------ the heading */
.heading {
  position: absolute;
  inset-block-start: clamp(2.2rem, 7vh, 5rem);
  inset-inline-start: clamp(1.4rem, 5vw, 5.5rem);
  z-index: 3;
  max-inline-size: min(21rem, 40vw);
  pointer-events: none;
}
.heading > * { pointer-events: auto; }

.heading h1 {
  margin: 0;
  font-weight: 400;
  font-size: clamp(2.1rem, 4.4vw, 3.6rem);
  line-height: 1.02;
  letter-spacing: .004em;
  color: var(--chalk);
  filter: url(#chalk-text) drop-shadow(0 0 10px rgb(230 227 216 / .16));
}
.heading h1 .given { display: block; color: var(--chalk-soft); font-size: .62em; letter-spacing: .06em; }

/* the title, in the same voice as the given name above it */
.heading .role {
  margin: .5rem 0 0;
  font-family: var(--sans);
  font-size: .72rem;
  font-weight: 400;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--chalk-soft);
}

/* The paragraph is the longest thing in the corner, so it is set at the
   quietest weight the board has and on the widest measure the column will
   take: every line it saves is a line of slate back. */
.heading .line {
  margin: .85rem 0 0;
  max-inline-size: 52ch;
  font-size: .80rem;
  line-height: 1.52;
  color: var(--chalk-soft);
  white-space: pre-line;
  text-shadow: 0 0 9px rgb(230 227 216 / .08);
}


/* --------------------------------------------------------------- the field */
.field {
  position: absolute;
  inset: 0;
  z-index: 2;
  /* The field is a frame, not a surface. It covers the whole board, so while
     it took the pointer nothing underneath it could — including the writing,
     which sits below it because an equation is background and a figure is
     not. The things in here that are meant to be pointed at say so
     themselves, as .sat already did. */
  pointer-events: none;
}

/* connectors are drawn beneath the figures */
.wires {
  position: absolute; inset: 0;
  inline-size: 100%; block-size: 100%;
  overflow: visible;
  pointer-events: none;
  z-index: 1;
}
.wire {
  fill: none;
  stroke: #7d857f;
  stroke-width: 1.3;
  opacity: .8;
  stroke-linecap: round;
  filter: url(#chalk-line);
  transition: stroke .5s var(--ease-out), opacity .5s var(--ease-out);
}
.wire.is-lit   { stroke: var(--chalk-soft); opacity: 1; }
.wire.is-dimmed{ opacity: .18; }

/* ----------------------------------------------------------------- a node */
.node {
  position: absolute;
  /* above the article marks (.sats, 2): where a mark's orbit brushes a
     drawing, the drawing is what a finger meant */
  z-index: 3;
  display: block;
  inline-size: var(--size);
  margin-inline-start: calc(var(--size) / -2);
  margin-block-start: calc(var(--size) / -2);
  text-decoration: none;
  color: var(--chalk-soft);
  /* the engine writes `translate` on this element every frame; scale and
     opacity stay CSS's, so the lens rearrangement springs without the
     physics knowing about it. The declaration below is only the resting
     value — an unstarted board, and any node the engine has not reached. */
  translate: 0 0;
  scale: var(--lens-scale, 1);
  transition: scale .85s var(--spring), opacity .5s var(--ease-out);
  will-change: translate, scale;
  pointer-events: auto;
}

.node .glyph {
  display: block;
  inline-size: 100%;
  aspect-ratio: 1;
  color: var(--ink, var(--chalk-soft));
  filter: url(#chalk-line) drop-shadow(0 0 6px rgb(230 227 216 / .13));
  transition: color .5s var(--ease-out), filter .5s var(--ease-out);
}
.node .glyph svg { display: block; inline-size: 100%; block-size: 100%; overflow: visible; }

.node .cap {
  display: block;
  margin-block-start: .35rem;
  text-align: center;
  font-family: var(--sans);
  font-size: clamp(.63rem, .4vw + .5rem, .74rem);
  letter-spacing: .07em;
  line-height: 1.32;
  color: var(--chalk-soft);
  transition: color .4s var(--ease-out);
}

/* weight 2 nodes are simply drawn heavier, the way you'd press harder */
.node[data-weight="2"] .glyph { --ink: var(--chalk); }

/* A quiet node is drawn the way you'd sketch something in the corner while
   thinking about it: smaller (board.js), in a lighter hand, and with its
   caption held back until you look at it. Nothing is hidden — point at one
   and it comes up to full strength like any other drawing. */
.node[data-quiet] .glyph { --ink: var(--chalk-hair); }
.node[data-quiet="2"] .glyph { --ink: #5d6460; }   /* 3.0:1 — the non-text floor */
/* The caption is the link's accessible name, so it stays at --chalk-faint —
   the documented 4.5:1 floor — at BOTH quiet levels. The two levels are told
   apart by size and by how hard the drawing was pressed, never by taking the
   text below the floor. */
.node[data-quiet] .cap { color: var(--chalk-faint); font-size: clamp(.6rem, .35vw + .48rem, .68rem); }
.node[data-quiet]:is(:hover, :focus-visible) .glyph { --ink: var(--chalk); color: var(--chalk); }
.node[data-quiet]:is(:hover, :focus-visible) .cap   { color: var(--chalk); }

/* hover / focus: the figure brightens and the note appears */
.node:is(:hover, :focus-visible) { z-index: 9; }
.node:is(:hover, :focus-visible) .glyph {
  color: var(--chalk);
  filter: url(#chalk-line) drop-shadow(0 0 14px rgb(230 227 216 / .4));
}
.node:is(:hover, :focus-visible) .cap { color: var(--chalk); }

.node .note {
  position: absolute;
  /* a description, not a target: it hangs below the figure and can lie
     across a neighbour, which must still get its own click */
  pointer-events: none;
  inset-block-start: calc(100% + .3rem);
  inset-inline-start: 50%;
  translate: -50% 0;
  inline-size: max-content;
  max-inline-size: 22ch;
  padding: .3rem .1rem;
  text-align: center;
  font-size: .78rem;
  font-style: italic;
  line-height: 1.4;
  color: var(--chalk-soft);
  opacity: 1;
  transition: opacity .32s var(--ease-out), translate .32s var(--ease-out);
}
@starting-style { .node .note { opacity: 0; translate: -50% -6px; } }
.node .note[hidden] { display: none; }

/* ---- the lens ---- */
.node.is-lit   { --lens-scale: 1.14; }
.node.is-lit .glyph { color: var(--chalk); filter: url(#chalk-line) drop-shadow(0 0 12px rgb(230 227 216 / .3)); }
.node.is-lit .cap   { color: var(--chalk-soft); }
.node.is-dimmed { --lens-scale: .88; opacity: .3; }
.node.is-dimmed .glyph { filter: url(#chalk-line) blur(.4px); }

/* ------------------------------------------------------- articles as marks
   Each paper is a small chalk mark orbiting the subjects it belongs to, a
   third the size of a drawing, joined to each of them by a thinner line. Its
   title is written beside it only while you are pointing at it — sixteen of
   them therefore cost the board no reading at rest. */
.sats { position: absolute; inset: 0; z-index: 2; pointer-events: none; }
.sat {
  position: absolute;
  inline-size: var(--size, 48px);
  margin-inline-start: calc(var(--size, 48px) / -2);
  margin-block-start: calc(var(--size, 48px) / -2);
  display: block;
  pointer-events: auto;
  text-decoration: none;
  color: #8a918a;
  transition: color .32s var(--ease-out), opacity .4s var(--ease-out),
              scale .5s var(--spring);
}
.sat .glyph {
  display: block; inline-size: 100%; aspect-ratio: 1;
  color: inherit;
  filter: url(#chalk-line);
  transition: filter .32s var(--ease-out);
}
.sat .glyph svg { display: block; inline-size: 100%; block-size: 100%; overflow: visible; }
/* the title, only while you are pointing at it */
.sat .cap {
  position: absolute;
  inset-block-start: calc(100% + .25rem);
  inset-inline-start: 50%; translate: -50% 0;
  inline-size: max-content; max-inline-size: 20ch;
  text-align: center;
  font-family: var(--sans); font-size: .64rem; line-height: 1.32;
  letter-spacing: .02em; color: var(--chalk);
  opacity: 0; pointer-events: none;
  transition: opacity .28s var(--ease-out);
}

/* the thinner line from a mark to each of its subjects */
/* Not drawn at rest. Thirty-five lines across the middle of the board is a
   thicket, and the structure is not what you look at first — it is what you
   ask for. Point at a drawing or a mark and its own lines appear. */
.tie {
  fill: none; stroke: var(--chalk-hair); stroke-width: .9;
  stroke-linecap: round;
  filter: url(#chalk-line);           /* chalk, like everything else drawn here */
  /* hidden outright rather than merely transparent, so the chalk filter costs
     nothing on the thirty-odd ties that are not being looked at */
  opacity: 0; visibility: hidden;
  transition: stroke .3s var(--ease-out), stroke-width .3s var(--ease-out),
              opacity .3s var(--ease-out), visibility 0s .3s;
}

/* ---- pointing at anything lights it and everything it is joined to ---- */
.sat.is-near, .sat:hover, .sat:focus-visible {
  color: var(--chalk); z-index: 9; scale: 1.16;
}
.sat.is-near .glyph, .sat:hover .glyph {
  filter: url(#chalk-line) drop-shadow(0 0 12px rgb(230 227 216 / .4));
}
/* the title belongs to the mark you are actually pointing at — not to the
   whole family that lights with it, which would be eight titles at once */
.sat:hover .cap, .sat:focus-visible .cap { opacity: 1; }
.node.is-near .glyph {
  color: var(--chalk);
  filter: url(#chalk-line) drop-shadow(0 0 15px rgb(230 227 216 / .45));
}
.node.is-near .cap { color: var(--chalk); }
:is(.tie, .wire).is-near { stroke: var(--chalk-ochre); opacity: 1; }
.tie.is-near  { stroke-width: 1.25; visibility: visible; transition-delay: 0s; }
.wire.is-near { stroke-width: 2.6; }    /* a line between two subjects is a heavier claim */

/* the lens carries the marks with their subjects */
.sat.is-lit   { color: var(--chalk); }
.sat.is-dimmed { opacity: .26; }

/* -------------------------------------------------------- the chalk tray */
.tray {
  position: absolute;
  inset-inline: 0;
  inset-block-end: 0;
  z-index: 4;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: clamp(.6rem, 2vw, 2rem);
  padding: 1rem clamp(1.4rem, 5vw, 5.5rem) 1.15rem;
  /* the physical ledge: a lit top edge and a shadow beneath it */
  background: linear-gradient(180deg, rgb(232 236 230 / .1), rgb(0 0 0 / .34) 46%, rgb(0 0 0 / .56));
  border-block-start: 1px solid rgb(232 236 230 / .18);
  box-shadow: 0 -16px 30px -20px rgb(0 0 0 / .95) inset;
}

.lenses { display: flex; flex-wrap: wrap; align-items: center; gap: .35rem .55rem; }
.lenses .lead {
  font-family: var(--sans); font-size: .62rem; letter-spacing: var(--tracking-label);
  text-transform: uppercase; color: var(--chalk-faint); margin-inline-end: .3rem;
}

.lens {
  appearance: none;
  border: 0;
  background: none;
  cursor: pointer;
  padding: .3rem .18rem;
  font-family: var(--sans);
  font-size: .74rem;
  letter-spacing: .03em;
  color: var(--chalk-faint);
  border-block-end: 1.5px solid transparent;
  transition: color .28s var(--ease-out), border-color .28s var(--ease-out);
}
.lens:hover { color: var(--chalk-soft); }
.lens[aria-pressed="true"] {
  color: var(--chalk-ochre);
  border-block-end-color: var(--chalk-ochre);
}

.tray .links { margin-inline-start: auto; display: flex; align-items: center; gap: 1.15rem; }
.tray .links a {
  font-family: var(--sans);
  font-size: .74rem;
  letter-spacing: .04em;
  color: var(--chalk-soft);
  text-decoration: none;
  border-block-end: 1px solid transparent;
  transition: color .25s, border-color .25s;
}
.tray .links a:hover { color: var(--chalk); border-block-end-color: var(--chalk-soft); }
.tray .links a.cv {
  color: var(--chalk);
  border-block-end-color: rgb(230 227 216 / .35);
}

/* the stub of chalk resting on the ledge — pure ornament, and the eraser */
.tray .chalk-stub, .tray .eraser {
  position: absolute; inset-block-start: -7px;
  block-size: 8px; border-radius: 2px; pointer-events: none;
}
.tray .chalk-stub {
  inline-size: 30px; block-size: 7px; inset-inline-end: 20.5%;
  background: linear-gradient(180deg, #d9d6ca, #8f8d84);
  rotate: -1.6deg;
  box-shadow: 0 1px 5px rgb(0 0 0 / .6), 0 0 10px rgb(230 227 216 / .12);
}
.tray .eraser {
  inline-size: 40px; block-size: 10px; inset-block-start: -9px; inset-inline-end: 12.5%;
  background: linear-gradient(180deg, #3f4441 0 52%, #b3b0a6 52%);
  rotate: .7deg;
  box-shadow: 0 1px 5px rgb(0 0 0 / .6);
}

/* ------------------------------------------------------- draw-on entrance */
/* Every path is dashed to its own length and then drawn in. Because the
   figures are hand-authored paths rather than a particle system, the board
   fills the way a board actually fills. */
@keyframes draw { to { stroke-dashoffset: 0; } }
@keyframes fade-in { from { opacity: 0 } to { opacity: 1 } }

.is-drawing .glyph path {
  stroke-dasharray: var(--len);
  stroke-dashoffset: var(--len);
  animation: draw var(--dur, .5s) var(--ease-out) forwards;
  animation-delay: var(--delay, 0s);
}
.is-drawing .node .cap,
.is-drawing .node .note { animation: fade-in .5s var(--ease-out) both; animation-delay: var(--cap-delay, 1s); }
.is-drawing :is(.wire, .tie) {
  stroke-dasharray: var(--len);
  stroke-dashoffset: var(--len);
  animation: draw .7s var(--ease-out) forwards;
  animation-delay: var(--delay, 0s);
}
.is-drawing .heading, .is-drawing .tray { animation: fade-in .9s var(--ease-out) both; }
.is-drawing .heading { animation-delay: .15s; }
.is-drawing .tray    { animation-delay: 1.5s; }

/* ------------------------------------------------------------- responsive */
/* On a phone the board splits in two. The research arc is a graph — five
   figures joined by chalk lines because the work really is joined — and it
   keeps its shape at the top of the page, renormalised to the width of the
   screen so every figure holds its place relative to the others. Everything
   below it was never joined to anything, so it becomes the legible column it
   already was. What survives the small screen is the one claim the
   composition makes: that these five belong together and the others stand
   alone. */
/* Keep this query identical to COMPACT_MQ in board.config.js. */
@media (max-width: 720px), (pointer: coarse) and (max-width: 900px), (pointer: coarse) and (max-height: 540px) {
  .board { min-block-size: auto; padding-block-end: 1rem; }
  /* A phone's board is the whole scrolling page — several screens tall. As a
     layer of its own at that size it is more texture than a phone will keep,
     and a dropped layer is an empty slate mid-scroll. */
  .chalk { will-change: auto; }
  .heading { position: static; max-inline-size: none; padding: 3.2rem 1.4rem 0; }
  .heading .line { max-inline-size: 34ch; white-space: normal; }
  .field { position: relative; inset: auto; }

  /* ---- the constellation ---- */
  /* --constellation is the graph's own aspect ratio, measured from the
     authored positions and handed here by the engine, so the group is never
     squashed into a box of the wrong shape. */
  .nodes {
    position: relative;
    inline-size: 100%;
    /* a phone on its side is wide but short: past this the constellation
       only grows taller than the screen, and its captions no larger */
    max-inline-size: 36rem;
    margin-inline: auto;
    aspect-ratio: var(--constellation, 1.22);
    margin-block-start: 1.6rem;
  }
  .wires {
    display: block;
    inset: 0 auto auto 50%;
    translate: -50% 0;
    max-inline-size: 36rem;
    inline-size: 100%; block-size: auto;
    aspect-ratio: var(--constellation, 1.22);
  }
  /* the caption needs more room than the figure, so the node is widened and
     the figure re-centred inside it */
  /* wide enough that a caption wraps rather than running off the edge of a
     narrow screen, and centred on the figure it belongs to */
  .nodes .node {
    inline-size: 27%;
    margin-inline-start: -13.5%;
    margin-block-start: calc(var(--size) / -2);
  }
  .nodes .node .glyph { inline-size: var(--size); margin-inline: auto; }
  .nodes .node .cap {
    margin-block-start: .2rem;
    font-size: .56rem; letter-spacing: .03em; line-height: 1.28;
  }
  .nodes .node .note { display: none; }   /* no hover on a phone; the tap is the link */

  /* ---- everything that was never connected ---- */
  .sats, .tie { display: none; }   /* no room for sixteen marks on a phone */
  .roster { display: block; padding: 1.4rem 1.4rem 1rem; }
  .roster .node {
    position: static;
    inline-size: auto; margin: 0;
    translate: none !important;
    scale: 1 !important;
    display: grid;
    grid-template-columns: 62px 1fr;
    align-items: center;
    gap: 1rem;
    padding: .85rem 0;
    border-block-end: 1px solid rgb(255 255 255 / .07);
  }
  .roster .node .glyph { inline-size: 62px; }
  .roster .node .cap {
    margin: 0; text-align: start; font-size: .82rem; letter-spacing: .04em;
    color: var(--chalk);
  }
  .roster .node .note {
    pointer-events: auto;          /* in the list the whole row is the link */
    position: static; translate: none; text-align: start;
    inline-size: auto; max-inline-size: 100%;   /* max-content would overflow */
    padding: .15rem 0 0; font-size: .76rem;
    grid-column: 2;
  }

  .node.is-dimmed { opacity: .42; }
  .tray { position: static; padding-inline: 1.4rem; }
  .tray .links { margin-inline-start: 0; flex-wrap: wrap; }
  .tray .chalk-stub, .tray .eraser { display: none; }
}

/* --------------------------------------------------------- reduced motion */
@media (prefers-reduced-motion: reduce) {
  .node { transition-duration: .01ms; }
  .is-drawing .glyph path,
  .is-drawing :is(.wire, .tie) { animation: none; stroke-dashoffset: 0; }
  .is-drawing .papers { animation-duration: .01ms; }
  .is-drawing :is(.heading, .tray, .node .cap, .node .note) { animation-duration: .01ms; }
  .node .note { transition: none; }
}
