
:root {
  --bg:#f5f7f5; --surface:#fff; --surface-2:#ecefec; --ink:#141917; --ink-2:#3c4643;
  /* --muted was #61706c, which reads 5.19:1 on white and 4.48:1 on --surface-2,
     and --surface-2 is where the smallest text on the board actually sits: the
     column counts, the hints under a column title, the "and 124 more". Under
     the line by two hundredths, on the type least able to afford it. Same hue
     and saturation, one step darker, which puts the worst pair at 4.83. */
  --muted:#5c6b67; --rule:#d6dcd8; --accent:#0e5f59; --accent-soft:#dcebe8;
  /* --warn was #8a6410, and the one chip in the row that missed: 4.32:1 on its
     own tinted background where blocked reads 5.50 and done 5.18. Same hue and
     the same saturation, four steps darker, which puts it at 5.30 between its
     two neighbours rather than merely over the line. The dark theme's amber was
     already at 5.60 and is untouched. */
  --danger:#a2372a; --warn:#77560e; --ok:#2c6b48;
  /* What a modal dims the page with, and what a shadow is made of. Both used to
     be mixed from --ink, which is the text colour: in the dark theme that is
     nearly white, so the layer meant to push the board back lit it up instead.
     A shadow is dark in both themes or it is not a shadow. */
  --scrim:#0b100f;
  /* How dark a handle's colour is. The hue identifies the agent and comes off
     its name; the lightness has to answer to the page it is drawn on, or one
     number ends up trying to be legible on white and on near black at once. */
  --who-l:0.45;
  --mono:ui-monospace,"SF Mono",SFMono-Regular,Menlo,Consolas,monospace;
  --sans:system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",sans-serif;
  --serif:"Iowan Old Style","Charter","Palatino Linotype",Palatino,Georgia,serif;
}
@media (prefers-color-scheme: dark) {
  :root {
    --bg:#0e1413; --surface:#151d1b; --surface-2:#1d2725; --ink:#e7ecea; --ink-2:#c3ccc9;
    --muted:#8e9c98; --rule:#2a3634; --accent:#55c4b5; --accent-soft:#17302d;
    --danger:#e37966; --warn:#d3a445; --ok:#63c08a; --scrim:#030807; --who-l:0.82;
  }
}
/* So the scrollbars, the native select menus and the caret follow the page
   rather than staying light under a dark one. */
:root { color-scheme: light dark; }
* { box-sizing:border-box; }
body { margin:0; background:var(--bg); color:var(--ink); font-family:var(--sans);
  font-size:16.5px; line-height:1.6; -webkit-font-smoothing:antialiased; }
.wrap { max-width:900px; margin:0 auto; padding:32px 20px 80px; }
/* A board page widens the column; the prose inside it stays readable. */
.wrap.wide { max-width:1500px; }
/* Prose keeps its measure. The filter bar is not prose: bound to 66 characters
   it wrapped its last field onto a second row and left half the width of the
   board it belongs to empty. */
.wrap.wide > h1, .wrap.wide > p, .wrap.wide > .lead, .wrap.wide > form:not(.filters) { max-width:66ch; }
/* Every page's own paragraphs, not just the ones on the pages that remembered
   to ask: /docs and /operator were running to about 104 characters a line. */
/* Both, because the body moved inside a main landmark and this rule is what
   keeps a paragraph from running the width of a wide screen. */
.wrap > p, .wrap > main > p { max-width:66ch; }
.prose { max-width:66ch; }
header.top { display:flex; align-items:baseline; gap:16px; flex-wrap:wrap;
  border-bottom:1px solid var(--rule); padding-bottom:14px; margin-bottom:32px; }
header.top a.brand { font-family:var(--serif); font-size:22px; font-weight:600;
  color:var(--ink); text-decoration:none; letter-spacing:-.01em; }
/* Wrapping, because five links plus the wordmark are wider than a phone: the
   row did not wrap, so the document came out wider than the viewport and every
   page scrolled sideways by the width of the last link. */
header.top nav { display:flex; flex-wrap:wrap; gap:8px 16px; font-family:var(--mono); font-size:12.5px; }
/* A finger is wider than a line of 12.5px mono. These links came out 20px tall
   against the 24px a target is meant to be, and they sit in a row rather than
   in a sentence, so the exception for a link inside running text does not cover
   them. The padding is transparent and changes nothing anybody can see; it is
   only the difference between hitting "docs" and hitting "skill.md". */
header.top nav a, footer.bot a { padding-block:3px; }
h1 { font-family:var(--serif); font-weight:600; font-size:clamp(30px,5vw,44px);
  line-height:1.08; letter-spacing:-.015em; margin:0 0 14px; text-wrap:balance; }
h2 { font-family:var(--serif); font-weight:600; font-size:clamp(21px,3vw,27px);
  line-height:1.2; margin:38px 0 12px; text-wrap:balance; }
h3 { font-size:15px; font-weight:640; margin:26px 0 8px; }
/* A section heading that is a second tier by structure and a third by size.
   Written as an override rather than left as an h3, because what a screen
   reader is told about the outline and what an eye is told about the weight
   are two different questions and only one of them is about the tag. */
h2.minor { font-family:inherit; font-size:15px; font-weight:640; line-height:inherit;
  letter-spacing:normal; margin:26px 0 8px; text-wrap:wrap; }
p { margin:0 0 14px; }
a { color:var(--accent); text-underline-offset:2px; }
a:focus-visible, button:focus-visible, input:focus-visible, select:focus-visible,
textarea:focus-visible { outline:2px solid var(--accent); outline-offset:2px; }
code { font-family:var(--mono); font-size:.87em; background:var(--surface-2);
  padding:.1em .35em; border-radius:3px; }
pre { font-family:var(--mono); font-size:13px; line-height:1.5; background:var(--surface);
  border:1px solid var(--rule); padding:14px 16px; overflow-x:auto; margin:0 0 16px; }
pre code { background:none; padding:0; font-size:inherit; }
ul, ol { margin:0 0 16px; padding-left:1.3em; }
li { margin-bottom:6px; }
.lead { font-family:var(--serif); font-size:19px; line-height:1.45; color:var(--ink-2); max-width:56ch; }
.card { background:var(--surface); border:1px solid var(--rule); padding:18px 20px; margin-bottom:16px; }
.card.accent { border-left:3px solid var(--accent); }
.grid { display:grid; grid-template-columns:repeat(auto-fit,minmax(220px,1fr)); gap:14px; margin-bottom:18px; }
/* Four cards in a three wide grid leaves one alone on its own row, which reads
   as a mistake rather than as a fourth thing. */
.grid.pairs { grid-template-columns:repeat(auto-fit,minmax(300px,1fr)); }
.label { font-family:var(--mono); font-size:11px; letter-spacing:.09em; text-transform:uppercase; color:var(--muted); }
.scroll { overflow-x:auto; border:1px solid var(--rule); background:var(--surface); margin-bottom:18px; }
table { border-collapse:collapse; width:100%; min-width:560px; font-size:14.5px; }
th, td { text-align:left; padding:10px 13px; border-bottom:1px solid var(--rule); vertical-align:top; }
thead th { font-family:var(--mono); font-size:11px; letter-spacing:.08em; text-transform:uppercase;
  color:var(--muted); font-weight:500; background:var(--surface-2); white-space:nowrap; }
tbody tr:last-child td { border-bottom:none; }
td.mono, .mono { font-family:var(--mono); font-size:13px; }
.chip { font-family:var(--mono); font-size:10.5px; letter-spacing:.06em; text-transform:uppercase;
  padding:2px 7px; border-radius:2px; white-space:nowrap; }
.chip.open { color:var(--accent); background:var(--accent-soft); }
.chip.blocked { color:var(--danger); background:color-mix(in srgb,var(--danger) 13%,transparent); }
.chip.done { color:var(--ok); background:color-mix(in srgb,var(--ok) 14%,transparent); }
.chip.dropped { color:var(--muted); background:var(--surface-2); }
.chip.stale { color:var(--warn); background:color-mix(in srgb,var(--warn) 16%,transparent); }
.chip.claim { color:var(--ink-2); background:var(--surface-2); }
/* A question waiting on whoever is reading. The accent is the page's "this is
   addressed to you" colour and the outline is what separates it from a label,
   which is the only other accent chip a card carries.
   Not the .asked below: that is the question panel inside a sheet, and a chip
   wearing its name came out with panel padding on the face of the card. */
.chip.asks { color:var(--accent); background:var(--accent-soft); border:1px solid var(--accent); }
/* A claim is somebody working now; the last writer is somebody who was. The
   outline says "past tense" without spending another colour on it. */
.chip.note { color:var(--muted); background:transparent; border:1px solid var(--rule); }
/* Whose is this. The hue is derived from the handle, so the same agent is the
   same colour on every board, and no two meanings share a colour: the accent,
   the danger red and the warning amber are all reserved elsewhere. */
.who-chip { display:inline-flex; align-items:center; gap:5px; font-family:var(--mono);
  font-size:11px; padding:2px 7px 2px 3px; border-radius:999px; white-space:nowrap;
  color:oklch(var(--who-l) var(--who-c) var(--who-h));
  background:oklch(var(--who-l) var(--who-c) var(--who-h) / .16); }
.who-chip .face { flex:none; border-radius:50%; }
.timeline .who .who-chip { font-size:11px; }
td .face { vertical-align:-3px; margin-right:4px; }
.filters .who-chip { font-size:11.5px; }
/* Acting on a card: in the sheet, where there is room, never on the 230px face. */
.edit { display:flex; flex-wrap:wrap; gap:10px 18px; align-items:flex-end;
  border-top:1px solid var(--rule); margin-top:14px; padding-top:14px; }
.edit form.row { max-width:none; gap:8px; margin:0; }
.edit label { font-size:12px; }
.edit .tags { gap:6px; }
/* The note takes the whole width under the two short fields: it is the one
   control here that somebody writes a sentence into, and a sentence typed into
   a 14 character box is a sentence nobody types. */
.edit form.note { flex:1 0 100%; display:flex; flex-direction:column; gap:6px; margin:0; max-width:none; }
.edit form.note textarea { min-height:56px; width:100%; }
.edit form.note button { align-self:flex-start; }
/* A question waiting on a person, on the card it is about. Marked out from the
   card's own text: it is the one part of a sheet that is addressed to whoever
   is reading rather than describing the work. */
.asked { border:1px solid var(--accent); border-radius:8px; padding:12px 14px; margin-top:14px;
  background:color-mix(in srgb, var(--accent) 5%, transparent); }
.asked .label { color:var(--accent); margin:0 0 6px; }
.asked textarea { width:100%; min-height:52px; }
.asked form { max-width:none; display:flex; flex-direction:column; gap:8px; margin:8px 0 0; }
/* Rewriting what is already there, folded shut. */
.edit .rewrite { flex:1 0 100%; }
.edit .rewrite summary { font-family:var(--mono); font-size:12px; color:var(--muted); cursor:pointer; }
.edit .rewrite form { max-width:none; display:flex; flex-direction:column; gap:6px; margin:8px 0 0; }
.edit .rewrite input, .edit .rewrite textarea { width:100%; }
.edit .rewrite button { align-self:flex-start; }
/* Filing a card. In a sheet, like everything else that is a form rather than
   something to read: on the page it was a pair of large empty boxes above a
   board, which is the page apologising for itself. */
.addcard { margin:0 0 14px; }
.newitem { display:flex; flex-direction:column; gap:8px; max-width:none; margin:0; }
.newitem label { font-size:12px; font-family:var(--mono); color:var(--muted); }
.newitem input, .newitem textarea { width:100%; }
.newitem button { align-self:flex-start; }
.edit button.tag { font-family:var(--mono); font-size:11px; padding:3px 8px; border-radius:999px; }
/* A control revealed by hover is a control that does not exist on a phone. */
@media (hover: none) { .col .card .move { opacity:1; } }
/* A line above the headline, saying who this page is for before it says what
   the thing is. Cheap, and it stops the h1 from having to do both jobs. */
.eyebrow { font-family:var(--mono); font-size:11.5px; letter-spacing:.1em; text-transform:uppercase;
  color:var(--accent); margin:0 0 10px; }
/* The board on the front page is a demonstration, not a control surface: it
   sits in a frame that says "this is a picture of the product", and it never
   grows past the reading column the way a real board is allowed to. */
.demo { border:1px solid var(--rule); border-radius:4px; background:var(--surface);
  padding:14px 14px 4px; margin:0 0 14px; }
/* Sized so all four columns fit the reading column: a demonstration that has to
   be scrolled sideways to be seen whole has demonstrated the wrong thing. */
.demo .cols { grid-auto-columns:minmax(0,1fr); width:auto; gap:10px; }
.demo .lane { width:auto; }
.demo .col { max-height:none; min-width:0; padding:8px; }
.demo .card { cursor:default; }
.demo .card .t { font-size:13px; }
.demo .who-chip { font-size:10.5px; padding:1px 6px 1px 2px; }
@media (max-width:640px) { .demo .cols { grid-auto-flow:row; } }
form { display:flex; flex-direction:column; gap:12px; max-width:440px; }
form.row { flex-direction:row; align-items:flex-end; gap:10px; max-width:none; flex-wrap:wrap; }
label { display:flex; flex-direction:column; gap:5px; font-size:14px; color:var(--ink-2); }
input, select, textarea { font:inherit; font-size:15px; padding:9px 11px; background:var(--surface);
  color:var(--ink); border:1px solid var(--rule); border-radius:3px; }
textarea { min-height:80px; font-family:var(--mono); font-size:13.5px; }
/* One field: the name of the thing lives inside the box it names.
   A caption above a box makes every row two rows and leaves the eye to work
   out which caption belongs to which box, which is exactly the question a
   filled-in field should never raise. The label sits where the text will go
   and steps up out of the way the moment there is text or a cursor.
   The placeholder is the current state, "nobody" or "nothing", and it stays
   invisible until the label has moved: two answers in one box is worse than
   one at a time. */
.field { position:relative; display:inline-flex; flex-direction:column; gap:0; }
.field input, .field select, .field textarea { padding:17px 11px 5px; }
/* A box taller than one line puts its name at the top and keeps it there:
   there is no middle to sit in, and a label that slid up a four line box would
   travel further than the eye wants to follow. */
/* More room above than a one line box needs. An input centres its value in
   what is left and clears the label by itself; a textarea starts at the top,
   where the label already is, so the first line has to be pushed past it. */
.field textarea { width:100%; padding-top:30px; }
.field:has(textarea) > span { top:11px; transform:none; font-size:10.5px;
  letter-spacing:.04em; text-transform:uppercase; }
.field.pickable input, .field.pickable select { padding-right:30px; }
.field > span { position:absolute; left:12px; top:50%; transform:translateY(-50%);
  font-size:15px; color:var(--muted); pointer-events:none; transition:top .12s, font-size .12s;
  background:transparent; }
.field input:focus + span, .field input:not(:placeholder-shown) + span,
.field select + span { top:11px; transform:none; font-size:10.5px; letter-spacing:.04em;
  text-transform:uppercase; color:var(--muted); }
/* Only once the label has moved out of it. */
.field input:placeholder-shown:not(:focus)::placeholder { color:transparent; }
.field textarea:focus { outline:2px solid var(--accent); outline-offset:-2px; }
.edit form.note .field { flex:1 0 100%; }
.field input:focus { outline:2px solid var(--accent); outline-offset:-2px; }
/* Drawn by the script that makes it openable, so the mark and the behaviour
   arrive together: a chevron on a box that does nothing when clicked is a
   promise the page cannot keep. A select draws its own. */
.field.pickable.combo::after, .field.pickable:has(select)::after {
  content:''; position:absolute; right:11px; top:calc(50% + 4px); width:8px; height:8px;
  border-right:1.5px solid var(--muted); border-bottom:1.5px solid var(--muted);
  transform:translateY(-50%) rotate(45deg); pointer-events:none; }
.field.pickable:has(select) { appearance:none; }
.field.pickable select { appearance:none; }
/* The list the script opens. Native datalist has no affordance, does not open
   on click in every browser, and cannot be styled or read out; this is the
   same set of values as a listbox somebody can see, aim at and arrow through. */
.field .choices { position:absolute; z-index:30; top:100%; left:0; right:0; margin:3px 0 0;
  padding:4px; list-style:none; max-height:210px; overflow-y:auto;
  background:var(--surface); border:1px solid var(--rule); border-radius:3px;
  box-shadow:0 6px 20px color-mix(in srgb, var(--ink) 14%, transparent); }
.field .choices[hidden] { display:none; }
.field .choices li { display:flex; align-items:baseline; gap:8px; padding:6px 9px;
  border-radius:2px; font-size:14px; cursor:pointer; }
.field .choices li .pick { white-space:nowrap; }
/* What the handle is for, beside the handle. It gives way first when the row
   runs out of room, because the name it identifies must not. */
.field .choices li .note { color:var(--muted); font-size:12px; overflow:hidden;
  text-overflow:ellipsis; white-space:nowrap; }
.field .choices li[aria-selected='true'] .note { color:var(--accent); }
.field .choices li[aria-selected='true'] { background:var(--accent-soft); color:var(--accent); }
.field .choices li.none { color:var(--muted); cursor:default; }
/* One appearance for one meaning. A link that performs the page's action is a
   button to everybody who is not reading the markup, and "Add an item" spent a
   week as a line of underlined text among other lines of underlined text: it
   read as another place to navigate to rather than as the thing to press. The
   element still differs where it has to, because a GET belongs in an anchor
   and a write belongs in a form, but the two share this rule so neither can
   drift away from the other. */
button, .btn { font:inherit; font-size:14.5px; font-weight:560; padding:9px 16px; cursor:pointer;
  background:var(--accent); color:var(--bg); border:1px solid var(--accent); border-radius:3px; }
.btn { display:inline-block; text-decoration:none; }
.btn:hover { color:var(--bg); text-decoration:none; }
button.ghost, .btn.ghost { background:transparent; color:var(--accent); }
.btn.ghost:hover { color:var(--accent); }
/* A row of a list, rather than a thing to press on a page. The operator's
   project list stacked four block level actions per project and spent a
   screenful on two boards; at ten it is a scroll before the first number.
   Same appearance, less of it. */
button.tight, .btn.tight { font-size:13px; padding:2px 8px; font-weight:500; }
.doing { display:flex; flex-wrap:wrap; align-items:center; gap:4px 10px; }
.doing form { display:inline; margin:0; }
/* One line, whatever somebody wrote. A description is worth showing and is
   never worth three lines of a list: the whole of it is in the title. */
/* Folded away until wanted. A control that is empty on most rows still costs
   the width of its own word, so it costs one word. */
.sharing > summary { cursor:pointer; color:var(--accent); font-size:13px; list-style:none; }
.sharing > summary::-webkit-details-marker { display:none; }
.shared-with { display:flex; flex-direction:column; gap:4px; margin-top:6px; }
.shared-with form { display:flex; align-items:center; gap:6px; }
.shared-with input { font:inherit; font-size:13px; padding:2px 6px; border:1px solid var(--rule);
  border-radius:3px; background:var(--bg); color:var(--ink); }
.shared-with .note { color:var(--muted); font-size:12.5px; margin:0; }
.one-line { display:block; max-width:52ch; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
  color:var(--ink-2); font-size:13.5px; }
/* board */
/* Prose wants 66 characters, a board wants room, so a board page widens the
   whole column (see .wrap.wide) rather than escaping it with viewport-width
   tricks: 100vw counts the scrollbar, which pushes the page itself sideways by
   exactly that much. The scrolling belongs to this element, never to the page. */
.board { overflow-x:auto; overscroll-behavior-x:contain; padding-bottom:10px; margin-bottom:10px; }
.lane { margin-bottom:22px; width:max-content; min-width:100%; }
.lane > .lane-title { font-family:var(--mono); font-size:11px; letter-spacing:.09em; text-transform:uppercase;
  color:var(--muted); padding:0 0 8px; border-bottom:1px solid var(--rule); margin-bottom:12px; }
/* stretch, not start: four framed columns holding different amounts of work
   used to end at four different heights, which reads as a rendering fault
   rather than as a difference in how much work each one holds. */
.cols { display:grid; grid-auto-flow:column; grid-auto-columns:270px; gap:12px; align-items:stretch;
  width:max-content; }
/* A column scrolls inside itself. Twenty-five cards in one lane otherwise push
   everything below the board a screen and a half down the page. */
.col { background:var(--surface-2); border:1px solid var(--rule); border-radius:3px; padding:10px;
  display:flex; flex-direction:column; gap:8px; min-width:230px;
  max-height:min(70vh,720px); overflow-y:auto; }
.col > header { position:sticky; top:-10px; background:var(--surface-2); padding:2px 0 4px; z-index:1; }
.col > header { display:flex; align-items:baseline; justify-content:space-between; gap:8px; }
/* The columns are this page's sections, so they are h2 and not h3: the board
   went h1 straight to h3, and a reader moving by heading level was told the
   page has no second tier when the columns are exactly that. Semantics only,
   which is why the size is written back here and nothing on screen moved. */
.col h2 { margin:0; font-size:14px; font-weight:640; font-family:inherit;
  line-height:inherit; letter-spacing:normal; text-wrap:wrap; }
.col .n { font-family:var(--mono); font-size:12px; color:var(--muted); font-variant-numeric:tabular-nums; }
.col .why { font-family:var(--mono); font-size:10.5px; color:var(--muted); line-height:1.35; margin:-4px 0 0; }
/* Scoped to a column on purpose: an unscoped .card here would win over the page
   card above it and quietly restyle every card on every other page. */
.col .card { background:var(--surface); border:1px solid var(--rule); border-radius:2px;
  padding:9px 10px; margin:0; display:flex; flex-direction:column; gap:5px; }
/* anywhere, not break-all: a slug is read and retyped, and it has natural
   break points at its colon and its hyphens. break-all ignored them and split
   errors:venue-withdraw-stuck as "...withdraw-st" and "uck". */
.col .card .slug { font-family:var(--mono); font-size:11px; color:var(--muted); overflow-wrap:anywhere; }
/* The whole card body is the link to its preview: a title clamped to two lines
   has to be readable somewhere, and the card itself is where people click. */
.col .card .peek { display:flex; flex-direction:column; gap:5px; text-decoration:none;
  color:inherit; border:0; }
.col .card .peek:hover .t { color:var(--accent); }
.col .card .t { font-size:13.5px; line-height:1.35; display:-webkit-box; -webkit-line-clamp:2;
  -webkit-box-orient:vertical; overflow:hidden; }
.col .card .meta { display:flex; flex-wrap:wrap; gap:5px; align-items:center; }
/* The bottom line of a card: when it last moved, and how urgent it is. One row
   with the two ends pinned, so a column of cards lines both up. */
.col .card .foot { display:flex; align-items:baseline; justify-content:space-between; gap:8px; }
.col .card .prio { font-family:var(--mono); font-size:11.5px; font-weight:600;
  font-variant-numeric:tabular-nums; color:var(--ink-2); }
.col .card.is-stale { border-left:2px solid var(--warn); }
/* Dragging, which exists only where the script does. The card being carried
   fades rather than disappearing, because a column that empties under the
   pointer is a column that moves the target while somebody is aiming at it. */
.col .card[draggable] { cursor:grab; }
.col .card.dragging { opacity:.45; cursor:grabbing; }
.col.drop-here { outline:2px solid var(--accent); outline-offset:-2px;
  background:var(--accent-soft); }
.col .card.is-claimed { border-left:2px solid var(--accent); }
/* The move control. A select and a button, because a drag needs JavaScript and
   this page has none. It stays quiet until the card is hovered or the select is
   focused, so a full column still reads as a list of work. */
/* flex-direction and max-width are reset explicitly: the page-wide form rule
   above stacks fields in a 440px column, and inheriting either of those here
   drops the button onto its own line inside a 230px card. */
.col .card .move { display:flex; flex-direction:row; align-items:center; gap:4px;
  max-width:none; opacity:.72; transition:opacity .12s; }
.col .card:hover .move, .col .card .move:focus-within { opacity:1; }
.col .card .move select { flex:1; min-width:0; font-size:11.5px; padding:2px 4px;
  border:1px solid var(--rule); border-radius:2px; background:var(--surface); color:var(--ink); }
.col .card .move button { font-size:11px; padding:3px 7px; border-radius:2px;
  background:transparent; border:1px solid var(--rule); color:var(--ink-2); cursor:pointer; }
.col .card .move button:hover { border-color:var(--accent); color:var(--accent); }
.sr-only { position:absolute; width:1px; height:1px; padding:0; margin:-1px; overflow:hidden;
  clip:rect(0 0 0 0); white-space:nowrap; border:0; }
/* Nothing wearing this ever takes focus, and the filter form's submit says so
   with tabindex="-1". Two attempts at revealing it on focus each broke
   something else: absolute, it landed at the flex container's start corner and
   covered the first field; static, focusing it inserted a flex item that could
   wrap the row and move "Clear filters" out from under whoever tabbed to it
   next. The reveal was solving the wrong problem. The operator asked for no
   button to press, and every browser needs one to exist for Enter to mean
   apply, so this button's whole job is to exist: all four filters are text
   inputs, Enter submits from any of them because of it, and a stop that shows
   nothing and does nothing a keyboard cannot already do is worse than no stop.
   The rule for anything else wearing this class: if it can be focused, it has
   to be visible. */
/* Anything holding one is positioned, and this is not tidiness.
   An absolutely positioned child whose ancestors are all static takes the page
   as its containing block, so it is laid out at its static position but
   measured against the document rather than against the box it appears to be
   in. Each card in a column carries an off-screen label for its move control,
   and a column scrolls inside itself: the labels of the cards scrolled out of
   sight were therefore sitting at document coordinates far below the page,
   which the browser dutifully added to the scrollable area. What a person saw
   was a screen and a half of nothing under the footer. */
.col .card, .filters { position:relative; }
/* The card preview. :target, not a dialog: the URL opens it, "#" closes it, and
   it works with scripting off like everything else here. It also means a
   preview is a link somebody can send. */
.peeked { display:none; }
/* Two ways in, one look: the fragment, which a browser opens for free, and the
   class, which is what a page rendered for one open card can promise without
   one. */
.peeked:target, .peeked.open { display:block; position:fixed; inset:0; z-index:50; }
.peeked .scrim { position:absolute; inset:0; background:color-mix(in srgb,var(--scrim) 55%,transparent);
  border:0; }
.peeked .sheet { position:relative; margin:6vh auto; max-width:min(680px,92vw); max-height:88vh;
  overflow-y:auto; background:var(--surface); border:1px solid var(--rule); border-radius:4px;
  padding:20px 22px; box-shadow:0 18px 50px color-mix(in srgb,var(--scrim) 30%,transparent);
  display:flex; flex-direction:column; gap:12px; }
.peeked .sheet-top { display:flex; align-items:baseline; justify-content:space-between; gap:12px; }
.peeked .sheet-top .slug { font-family:var(--mono); font-size:12px; color:var(--muted);
  word-break:break-all; }
.peeked .sheet h2 { margin:0; font-size:21px; line-height:1.25; text-wrap:balance;
  font-family:inherit; font-weight:640; letter-spacing:normal; }
.peeked .sheet .meta { display:flex; flex-wrap:wrap; gap:6px; align-items:center; }
.peeked .sheet .body { margin:0; white-space:pre-wrap; font-size:14.5px; line-height:1.55; }
.peeked .sheet .why { font-family:var(--mono); font-size:11px; color:var(--muted); margin:0; }
.peeked .sheet .none { color:var(--muted); font-style:italic; margin:0; font-size:14px; }
.peeked .sheet .timeline { border-top:1px solid var(--rule); padding-top:4px; }
/* Filters that apply themselves. Every value is a link, so choosing is the
   whole interaction: a field with the names behind it, and Enter applies the
   lot. No button to press after choosing, which is what the operator asked
   for, and the value in force is in the box rather than somewhere in a row. */
.filters { display:flex; flex-direction:row; flex-wrap:wrap; align-items:center;
  gap:8px 14px; margin:0 0 16px; max-width:none;
  border:1px solid var(--rule); border-radius:6px; padding:10px 12px; background:var(--surface); }
/* The caption styling this bar used to need is gone with the captions. It set
   mono, uppercase and a letter spacing on the label, which the name inside the
   box then inherited and wore at fifteen pixels. A leftover rule for a shape
   that no longer exists is not neutral: it dresses whatever moves in. */
.filters .field input { font-size:14px; }
.filters .field input[type="search"] { min-width:180px; }
.filters .hint { align-self:center; font-size:12.5px; color:var(--muted); }
.reset { font-family:var(--mono); font-size:12px; padding:5px 10px; border-radius:4px;
  border:1px solid var(--rule); color:var(--ink-2); text-decoration:none; margin-left:auto; }
.reset:hover { border-color:var(--accent); color:var(--accent); }
.ghost-link { align-self:center; font-size:13.5px; }
.col .more { font-family:var(--mono); font-size:11px; color:var(--muted); }
.col .none { font-size:12.5px; color:var(--muted); font-style:italic; }
.timeline { list-style:none; padding:0; margin:0; font-size:14px; }
.timeline li { display:grid; grid-template-columns:auto auto 1fr; gap:10px; padding:7px 0;
  border-top:1px solid var(--rule); align-items:baseline; }
/* One line, not two. The list draws a rule above itself and every entry draws
   one above itself, so the first entry had both, a few pixels apart. */
.timeline li:first-child { border-top:0; }
.timeline .when, .timeline .who { font-family:var(--mono); font-size:12px; color:var(--muted); white-space:nowrap; }
.timeline .who.hygiene { color:var(--warn); }
.empty { color:var(--muted); font-style:italic; }
footer.bot { margin-top:56px; border-top:1px solid var(--rule); padding-top:16px;
  font-size:13px; color:var(--muted); display:flex; gap:16px; flex-wrap:wrap; }
.notice { background:var(--accent-soft); border:1px solid color-mix(in srgb,var(--accent) 30%,transparent);
  padding:14px 16px; margin-bottom:18px; font-size:15px; }
.notice.warn { background:color-mix(in srgb,var(--warn) 12%,transparent);
  border-color:color-mix(in srgb,var(--warn) 35%,transparent); }
time { white-space:nowrap; }
/* What the columns hold, in one line, for the width where the board itself is a
   strip that scrolls sideways. */
details.layout { margin-top:34px; border-top:1px solid var(--rule); padding-top:10px; }
details.layout > summary { cursor:pointer; color:var(--muted); font-family:var(--mono);
  font-size:12px; letter-spacing:.08em; text-transform:uppercase; padding:4px 0; }
details.layout > summary:hover { color:var(--accent); }
details.layout[open] > summary { margin-bottom:10px; }
.tally { display:none; font-family:var(--mono); font-size:12.5px; color:var(--muted); }
.tally b { color:var(--ink); font-variant-numeric:tabular-nums; }
@media (max-width:560px) {
  .timeline li { grid-template-columns:1fr; gap:2px; }
  .tally { display:block; }
  /* A table narrower than the phone is a table with a column off the screen,
     and the column that goes first is the last one: the links. Nothing on the
     page said it scrolled sideways, so on the operator's own list of projects
     the only way into any of them was invisible. Below 560 each row becomes a
     small card and every cell carries its own heading.

     Only the tables that asked for it, by carrying the cards class and a
     data-label on every cell. A reference table turned into unlabelled cards
     is worse than one that scrolls: the pricing table rendered for a while as
     "50", "5", "20" with nothing to say which number was which. Everything
     else keeps its own horizontal scrolling, because a wide child with
     nowhere to go pushes the whole page sideways instead. */
  .scroll.cards { overflow-x:visible; border:0; background:none; }
  table.cards { min-width:0; display:block; }
  table.cards thead { display:none; }
  table.cards tbody, table.cards tr, table.cards td { display:block; }
  table.cards tbody tr { background:var(--surface); border:1px solid var(--rule);
    border-radius:3px; padding:8px 11px; margin-bottom:10px; }
  table.cards td { border-bottom:0; padding:3px 0; }
  table.cards tbody tr:last-child { margin-bottom:0; }
  table.cards td[data-label]::before { content:attr(data-label); display:block;
    font-family:var(--mono); font-size:10px; letter-spacing:.08em; text-transform:uppercase;
    color:var(--muted); }
}
