/* A terminal. Dark, monospace, minimal -- the same house style as congo:
   no text-transform anywhere, no capitalisation of any kind. Monospace
   because this is a view onto a command-line tool and the columns should
   line up the way `task list` lines them up. */

:root {
  color-scheme: dark;

  --bg:        #0b0e14;
  --bg-raised: #11161f;
  --bg-hover:  #151b26;
  --rule:      #1f2733;

  --fg:        #c5cdd9;
  --dim:       #6b7684;
  --faint:     #3d4653;

  --green:     #7ee787;   /* prompt, wordmark -- where you are */
  --cyan:      #79c0ff;   /* links */
  --amber:     #d9a441;   /* due soon */
  --red:       #f2777a;   /* overdue */
  --mauve:     #b48ead;   /* projects */

  /* A sequential ramp for practice intensity: one hue, rising in lightness,
     never a rainbow. Stepped so adjacent cells are actually distinguishable --
     checked in OKLab rather than by eye, every neighbouring pair dE >= 13
     against a target of 8, and the top step reads 12.6:1 on the background. */
  --heat-1:    #1c3a24;
  --heat-2:    #2f6b3c;
  --heat-3:    #4fa85c;
  --heat-4:    #7ee787;

  /* The three row actions at rest. Full --green/--amber/--red are for hover;
     these are the quieter versions that sit on every row.

     Stratified by lightness, not only by hue, and that is the whole point:
     green and red are the deuteranopia confusion axis, so hue alone cannot
     separate complete from remove. Measured in OKLab under simulated
     protanopia and deuteranopia -- the worst pair separates by dE 10.0 against
     a target of 8, where a hue-only set collapsed to 3. A truer red for remove
     was tried and rejected: it scored 7.5, colliding with the amber.
     All three clear 3:1 against the background; the glyph shapes remain the
     primary cue and the colour reinforces them. */
  --act-complete: #7ec98d;
  --act-push:     #b5934f;
  --act-remove:   #b2606f;

  --mono: ui-monospace, "JetBrains Mono", "Fira Code", SFMono-Regular, Menlo,
          Consolas, "Liberation Mono", monospace;

  /* The height of a row action. 1.7em is the body line-height, which is what
     the lettered buttons were, so swapping in icons moved nothing. The mobile
     pass raises this to a comfortable touch target and changes nothing else. */
  --tap: 1.7em;

  /* **Hand-set, so it has to be kept true.** Nothing measures the header, and
     on a desktop it is the *command bar* that sets this height rather than the
     nav line -- the bar shares the line and is the taller of the two. Below
     34rem the bar takes a line of its own and the header becomes two lines, so
     this is overridden there. It was not, for as long as there has been a
     phone: the day card's summary pinned 36px *underneath* the header and
     vanished behind it on every scroll, since the header is z-index 5 and the
     summary 2. `header_test.rb` measures the real header against this now. */
  --header-h: 3.55rem;
  /* Anything that pins has to clear the site header, which is itself sticky.
     Flush against it: a gap here is see-through, and rows scroll through the
     sliver between the bar and whatever is pinned below it. */
  --sticky-top: var(--header-h);
  --measure: 64rem;   /* wider than congo: this is a table, not a lyric */
}

* { box-sizing: border-box; }
[hidden] { display: none !important; }

html { background: var(--bg); }

body {
  margin: 0;
  background: var(--bg);
  color: var(--fg);
  font: 13.5px/1.7 var(--mono);
  -webkit-font-smoothing: antialiased;
}

::selection { background: #24405c; color: #fff; }

a { color: var(--cyan); text-decoration: none; }
a:hover { text-decoration: underline; text-underline-offset: 3px; }

main {
  max-width: var(--measure);
  margin: 0 auto;
  padding: 1.75rem 1.25rem 6rem;
}

h1, h2 { font-size: 1rem; font-weight: 800; margin: 2rem 0 0.75rem; }
h1 { color: var(--fg); }
h2 { color: var(--dim); }

/* --- status bar --------------------------------------------------------- */

header.site {
  border-bottom: 1px solid var(--rule);
  background: var(--bg-raised);
  position: sticky;
  top: 0;
  z-index: 5;
  box-shadow: 0 8px 12px -8px rgba(0, 0, 0, 0.75);
}

.bar {
  max-width: var(--measure);
  margin: 0 auto;
  padding: 0.55rem 1.25rem;
  display: flex;
  align-items: center;
  gap: 1rem;
  flex-wrap: wrap;
}

.wordmark { color: var(--dim); font-weight: 800; }
.wordmark:hover { color: var(--fg); text-decoration: none; }
.wordmark.is-here { color: var(--green); }

/* One bar, and it moves: drawn beside every item, coloured on the active one.
   Adding and removing it instead would shift the header sideways on every
   navigation. */
header.site .wordmark::before,
header.site nav a::before {
  content: "▍";
  color: transparent;
  margin-right: 0.35rem;
}
header.site .is-here::before { color: var(--green); }

header.site nav a { color: var(--dim); }
header.site nav a:hover { color: var(--fg); text-decoration: none; }
header.site nav a.is-here { color: var(--green); }
header.site .is-here:hover { text-decoration: none; }

/* --- the prompt --------------------------------------------------------- */

.prompt-box {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  border: 1px solid var(--rule);
  background: var(--bg);
  padding: 0.15rem 0.5rem;
  border-radius: 2px;
  flex: 1 1 auto;
  min-width: 20rem;
}
.prompt-box:focus-within { border-color: var(--green); }
.prompt { color: var(--green); user-select: none; }

.prompt-box input[type="text"] {
  font: inherit;
  color: var(--fg);
  background: transparent;
  border: 0;
  outline: none;
  padding: 0.25rem 0;
  width: 100%;
}
.prompt-box input::placeholder { color: var(--faint); }

/* The command that produced this page, echoed back the way a shell leaves it
   above its own output. It is also the thing to copy into a terminal. */
.echo {
  color: var(--dim);
  margin: 0 0 1.25rem;
  overflow-x: auto;
  white-space: nowrap;
}
.echo .typed { color: var(--fg); }

/* --- messages ----------------------------------------------------------- */

.flash {
  border-left: 2px solid var(--green);
  background: var(--bg-raised);
  color: var(--fg);
  padding: 0.5rem 0.75rem;
  margin: 0 0 1.25rem;
}
.flash.is-alert { border-left-color: var(--red); }

.empty, .count { color: var(--dim); }
.count { margin-top: 1.25rem; }

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

table.tasks {
  width: 100%;
  border-collapse: collapse;
}

table.tasks th {
  text-align: left;
  font-weight: 400;
  color: var(--faint);
  border-bottom: 1px solid var(--rule);
  padding: 0.25rem 0.6rem 0.4rem 0;
}

table.tasks td {
  padding: 0.3rem 0.6rem 0.3rem 0;
  border-bottom: 1px solid var(--bg-raised);
  vertical-align: baseline;
}

table.tasks tbody tr:hover { background: var(--bg-hover); }

.num { text-align: right; width: 1%; white-space: nowrap; padding-right: 0.9rem !important; }
.dim { color: var(--dim); }
.faint { color: var(--faint); }

.due { white-space: nowrap; color: var(--amber); width: 1%; }
.is-overdue, td .is-overdue { color: var(--red); }

.project, a.project { color: var(--mauve); white-space: nowrap; }
.description a { color: var(--fg); }
.description a:hover { color: var(--cyan); }

.tag {
  color: var(--green);
  opacity: 0.75;
  margin-left: 0.4rem;
  white-space: nowrap;
}

/* --- one task ----------------------------------------------------------- */

.task-title { margin-top: 0; }

dl.info {
  display: grid;
  grid-template-columns: 7rem 1fr;
  gap: 0.15rem 1rem;
  margin: 0 0 1.5rem;
}
dl.info dt { color: var(--faint); }
dl.info dd { margin: 0; }
dl.info .uuid { color: var(--dim); }

ul.annotations { list-style: none; padding: 0; margin: 0; }
ul.annotations li { padding: 0.15rem 0; }

/* --- help --------------------------------------------------------------- */

table.reference { border-collapse: collapse; width: 100%; }
table.reference td { padding: 0.3rem 1rem 0.3rem 0; vertical-align: baseline; }
table.reference td.key { white-space: nowrap; }
.key { color: var(--green); }

/* The long set is what a desktop reads; the short one only exists on a phone.
   Declared **before** the media query, not after: both rules are
   `header.site .short` at the same specificity, so putting the default
   afterwards beat the override on source order alone and hid every label at
   every width -- a header of four empty links. */
header.site .short { display: none; }

/* The header's own breakpoint, and its only one: the bar takes a line of its
   own here, and the four labels shorten to the commands that reach the same
   pages. */
@media (max-width: 34rem) {
  .prompt-box { min-width: 100%; order: 3; }

  /* Two lines now, so what pins below the header has to clear both of them. */
  :root { --header-h: 5.85rem; }

  /* `~ proj hist ?`. Measured the way the long set was: 208px against the
     338px the words need, which is what buys the headroom for Android's
     text-scaling setting -- at 130% the words want 427px and these want 258. */
  header.site .long { display: none; }
  header.site .short { display: inline; }
}

/* --- the project tree --------------------------------------------------- */

table.tree { width: 100%; border-collapse: collapse; }

table.tree th {
  text-align: left;
  font-weight: 400;
  color: var(--faint);
  border-bottom: 1px solid var(--rule);
  padding: 0.25rem 0.6rem 0.4rem 0;
}

table.tree td {
  padding: 0.15rem 0.6rem 0.15rem 0;
  vertical-align: baseline;
}

table.tree tbody tr:hover { background: var(--bg-hover); }

/* Depth is carried on the row as a custom property rather than baked into a
   class per level, so a fifth level of nesting needs no new css. */
table.tree .node { padding-left: calc(var(--depth, 0) * 1.25rem); }
table.tree .node a { color: var(--mauve); }

/* A node that exists only because something below it does. It is still a
   working filter -- `project:house` matches everything under house -- so it
   stays a link, just a quieter one. */
table.tree .node a.is-implied { color: var(--dim); }

/* --- the command bar's hint -------------------------------------------- */

/* Swapped by the browser: "esc" while focused, "/" while not. The server
   renders the resting state, so it says something true with no javascript. */
.prompt-box .hint {
  color: var(--faint);
  border: 1px solid var(--rule);
  border-radius: 2px;
  padding: 0 0.3rem;
  font-size: 0.85em;
  user-select: none;
  white-space: nowrap;
}

/* --- history ------------------------------------------------------------ */

/* Completed work reads as a log, so it is grouped under the day it happened
   rather than given a date column repeating the same value down the page. */
/* The day heading is a full-width row of the one table the history page is,
   not a heading above a table of its own. That is what makes the columns line
   up from day to day -- separate tables size their columns independently, so
   every day's description started at its own x. The <h2> stays inside the
   cell so the page keeps its heading outline; the cell carries the look. */
/* Qualified by the table, because `table.tasks th` already dims and shrinks
   every header cell and would otherwise win on specificity -- which it did,
   and the headings came out faint, unspaced and lighter than the rows under
   them. */
table.tasks th.day {
  color: var(--fg);
  font-weight: 800;
  padding: 1.75rem 0 0.3rem;
  border-bottom: 1px solid var(--rule);
}
/* The first group sits under the period nav and needs no gap above it. */
table.tasks tbody:first-child th.day { padding-top: 0.5rem; }
table.tasks th.day h2 { margin: 0; font-size: inherit; font-weight: inherit; color: inherit; }
table.tasks th.day .dim { font-weight: 400; margin-left: 0.5rem; }

/* A heading is not a row you can act on, so it does not light up like one. */
table.tasks tbody tr.day-row:hover { background: transparent; }

/* Nothing here is due, and nothing here is late. The amber that means "due" on
   the pending list would be saying something untrue about a finished task. */
table.tasks.done .due { color: var(--dim); }
table.tasks.done td { border-bottom: none; }

nav.periods { display: flex; gap: 1rem; margin: 0 0 0.5rem; }
nav.periods a { color: var(--dim); }
nav.periods a.is-here { color: var(--green); }
nav.periods a.is-here:hover { text-decoration: none; }

/* --- day cards ---------------------------------------------------------- */

/* The expand control is pushed right with margin-left rather than the row
   being right-aligned, so a row holding only the toggle still puts it on the
   right while /projects can sit its active/all toggle on the left of the same
   line. */
/* The header a listing wears: what this project is, before what is under it.
   Reuses section.card, so it sits in the same box the cards below it do. */
.listing-head .project-title { margin: 0 0 0.5rem; }
.listing-head .stats { color: var(--dim); margin: 0.15rem 0 0; font-size: 0.85rem; }
.listing-head .stats.links { margin-top: 0.6rem; }

.tree-controls {
  display: flex;
  align-items: center;
  gap: 1rem;
  margin: 0 0 0.75rem;
}
.tree-controls .tree-toggle { margin-left: auto; }
.tree-controls nav.periods { margin: 0; }

/* Both glyphs ship inside the anchor and css picks one, the same way the copy
   button swaps its check in. Nothing constructs markup in javascript. */
.tree-toggle {
  display: inline-flex;
  align-items: center;
  color: var(--faint);
  padding: 0.3rem;
  border-radius: 2px;
}
.tree-toggle:hover { color: var(--cyan); text-decoration: none; }
.tree-toggle:focus-visible { outline: 2px solid var(--cyan); outline-offset: 1px; }
.tree-toggle[data-open="true"] .i-expand { display: none; }
.tree-toggle[data-open="false"] .i-collapse { display: none; }

/* A card and its header row, shared by the days on the home page and the
   projects on /projects. The header pins under the site header so whatever the
   card is named stays named for as long as its contents are on screen. Same
   treatment congo gives an album. */
.tree-card {
  border: 1px solid var(--rule);
  margin-bottom: 0.9rem;
  padding: 0 0.6rem;
}

.tree-card > summary,
.tree-card > .card-head {
  position: sticky;
  top: var(--sticky-top);
  z-index: 2;
  display: flex;
  align-items: baseline;
  gap: 0.75rem;
  /* spans the card's padding, so nothing scrolls through beside it */
  margin: 0 -0.6rem;
  padding: 0.7rem 0.6rem 0.45rem;
  background: var(--bg);
  cursor: pointer;
  border-left: 2px solid transparent;
  list-style: none;              /* the default triangle, replaced below */
}
.tree-card > summary::-webkit-details-marker { display: none; }
.tree-card > summary:hover,
.tree-card > .card-head:hover { background: var(--bg-hover); border-left-color: var(--rule); }

/* An open card looks open, the way a tree draws it. */
.tree-card > summary::before,
.tree-card > .card-head::before {
  content: "+";
  color: var(--faint);
  flex: none;
  width: 1ch;
}
.tree-card[open] > summary::before { content: "\2212"; color: var(--dim); }
.tree-card[open] > summary { color: var(--fg); }

/* The count sits where congo puts its ls -l columns: first, right-aligned, so
   the day names start on a common left edge however many tasks each holds. */
.tree-card > summary .col,
.tree-card > .card-head .col {
  color: var(--faint);
  font-size: 0.78rem;
  flex: none;
  min-width: 2.5rem;
  text-align: right;
}
/* The name's colour is the page's, not the card's: a day reads as somewhere to
   go, and a project is --mauve like every other project in this app. */
.day-card .tree-name { color: var(--cyan); font-weight: 600; }
.project-card .tree-name { color: var(--mauve); font-weight: 600; }
.project-card .tree-name.is-implied { color: var(--dim); font-weight: 400; }
.day-card .day-date { color: var(--faint); font-size: 0.78rem; }

/* Today is where you are, and green means that everywhere else in this app. */
.day-card[data-card="today"] .tree-name { color: var(--green); }
.day-card.is-overdue .tree-name { color: var(--red); }
.day-card.is-none .tree-name { color: var(--dim); font-weight: 400; }

/* A project with no sub-projects. Not a <details> that is stopped from
   opening: suppressing the toggle needs pointer-events, which would kill the
   project link inside the header too, and it would still open from the
   keyboard. It is simply a card with a header and nothing under it, so the
   marker column stays for alignment and holds nothing. */
.tree-card.is-leaf > .card-head { cursor: default; }
.tree-card.is-leaf > .card-head:hover { background: none; border-left-color: transparent; }
/* The marker column stays, holding nothing, so the names still line up. */
.tree-card.is-leaf > .card-head::before { content: " "; }

/* What the card holds, said quietly beside the name. */
.tree-card .card-stats {
  color: var(--faint);
  font-size: 0.78rem;
  margin-left: auto;
  flex: none;
}

/* A long project name and its stats do not fit a phone on one line, and
   because neither could shrink the header pushed the whole page sideways.
   The stats drop to their own line rather than being cut or thrown away --
   how many sub-projects there are is the reason the card is worth opening. */
/* Wrapping is released here rather than at 640px, where it used to be. A
   project cell that cannot shrink drags the page sideways, and 640 was too
   narrow a place to start allowing it: measured, a 30-character project name
   scrolled the page at **700px**, which is above the old breakpoint and below
   any width where nowrap was buying anything. 820 is where the note box
   already goes, so it is the width at which a row starts giving things up. */
/* **A description breaks at every width, not only below 820.** Everything
   below is about a narrow screen, and a long *project* name is what prompted
   it -- but a description can carry an unbroken 40-character token at any
   width, and one of Chad's real annotations is a filesystem path. Measured
   with a 40-character word: the page scrolled sideways at **900px and
   1024px**, well above where any of this was looking.

   `anywhere` rather than `break-word`, which is the weaker of the two and
   would not have helped: only `anywhere` reduces the cell's min-content width,
   and the min-content width is exactly what the table algorithm sizes the
   column to. Below 820 the floors beneath keep it from shrinking to nothing;
   above 820 there is width to spare and nothing to starve.

   Found by a test written for the phone layout -- the description takes a full
   line there, so this rule became the only thing between a long word and the
   edge, and asking that question at eight widths answered it for the desktop
   too. */
table.tasks td.description { overflow-wrap: anywhere; }

@media (max-width: 820px) {
  /* A project name is nowrap so it reads as one token on a wide screen, but
     `peeps.carsten-carissa` cannot shrink, and a table cell that cannot shrink
     drags the whole page sideways. On a narrow screen let it break.
     Without this the page scrolled horizontally whenever a long project
     happened to be due -- which made the width test depend on what other tests
     had left lying about rather than on the layout. */
  /* The link inside carries its own nowrap, so overriding only the cell left
     the project 148px wide and squeezed the description to 18px. Both have to
     be told. */
  table.tasks td.project,
  table.tasks td.project a { white-space: normal; overflow-wrap: anywhere; }

  /* `anywhere` lets the cell shrink to nothing, so the table algorithm gave it
     nothing: `bills.bf-credit` came out one character a line. A floor stops
     that without bringing back the problem the rule above exists for -- 5rem
     is far too narrow to drag the page sideways, and long names still break,
     they just break at a readable width. */
  table.tasks td.project { min-width: 4.5rem; }

  /* Both floors, and it has to be both: with only the project's, the
     description starved and `streaming` came out one character a line inside a
     day card -- before this change and after. With only the description's, the
     project starved instead and `bills.apple-tv` did the same. `anywhere` lets
     either cell shrink to nothing, so the table algorithm will always starve
     whichever one is not told otherwise.

     The sizes are the budget, not a preference: at 6rem and 5rem the home row
     came to 401px against a 380px phone and the page scrolled sideways, which
     `row_width_test.rb` catches at six widths. A long name still wraps -- it
     just wraps at a width you can read. */
  table.tasks td.description { min-width: 5rem; }
}

@media (max-width: 640px) {
  .tree-card > summary,
  .tree-card > .card-head { flex-wrap: wrap; }
  .tree-card .tree-name { min-width: 0; overflow-wrap: anywhere; }
  /* Full width and no indent. `flex-basis: 100%` is 100% of the container and
     a left margin is added to it, so aligning the stats under the name pushed
     the header past the viewport by exactly that margin. */
  .tree-card .card-stats {
    flex-basis: 100%;
    margin: 0.15rem 0 0;
  }
}

/* The sub-projects sit under their parent, indented and ruled, the same way a
   day's tasks do. */
.project-card > table.tree {
  margin: 0 0 0.6rem 1.5ch;
  border-left: 1px solid var(--rule);
  width: calc(100% - 1.5ch);
}
/* The card's own inset *plus* the depth indent, added rather than replaced.
   A flat `padding-left: 1rem` here -- which is what the day card uses, where
   tasks have no depth -- beat `table.tree .node` on specificity and flattened
   the nesting, so `peeps.luther.hvac` sat level with `peeps.luther`. */
.project-card > table.tree .node {
  padding-left: calc(1rem + var(--depth, 0) * 1.25rem);
}
.project-card > table.tree th:first-child { padding-left: 1rem; }
.project-card > table.tree td { border-bottom: none; }
/* Quieter than the page-wide header it replaces: it is a note about the two
   columns beneath it, not a heading for the card. */
.project-card > table.tree th { font-size: 0.78rem; border-bottom-color: var(--bg-raised); }

/* The tasks sit under their day, indented and ruled, so the nesting is visible
   without a second colour. */
.day-card > table.tasks {
  margin: 0 0 0.6rem 1.5ch;
  border-left: 1px solid var(--rule);
  width: calc(100% - 1.5ch);
}
.day-card > table.tasks td:first-child { padding-left: 1rem; }
.day-card > table.tasks td { border-bottom: none; }

/* An open card rules its header off from the tasks below it. */
.tree-card[open] > summary { border-bottom: 1px solid var(--rule); }

/* --- the calendar ------------------------------------------------------- */

/* `task calendar` puts three months on a terminal line; a browser has room for
   more, and wraps rather than scrolling when it does not. */
/* Above the strip, never either side of it. Three months and their gaps are
   744px against a 984px budget, and the two media queries that drop a month are
   computed from those numbers -- arrows beside the months would eat the budget
   and move both breakpoints, which is what calendar_width_test.rb catches.
   Keeping the controls out here also keeps .calendar's children nothing but
   months, which those same rules select by position. */
.calendar-controls {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  margin: 0 0 0.5rem;
  color: var(--dim);
  font-size: 0.82rem;
}
.calendar-controls .cal-step {
  display: inline-flex;
  align-items: center;
  color: var(--faint);
  padding: 0.15rem;
  border-radius: 2px;
}
.calendar-controls .cal-step:hover { color: var(--cyan); }
/* A real outline: these are 13px glyphs, and a colour change alone is not a
   signal that the keyboard is on one -- the same finding as the row actions. */
.calendar-controls .cal-step:focus-visible {
  color: var(--cyan);
  outline: 2px solid var(--cyan);
  outline-offset: 1px;
}
.calendar-controls .cal-range { color: var(--dim); }
/* Three months up: the label runs first to third, and the short form and the
   two-month ending are not wanted. Defaults first -- the overrides below are
   the same specificity and would lose on source order otherwise, which is a
   mistake this stylesheet has made three times now. */
.cal-short,
.cal-thru.is-two { display: none; }
.calendar-controls .tally { color: var(--faint); margin-left: 0.35rem; }
.calendar-controls .cal-today { margin-left: 0.6rem; }

.calendar {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem 1.4rem;
  margin-bottom: 1.5rem;
}

table.month { border-collapse: collapse; font-size: 0.82rem; }

table.month caption {
  text-align: left;
  color: var(--dim);
  padding: 0 0 0.25rem 1.9rem;   /* clears the week-number column */
  white-space: nowrap;
}
table.month caption .tally { color: var(--faint); margin-left: 0.35rem; }

table.month th {
  color: var(--faint);
  font-weight: 400;
  padding: 0 0.28rem 0.15rem;
  text-align: center;
}

table.month td {
  padding: 0;
  text-align: right;
  width: 1.85rem;
  height: 1.5rem;
}

table.month td a,
table.month td span {
  display: block;
  padding: 0.1rem 0.32rem;
  color: var(--faint);
  border: 1px solid transparent;
  border-radius: 2px;
}

/* The week-number gutter, which `task calendar` prints too. */
table.month .wk {
  color: var(--faint);
  opacity: 0.55;
  font-size: 0.72rem;
  padding-right: 0.45rem;
  text-align: right;
  width: 1.6rem;
}

/* A day with work on it is the only kind worth clicking, so it is the only
   kind that looks clickable. */
table.month td.is-busy a {
  color: var(--cyan);
  background: var(--bg-raised);
}
table.month td.is-busy a:hover {
  color: var(--fg);
  background: var(--bg-hover);
  text-decoration: none;
}

/* Green means where you are, here as everywhere else in this app. */
table.month td.is-today a,
table.month td.is-today span {
  color: var(--green);
  border-color: var(--rule);
}

table.month td.is-selected a {
  background: var(--green);
  color: var(--bg);
  border-color: var(--green);
}
table.month td.is-selected a:hover { background: var(--green); color: var(--bg); }

/* Days already gone read quieter, without disappearing -- an overdue task
   still lives on one. */
table.month td.is-past a { opacity: 0.65; }
table.month td.is-past span { opacity: 0.4; }

.echo-back { margin-left: 1rem; color: var(--dim); }

/* The phone's per-row accordion, and it exists only there: above 640px a row
   shows everything at once and there is nothing to disclose. `display: none`
   rather than the off-screen treatment the tab strip's radios get, because
   here the control is not merely invisible -- it does nothing, and a keyboard
   landing on it would be landing on a no-op. */
.row-state,
.row-toggle { display: none; }

/* --- completing from a row ---------------------------------------------- */

/* The row's own prompt: a note and a `d`, which is what `t 42 d 30 minutes`
   types. Quiet until reached for, so a list still reads as a list. */
td.act { width: 1%; white-space: nowrap; text-align: right; }

/* A flex container inside the cell, never on the cell. A td given display:flex
   stops being a table cell and its column stops lining up with every other
   row. This wrapper is also what lets the mobile pass be a media query with no
   markup change: the controls wrap, the note goes full width, and the row
   grows downwards instead of sideways. */
.controls {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}
/* In a row the controls hang off the right edge; on the task's own page they
   start at the left margin like everything else on it. */
td.act .controls { justify-content: flex-end; }

/* Every action button is sized to the text line, so swapping words for icons
   changed no row height and nothing else on the page moved. --tap is the one
   value the mobile pass has to raise. */
[data-action="complete"] { color: var(--act-complete); }
[data-action="push"]     { color: var(--act-push); }
[data-action="remove"]   { color: var(--act-remove); }
/* Reopen keeps the quiet-until-reached-for treatment it has always had rather
   than joining the three action colours. Those three were computed together
   under simulated colour-vision deficiency, and adding a fourth means re-running
   that -- not picking a hue that looks free. */

.controls .do,
.controls button.remove,
.controls button.reopen {
  font: inherit;
  height: var(--tap);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  line-height: 1;
  vertical-align: middle;
  background: transparent;
  border: 1px solid var(--rule);
  border-radius: 2px;
  padding: 0 0.45rem;
  cursor: pointer;
}

form.complete { display: flex; gap: 0.3rem; align-items: center; }

/* A line to write on, not a box.
   The note sits on every row because writing one is part of completing. But
   there is no Turbo here -- every completion is a full page load -- so a note
   typed on another row would be lost with the document. Forty bordered boxes
   would promise state the page cannot keep. A bottom rule says "available",
   and the full border arrives on the one row being acted on. */
form.complete .note {
  font: inherit;
  /* Grows with what is typed, in css rather than javascript. Chromium has
     this; where it is missing the box stays one row and scrolls, which is
     worse and not broken. */
  field-sizing: content;
  resize: none;
  min-height: var(--tap);
  max-height: 8rem;
  width: 6rem;
  color: var(--fg);
  background: transparent;
  border: 1px solid transparent;
  border-bottom-color: var(--rule);
  border-radius: 2px;
  padding: 0 0.3rem;
  transition: border-color 0.12s ease;
}
form.complete .note::placeholder { color: transparent; }
tr:hover form.complete .note,
tr:focus-within form.complete .note { border-color: var(--rule); }
form.complete .note:focus { border-color: var(--green); }

/* Wider while you are in it. 6rem is right for "30 minutes" and cramped for
   three lines -- at that width a note wraps mid-phrase.
   
   Only where there is room for it: measured, the extra 112px pushes a row with
   a long project name 10px past the edge at 830px, while 900px and up has room
   to spare. Below that it stays 6rem, which is narrow and not broken. */
@media (min-width: 900px) {
  /* `:focus-within` on the form, not `:focus` on the box. With `:focus` the
     box shrinks the instant focus leaves it -- which is when you press the
     check beside it -- so the row reflows and the button moves out from under
     the cursor mid-click. Scoped to the form, clicking the check keeps focus
     inside it and the width holds until the submit lands. */
  form.complete:focus-within .note { width: 13rem; }
}
form.complete .note:focus-visible { outline: 2px solid var(--green); outline-offset: 1px; }
form.complete .note:focus::placeholder { color: var(--faint); }

/* **Guarded, because a tap counts as a hover and never un-hovers.** With no
   pointer query these colours latch onto the last control touched and stay
   there until something else is tapped, which reads as "this row is still
   doing something". `:focus-visible` carries the keyboard case separately, a
   few lines down, so nothing is lost by asking for a real pointer here. */
@media (hover: hover) {
  form.complete .do:hover { color: var(--green); border-color: var(--green); }
}
form.complete .do:focus-visible {
  color: var(--green);
  outline: 2px solid var(--green);
  outline-offset: 1px;
}

/* Editing is not destructive and does not compete with the three action
   colours, so it wears reopen's treatment: quiet until reached for. */
.controls a.edit {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: var(--tap);
  padding: 0 0.45rem;
  color: var(--faint);
  border: 1px solid transparent;
  border-radius: 2px;
}
tr:hover .controls a.edit { border-color: var(--rule); color: var(--dim); }
.task-actions a.edit { border-color: var(--rule); color: var(--dim); }
.controls a.edit:hover { color: var(--cyan); border-color: var(--cyan); }
.controls a.edit:focus-visible {
  color: var(--cyan);
  outline: 2px solid var(--cyan);
  outline-offset: 1px;
}

/* The notes already on the task, above the box for new ones. Read-only, and
   quiet, because they are context for what you are about to write rather than
   the thing you came to change. */
.notes-had { flex: 1 1 100%; }
.notes-had > span { color: var(--faint); font-size: 0.78rem; display: block; }
.notes-had ul.annotations { margin: 0.2rem 0 0; color: var(--dim); font-size: 0.85rem; }

/* A ticked note is removed on save. The whole line is the label, so the target
   is the note rather than a 13px box beside it -- and it turns red while
   ticked, because red is what removing means everywhere else here. The colour
   reinforces the tick; it never carries it alone. */
.notes-had label.drop-note {
  display: flex;
  /* `form.add-form label` stacks its span above its input, which is right for
     a field and wrong for a note -- and `flex-direction` is a separate
     declaration, so higher specificity on the rest of this block does not undo
     it. It has to be said. */
  flex-direction: row;
  align-items: baseline;
  gap: 0.5rem;
  cursor: pointer;
  padding: 0.1rem 0;
}
/* Same reason: the add form styles a label's span as a field caption. Inside a
   note that span is the date it was written. */
.notes-had label.drop-note span { font-size: inherit; color: var(--faint); }
/* `form.add-form input` gives every input a 9rem floor, a border and padding,
   which is right for a text field and turns a checkbox into a grey slab with
   70px of empty space after it. The box model has to be handed back. */
.notes-had label.drop-note input {
  min-width: 0;
  width: auto;
  margin: 0;
  padding: 0;
  border: 0;
  background: none;
  accent-color: var(--red);
  cursor: pointer;
}
.notes-had label.drop-note:hover { color: var(--fg); }
.notes-had label.drop-note:has(input:checked) {
  color: var(--act-remove);
  text-decoration: line-through;
}
.notes-had label.drop-note:has(input:focus-visible) {
  outline: 2px solid var(--cyan);
  outline-offset: 2px;
}

form.add-form textarea {
  font: inherit;
  width: 100%;
  color: var(--fg);
  background: var(--bg);
  border: 1px solid var(--rule);
  border-radius: 2px;
  padding: 0.3rem;
}
form.add-form textarea:focus { outline: none; border-color: var(--green); }
form.add-form textarea::placeholder { color: var(--faint); }

.edit-form .cancel { margin-left: 0.5rem; }

/* --- undo and reopen ---------------------------------------------------- */

/* Reversing something is never the loud option: quiet until reached for, the
   same as the `d` on a row. */
.reopen button,
.undo button,
button.reopen,
button.undo {
  font: inherit;
  color: var(--faint);
  background: transparent;
  border: 1px solid var(--rule);
  border-radius: 2px;
  padding: 0 0.45rem;
  cursor: pointer;
}
tr:hover .reopen button,
tr:hover button.reopen { color: var(--dim); }
.reopen button:hover, .undo button:hover,
button.reopen:hover, button.undo:hover,
.reopen button:focus, .undo button:focus,
button.reopen:focus, button.undo:focus {
  color: var(--amber);
  border-color: var(--amber);
}

/* button_to renders a form; keep it from taking a line of its own. */
form.button_to { display: inline; }

.flash form.button_to { margin-left: 0.75rem; }

/* A task is a card, the same shape a day is on the home page. The actions are
   its footer: last, because the page is for reading what the task is and then
   acting on it, and inside the border so they belong to this record.

   A footer band rather than controls straddling the border. Straddling is what
   a fieldset legend does, and it works for a label -- a thing you only read.
   A button sitting half on a rule has an ambiguous hit area and nowhere to go
   when the card narrows, which is a lot to pay for the same reading. */
.task-card {
  border: 1px solid var(--rule);
  padding: 0 0.9rem;
  margin: 0 0 1.25rem;
}
.task-card .task-title { margin: 0.85rem 0 0.9rem; }
.task-card dl.info { margin-bottom: 1.1rem; }
.task-card .annotations-head,
.task-card .series-head { font-size: 0.95rem; margin: 0 0 0.35rem; }
.task-card ul.annotations { margin-bottom: 1.1rem; }
.task-card .annotations-head + .note { margin: 0 0 1.1rem; }

/* Where this task lives. Mauve because that is what a project is everywhere
   else here, and the same trail the project page's own title draws -- one
   convention, applied, rather than a second one invented for this page. */
.crumbs { margin: 0 0 0.6rem; font-size: 0.85rem; }
.crumbs a { color: var(--mauve); }
.crumbs .sep { color: var(--faint); }

/* The recurrence around this task, a page at a time. Quiet -- it is context
   for the record above it, not a second list competing with it. */
.task-card .series-head { display: flex; align-items: baseline; gap: 0.6rem; }
.task-card .series-head a { font-size: 0.8rem; }
/* Two columns when there is room, one when there is not. A table cannot be
   given `columns`, which is why these are rows of spans -- and the fields still
   line up across both columns because the app is monospace, so a `ch` width is
   an exact number of characters rather than a guess at one.

   Column-major, which multicol does for free and which is right here: the
   dates run down the first column and continue down the second, so the series
   still reads in order. */
.series-grid { font-size: 0.85rem; margin-bottom: 0.5rem; column-gap: 2.5rem; }
@media (min-width: 900px) { .series-grid { columns: 2; } }

.series-row {
  display: flex;
  flex-wrap: nowrap;
  gap: 0.75rem;
  padding: 0.15rem 0;
  break-inside: avoid;
}
/* Wide enough for the longest each can be: "12/10/26" and "this one". */
.series-row .weekday { width: 3ch; color: var(--faint); }
.series-row .due { width: 8ch; white-space: nowrap; }
/* The word and the note share this cell, so a note starts exactly where the
   word it replaced would have. The word keeps a fixed width so that a row
   carrying both still lines its note up with the rest. */
.series-row .outcome { flex: 1; min-width: 0; display: flex; gap: 0.75rem; }
.series-row .state { width: 8ch; color: var(--dim); flex: none; }
/* `series-note`, not `note`. `.note` is the app's page-note paragraph class
   and carries `margin: 0.75rem 0 0`, which a scoped rule that sets no margin
   does not undo -- so the note sat 12px below its own row, looking exactly
   like a flex line that had wrapped. Naming it for what it is costs nothing
   and cannot collide. */
.series-row .outcome .series-note {
  flex: 1;
  min-width: 0;
  color: var(--dim);
  overflow-wrap: anywhere;
}
.series-row.is-current { color: var(--fg); font-weight: 600; }
.series-row.is-current .state { color: var(--cyan); }

/* Paging. Plain links, so it works with javascript off like everything else. */
nav.series-pages {
  display: flex;
  align-items: baseline;
  gap: 1rem;
  margin: 0 0 1.1rem;
  font-size: 0.8rem;
}
nav.series-pages .count { color: var(--faint); margin-left: auto; margin-right: auto; }
nav.series-pages a { color: var(--dim); }
nav.series-pages a:hover { color: var(--cyan); }

/* Spans the card's padding so its rule meets the card's own sides rather than
   stopping short of them -- the same trick the day card's summary uses. */
.task-actions {
  margin: 0 -0.9rem;
  padding: 0.5rem 0.9rem;
  border-top: 1px solid var(--rule);
  background: var(--bg-raised);
}
.task-actions form.complete { justify-content: flex-start; }
.task-actions form.complete .note { border-color: var(--rule); }

/* The uuid column on the history page. Quiet: it is an identifier to reach
   for, not something to read down the page. */
/* What was written about a completed task, previewed beside it. Quiet -- the
   description is what the row is, and these are the facts hanging off it. */
table.tasks.done td.notes { color: var(--dim); font-size: 0.85rem; width: 30%; }
tr.note-row { display: none; }
.note-preview {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  overflow-wrap: anywhere;
}

table.tasks.done td.uuid { width: 1%; white-space: nowrap; padding-right: 0.9rem; }
table.tasks.done td.uuid a { color: var(--faint); }
table.tasks.done td.uuid a:hover { color: var(--cyan); }

/* --- copy to clipboard -------------------------------------------------- */

table.tasks.done td.uuid a { margin-right: 0.35rem; }

button.copy {
  font: inherit;
  color: var(--faint);
  background: transparent;
  border: 0;
  padding: 0 0.1rem;
  cursor: pointer;
  line-height: 1;
  /* Holds both glyphs' worth of space so the row does not twitch on success. */
  display: inline-flex;
  align-items: center;
}
button.copy svg { display: block; }
button.copy { vertical-align: -0.05em; }
button.copy .i-done { display: none; }

tr:hover button.copy { color: var(--dim); }
button.copy:hover { color: var(--cyan); }
/* An icon-only button needs a real focus ring: a colour change on an 11px
   glyph is not a signal that the keyboard is here. */
button.copy:focus-visible {
  color: var(--cyan);
  outline: 2px solid var(--cyan);
  outline-offset: 1px;
}

button.copy[data-copied] { color: var(--green); }
button.copy[data-copied] .i-copy { display: none; }
button.copy[data-copied] .i-done { display: block; }

button.copy[data-failed] { color: var(--red); }

/* On a task's own page there is one uuid rather than a column of them, and no
   row to hover, so the button rests at the same weight a hovered row gives it. */
dl.info .uuid button.copy { color: var(--dim); margin-left: 0.4rem; }
dl.info .uuid button.copy:hover,
dl.info .uuid button.copy:focus-visible { color: var(--cyan); }
dl.info .uuid button.copy[data-copied] { color: var(--green); }
dl.info .uuid button.copy[data-failed] { color: var(--red); }

/* --- a project's page --------------------------------------------------- */

.project-title { margin: 0 0 1.25rem; font-size: 1rem; }
.project-title a { color: var(--mauve); }
.project-title .sep { color: var(--faint); }

/* Each block answers one question and appears only when its data exists, so a
   one-off project simply shows fewer of them. */
section.card {
  border: 1px solid var(--rule);
  padding: 0.9rem 1rem 1rem;
  margin: 0 0 1.25rem;
}
section.card h2 { margin: 0 0 0.75rem; color: var(--dim); font-size: 0.85rem; }

/* --- tabs --------------------------------------------------------------- */

/* Two charts of the same thing over different windows, so one card with tabs.
   Built from radios and labels rather than javascript: nothing in this app
   needs javascript to work, and a tab strip is not where that would start.
   The happy consequence of them really being a radio group is that the arrow
   keys move between them already -- the keyboard behaviour a tab widget is
   supposed to have, with nothing to maintain.

   The inputs are moved off-screen rather than display:none, which would take
   them out of the tab order and off every screen reader with them. */
.tabs > input {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  pointer-events: none;
}

/* Sits where a card's h2 sits and reads at the same size, so the strip is the
   card's heading rather than a row of chrome underneath one. */
.tab-list {
  display: flex;
  gap: 0.2rem;
  border-bottom: 1px solid var(--rule);
  margin: 0 0 0.9rem;
}
.tab-list label {
  cursor: pointer;
  /* **Never break mid-phrase.** Without this the flex items shrink to
     min-content and "by month" renders as "by" over "month", "the last year"
     as three lines -- which set the strip's height from the raggedest label
     and made it 90px tall on a phone. */
  white-space: nowrap;
  padding: 0.25rem 0.7rem;
  font-size: 0.85rem;
  color: var(--dim);
  border: 1px solid transparent;
  border-bottom: none;
  margin-bottom: -1px;   /* the tab sits on the strip's rule, not above it */
}
.tab-list label:hover { color: var(--fg); }

/* Positional, not named. This used to list the three tab names, because a
   sibling selector has to name what it points at -- and that made the tab set
   closed, with every new tab costing three rules here. But the relationship is
   positional by construction: the nth input drives the nth panel. Written that
   way once it serves any tab set, and six is more than any page will want.

   This is the one place "name the thing, not its place" does not apply. The
   position *is* the relationship -- these rules say nothing about what a tab
   holds, only that the nth one belongs to the nth. */
.panel { display: none; }
.tabs > input:nth-of-type(1):checked ~ .panels > .panel:nth-child(1),
.tabs > input:nth-of-type(2):checked ~ .panels > .panel:nth-child(2),
.tabs > input:nth-of-type(3):checked ~ .panels > .panel:nth-child(3),
.tabs > input:nth-of-type(4):checked ~ .panels > .panel:nth-child(4),
.tabs > input:nth-of-type(5):checked ~ .panels > .panel:nth-child(5),
.tabs > input:nth-of-type(6):checked ~ .panels > .panel:nth-child(6) {
  display: block;
}

/* The selected tab joins the panel by opening its own bottom edge. */
.tabs > input:nth-of-type(1):checked ~ .tab-list > label:nth-child(1),
.tabs > input:nth-of-type(2):checked ~ .tab-list > label:nth-child(2),
.tabs > input:nth-of-type(3):checked ~ .tab-list > label:nth-child(3),
.tabs > input:nth-of-type(4):checked ~ .tab-list > label:nth-child(4),
.tabs > input:nth-of-type(5):checked ~ .tab-list > label:nth-child(5),
.tabs > input:nth-of-type(6):checked ~ .tab-list > label:nth-child(6) {
  color: var(--fg);
  border-color: var(--rule);
  border-bottom-color: var(--bg);
}

/* A real outline: with the input off-screen there is nothing else to show the
   keyboard is here, and a colour change alone was not enough on the row
   actions either. */
.tabs > input:nth-of-type(1):focus-visible ~ .tab-list > label:nth-child(1),
.tabs > input:nth-of-type(2):focus-visible ~ .tab-list > label:nth-child(2),
.tabs > input:nth-of-type(3):focus-visible ~ .tab-list > label:nth-child(3),
.tabs > input:nth-of-type(4):focus-visible ~ .tab-list > label:nth-child(4),
.tabs > input:nth-of-type(5):focus-visible ~ .tab-list > label:nth-child(5),
.tabs > input:nth-of-type(6):focus-visible ~ .tab-list > label:nth-child(6) {
  outline: 2px solid var(--cyan);
  outline-offset: 1px;
}

/* --- the tab strip, on a phone ------------------------------------------ */

/* **A folder tab only works in one row.** The selected one joins the panel by
   opening its own bottom edge onto the strip's rule, and that stops meaning
   anything the moment there are two rows.

   Six tabs do not fit: measured on `/projects/code` at 380px, the strip wants
   565px against a 291px box, so `history` was clipped, `beneath` was off the
   screen entirely, and the page itself scrolled sideways at 477px wide in a
   380px window.

   So below 640 the folder goes and each tab becomes a bordered pill that wraps.
   Bare words were tried first, matching `nav.periods` -- the app's other
   pick-one-of-N control -- and read as running text at this size: "the last
   year history beneath" is six words and three tabs. A border says where each
   one ends, and doubles the touch target.

   The current one is `--green`, which is what `is-here` already means in the
   wordmark, the header nav and the period switcher.

   Wrapping is what makes this hold for any number of tabs, and it is why there
   is no second set of shorter labels to keep in step with the first. */
@media (max-width: 640px) {
  .tab-list {
    flex-wrap: wrap;
    gap: 0.3rem;
    border-bottom: none;
    margin-bottom: 0.8rem;
  }

  .tab-list label {
    padding: 0.2rem 0.55rem;
    margin-bottom: 0;
    border: 1px solid var(--rule);
  }

  /* The six again, because the relationship is positional and a media query
     cannot inherit the specificity of the block it is overriding. */
  .tabs > input:nth-of-type(1):checked ~ .tab-list > label:nth-child(1),
  .tabs > input:nth-of-type(2):checked ~ .tab-list > label:nth-child(2),
  .tabs > input:nth-of-type(3):checked ~ .tab-list > label:nth-child(3),
  .tabs > input:nth-of-type(4):checked ~ .tab-list > label:nth-child(4),
  .tabs > input:nth-of-type(5):checked ~ .tab-list > label:nth-child(5),
  .tabs > input:nth-of-type(6):checked ~ .tab-list > label:nth-child(6) {
    color: var(--green);
    border-color: var(--green);
  }
}

/* The concerts log: a date, then what it was and whatever was written about
   it. The notes sit under the name rather than in a column of their own --
   they are sentences of wildly different lengths and a column would be mostly
   empty. */
table.tasks.log td.due { color: var(--dim); vertical-align: top; }
/* A pending row's note sits under its description rather than in a column:
   only 4 of 141 pending tasks carry one, so a column would be empty on 137
   rows. `.note-preview` is already a block box for the clamp, so it takes its
   own line here without being told. */
table.tasks td.description .note-preview {
  color: var(--faint);
  font-size: 0.85rem;
  padding-left: 0.1rem;
}

/* Inside a panel the old card headings become sub-headings: "practice" and
   "rhythm" are different questions and still say so, one register below the
   tab that holds them. */
.panel h3 {
  margin: 1.1rem 0 0.5rem;
  color: var(--faint);
  font-size: 0.8rem;
  font-weight: 600;
}
.panel > h3:first-child { margin-top: 0; }


/* Numbers about the numbers: how many instances, how many lacked a duration.
   Recessive, because they qualify the figures rather than competing with them. */
.note { color: var(--faint); margin: 0.75rem 0 0; }
.note.caveat { color: var(--amber); opacity: 0.75; }

/* The blocks of figures on a details tab, in the days list's idiom: 0.85rem,
   flex rows, spans sized in `ch`, which line up exactly because this app is
   monospace throughout.

   They replaced a `table.stats` at `width: 100%` -- "this week" at x=93 and
   its number at x=910, ~900px of eye travel over four rows -- and a
   `dl.info.rhythm` beside it that used only the left third of the card. The
   two failed in opposite directions and neither matched the list underneath.

   Two-up at 900px, which is where the days list already flows into two
   columns; one column below it. `minmax(0, 1fr)` rather than `1fr`, or a long
   value stops the column shrinking and pushes the page sideways. */
.stat-blocks { display: grid; gap: 0 2.5rem; margin-bottom: 0.4rem; }
@media (min-width: 900px) {
  .stat-blocks { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
.stat-block { min-width: 0; }

.stat-row { display: flex; gap: 0.75rem; font-size: 0.85rem; padding: 0.1rem 0; }
/* 15ch is the budget every label has to fit: the column is fixed so that the
   figures beside it line up, and a longer label wraps onto a second line
   rather than pushing the page sideways. "birthdays tracked" was 17 and did
   exactly that, and is "birthdays" now. "several, summed" is 15 on the nose. */
.stat-row .stat-label { width: 15ch; flex: none; color: var(--faint); }

/* A phrase -- "6 days ago", "4.4 sessions" -- reads from the left and takes
   what it needs. A figure is right-aligned in a fixed column, which is what
   makes it comparable with the one above it. */
.stat-row .stat-value { flex: 1; min-width: 0; }
.stat-block.is-numeric .stat-value { width: 9ch; flex: none; text-align: right; }

/* The header names the figures once rather than every row repeating the word. */
.stat-row.is-head .stat-value {
  color: var(--faint);
  border-bottom: 1px solid var(--rule);
  padding-bottom: 0.2rem;
}

/* Numbers about the numbers: how many lacked a duration, how many closed on a
   day other than the one they were due. Amber because they qualify the figures
   rather than joining them -- the treatment the caveat sentence had before it
   became a block. */
.stat-block.is-caveat .stat-value { color: var(--amber); opacity: 0.75; }

/* --- bars --------------------------------------------------------------- */

/* Length encodes minutes and nothing else. The session count rides alongside
   as a printed number rather than a second scale. */
table.bars { width: 100%; border-collapse: collapse; }
table.bars th {
  text-align: left; font-weight: 400; color: var(--dim);
  white-space: nowrap; padding-right: 0.9rem; width: 1%;
}
table.bars td { padding: 0.12rem 0; }
table.bars td.num { padding-left: 0.9rem; white-space: nowrap; width: 1%; }

table.bars .track { width: auto; }
table.bars .bar {
  width: calc(var(--pct, 0) * 1%);
  min-width: 1px;
  height: 0.7rem;
  background: var(--heat-3);
  border-radius: 2px;   /* rounded data-end, anchored at the baseline */
}
table.bars tr:hover .bar { background: var(--heat-4); }

/* --- heatmap ------------------------------------------------------------ */

.calendar.heat table.month td span {
  display: block;
  padding: 0.1rem 0.32rem;
  border: 1px solid transparent;
  border-radius: 2px;
  color: var(--faint);
}
.calendar.heat table.month td.heat-0 span { background: var(--bg-raised); }
.calendar.heat table.month td.heat-1 span { background: var(--heat-1); color: var(--dim); }
.calendar.heat table.month td.heat-2 span { background: var(--heat-2); color: var(--fg); }
.calendar.heat table.month td.heat-3 span { background: var(--heat-3); color: var(--bg); }
.calendar.heat table.month td.heat-4 span { background: var(--heat-4); color: var(--bg); }
.calendar.heat table.month td.is-today span { border-color: var(--fg); }

/* One series needs no legend, but a shaded scale does: without it the reader
   cannot tell what a darker square means. */
.legend { color: var(--dim); margin-top: 0.6rem; }
.legend span {
  display: inline-block;
  width: 0.75rem; height: 0.75rem;
  border-radius: 2px;
  margin: 0 0.15rem 0 0.6rem;
  vertical-align: -0.1em;
}
.legend .heat-0 { background: var(--bg-raised); }
.legend .heat-1 { background: var(--heat-1); }
.legend .heat-2 { background: var(--heat-2); }
.legend .heat-3 { background: var(--heat-3); }
.legend .heat-4 { background: var(--heat-4); }

/* --- completion menu ---------------------------------------------------- */

.prompt-box { position: relative; }

/* Hangs under the bar rather than pushing the page down: it appears and
   disappears on every tab, and reflowing the whole layout for it would be
   worse than the help it gives. */
ul.completions {
  position: absolute;
  top: calc(100% + 3px);
  left: 0;
  right: 0;
  z-index: 6;
  margin: 0;
  padding: 0.2rem 0;
  list-style: none;
  max-height: 16rem;
  overflow-y: auto;
  background: var(--bg-raised);
  border: 1px solid var(--rule);
  border-radius: 2px;
  box-shadow: 0 8px 16px -8px rgba(0, 0, 0, 0.8);
}
/* The row's colour says what the row is: --mauve for a project, which is what
   --mauve means everywhere else in this app, and --cyan for a page, which is
   what --cyan means everywhere else -- somewhere you can go. The word beside
   it says the same thing again, because green/red taught us that colour has
   to reinforce a cue and never carry one alone. */
ul.completions li {
  display: flex;
  align-items: baseline;
  gap: 0.6rem;
  padding: 0.15rem 0.6rem;
  color: var(--mauve);
}
ul.completions li[data-kind="page"] { color: var(--cyan); }

/* The truncation moved off the li when it became a flex row: a flex parent
   cannot ellipsis its children, so the name does it and the label is never
   the part that gets cut. */
ul.completions li .name {
  flex: 1;
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
ul.completions li .kind {
  flex: none;
  font-size: 0.85em;
  color: var(--dim);
}

/* Selecting a row moves the indicator rather than dropping it. The old rule
   flipped the whole row to --fg, which erased the colour at exactly the moment
   you are cycling the list looking for it; now the name takes the highlight
   and the label picks up the kind colour, so the kind is legible in both
   states and selection reads as strongly as it did. */
ul.completions li[aria-selected="true"] {
  background: var(--bg-hover);
  color: var(--fg);
}
ul.completions li[aria-selected="true"] .kind { color: var(--mauve); }
ul.completions li[aria-selected="true"][data-kind="page"] .kind { color: var(--cyan); }

ul.completions li.more { color: var(--faint); }

/* --- keeping the calendar to one line ----------------------------------- */

/* The home calendar is meant to read as a single row. Measured: a month table
   is 233px and the gap 22.4px, against a content budget of 984px at the page's
   own max width -- so three fit and a fourth would wrap.
   Below that the months drop one at a time rather than wrapping, at the widths
   where they stop fitting: two need 528px of viewport, one needs 273px.
   Scoped away from .heat, where a year of months is supposed to wrap. */
/* The two widths where a month stops fitting -- 783px for the third, 527px for
   the second. **The label has to move with them**, or it names months that are
   not on screen, which is what it did on every phone. */
@media (max-width: 783px) {
  .calendar:not(.heat) table.month:nth-child(3) { display: none; }
  .cal-thru.is-three { display: none; }
  .cal-thru.is-two { display: inline; }
}
@media (max-width: 527px) {
  .calendar:not(.heat) table.month:nth-child(2) { display: none; }

  /* One month wide: the label is that month, short, with its tally -- and the
     caption goes, because it was saying the same thing one line further down.
     Two rows become one, which is the tightening this was asked for.

     Both endings named, not a bare `.cal-thru`: the rule above turning the
     two-month one *on* carries a class more, and a later media query does not
     beat specificity. The label read "sep 2026 – october 2026" on a phone. */
  .cal-thru.is-two,
  .cal-thru.is-three { display: none; }
  .cal-long { display: none; }
  .cal-short { display: inline; }
  .calendar:not(.heat) table.month caption { display: none; }
}

/* --- pushing a due date ------------------------------------------------- */

/* Sits beside the complete control in the same cell. Both stay quiet until
   the row is reached for, so a list still reads as a list.
   The cell stays a cell: giving a td display:flex stops it being a table cell
   and takes its column out of alignment with every other row. The two forms
   are inline-flex instead, which keeps them side by side without that. */
td.act { white-space: nowrap; }

form.push { display: flex; gap: 0.3rem; align-items: center; }

/* The number is doing much of the chevron's work -- "1 »" reads as a phrase --
   so it gets the same line-to-write-on treatment rather than hiding. */
form.push .days {
  font: inherit;
  width: 2.2rem;
  text-align: right;
  color: var(--fg);
  background: transparent;
  border: 1px solid transparent;
  border-bottom-color: var(--rule);
  border-radius: 2px;
  padding: 0 0.3rem;
}
tr:hover form.push .days,
tr:focus-within form.push .days { border-color: var(--rule); }
form.push .days:focus { border-color: var(--amber); }
form.push .days:focus-visible { outline: 2px solid var(--amber); outline-offset: 1px; }

/* Amber, because pushing a date is the one row action that changes when
   something is due rather than whether it is done. */
@media (hover: hover) {
  form.push .do:hover { color: var(--amber); border-color: var(--amber); }
}
form.push .do:focus-visible {
  color: var(--amber);
  outline: 2px solid var(--amber);
  outline-offset: 1px;
}

.task-actions form.push .days { border-color: var(--rule); }
.task-actions button.remove { border-color: var(--rule); }
.task-actions button.reopen { color: var(--dim); }

/* On a narrow screen the buttons are what matter and the text boxes are the
   luxury, so the boxes go rather than the actions. Hidden fields are still
   submitted, so push keeps its default of one day and complete simply carries
   no note. Without this the row was wide enough to make the page scroll
   sideways at phone width.

   820px, not 640: the row grew a fourth control when editing arrived, and a
   long project name plus both boxes plus four icons overflowed by 28px at
   700px -- measured. The threshold moved rather than the control going,
   because this rule already says which of the two gives way first. */
/* **A band, and scoped to the table.** Two things were wrong with hiding these
   at every width below 820.

   The rule named no table, so it also stripped the note box from the task's
   *own* page -- a page with one task on it, no columns and nothing competing
   for width. That is where someone goes to write a note, and it was the only
   place left to write one on a phone.

   And it reached all the way down, where it is no longer true: below 640 the
   row is not a row any more, so the boxes have a line of their own and there
   is nothing to squeeze. What this hiding was ever about is the squeezed
   middle -- 641 to 820, where five columns and four controls still share a
   line -- so that is what it says now. */
@media (min-width: 641px) and (max-width: 820px) {
  table.tasks form.complete .note,
  table.tasks form.push .days { display: none; }
}

@media (max-width: 640px) {



  /* The notes column is a sixth column, and six do not fit: at 380px it broke
     the project to one character a line and clamped "40 minutes" to "40 m…".
     Something had to give, and the uuid is what a phone needs least -- it is a
     copy target for typing into a terminal, and the description beside it
     already links to the page that shows the whole uuid. The note is what the
     column was added for, so it stays.

     The day heading keeps colspan 6; a hidden column simply takes no width. */
  table.tasks.done td.uuid { display: none; }

  /* And the id goes on the pending lists, which is what pays for the two
     floors above at phone width -- they are what took the home row to 401px
     against a 380px screen. Same trade as the uuid on the line above, for the
     same reason: an identifier is what you type into a terminal, `t 303 d`,
     and on a phone you tap the control instead. It is still on the task's own
     page, and the row's description links there.

     Scoped to `table.tasks`, because `.num` is also the right-aligned number
     class in every stats table on a project page. */
  table.tasks td.num { display: none; }

  /* --- the row stops being a row ---------------------------------------
     Five columns sharing 380px is what every rule above is working around:
     the two min-width floors, the id going, the uuid going, `overflow-wrap:
     anywhere` on both text cells. **None of it is needed once there are no
     columns.** A task becomes three lines --

         9:00am · bills.att-uverse
         pay cell phone
         [ 206.91.......... ] check  [1] push

     -- which is what makes completing-with-a-note possible on a phone at all,
     and it is the whole reason: measured over the last 90 days, 239 of 302
     completions carry a note, 229 of those carry exactly one, and the median
     is ten characters. Completing here *is* completing with a short note.

     This is what `.controls`' own comment has promised since the icons landed:
     "the mobile pass is a media query with no markup change -- the controls
     wrap, the note goes full width, and the row grows downwards instead of
     sideways."

     The cost, said plainly: `display: block` drops the table semantics, so a
     screen reader stops announcing row and column. At this width there are no
     columns to announce, and every control already carries an `aria-label`
     naming the action *and* the task, which is what actually tells forty
     buttons apart. */
  table.tasks,
  table.tasks tbody,
  table.tasks tr,
  table.tasks td { display: block; }

  /* The rule belongs to the row now. Left on each cell it would draw three
     lines under every task instead of one. */
  table.tasks td { border-bottom: 0; padding: 0; }
  table.tasks tr { border-bottom: 1px solid var(--bg-raised); padding: 0.5rem 0; }
  /* The last one in a card needs none: the card's own edge is under it. */
  table.tasks tr:last-child { border-bottom: none; }

  /* --- the accordion ----------------------------------------------------
     A task is ~157px with every control on show -- 11 of them is past three
     screens on a phone. Collapsed it is ~40px, so a day fits on one, and the
     controls are one tap away rather than always underfoot.

     Off-screen, not `display: none`: hiding a radio outright takes it out of
     the tab order and off every screen reader with it, which is the rule the
     tab strip's inputs already follow. This is a real radio group, so the
     arrow keys walk the rows for free. */
  .row-state {
    position: absolute;
    width: 1px;
    height: 1px;
    opacity: 0;
    pointer-events: none;
  }

  /* The chevron sits on the first line at the row's right edge, which is where
     the eye already is after reading the description -- and it cannot be laid
     out in flow there, because the cell it would belong to is the one it
     un-hides. The row is a block at this width, so it can be the containing
     block. */
  table.tasks tr { position: relative; }
  .row-toggle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    position: absolute;
    top: 0.35rem;
    right: 0;
    height: var(--tap);
    padding: 0 0.45rem;
    color: var(--faint);
    cursor: pointer;
  }

  .row-toggle.is-shut { display: none; }

  /* **`:has()`, because the toggle and what it reveals are in different
     cells** -- the tab strip's `~` cannot reach across them. It is already in
     this stylesheet for the edit page's note checkboxes, and the browser gate
     guarantees it: `allow_browser versions: :modern` is Safari 17.2, Chrome
     120, Firefox 121. */
  table.tasks tr:has(.row-open:checked) td.project,
  table.tasks tr:has(.row-open:checked) td.act { display: block; }
  table.tasks tr:has(.row-open:checked) .row-toggle.is-open { display: none; }
  table.tasks tr:has(.row-open:checked) .row-toggle.is-shut { display: inline-flex; }
  table.tasks tr:has(.row-open:checked) .row-toggle { color: var(--cyan); }

  /* A real outline on focus: the input is off-screen, so the label is the only
     thing that can show the keyboard is here -- the same bargain the tab
     strip's labels make. */
  .row-open:focus-visible ~ .row-toggle.is-open,
  .row-shut:focus-visible ~ .row-toggle.is-shut {
    outline: 2px solid var(--cyan);
    outline-offset: 1px;
    color: var(--cyan);
  }

  /* **The row is a flex column-wrapper, and the cells are ordered.**
     The markup order is due, project, description, act -- right for a table
     and wrong for this, where the time and the description belong together on
     the first line and the project goes underneath. Inline boxes cannot do
     that: a block-level project sits *between* the two in the DOM and breaks
     the line, which is what it did. `order` says the arrangement without the
     markup having to change for one width.

     The time is subordinate by size, not by colour: --amber means due and
     --red means overdue everywhere in this app, and `.is-overdue` is on the
     row, so the red still reaches this line. */
  table.tasks tr { display: flex; flex-wrap: wrap; gap: 0 0.5rem; }
  table.tasks td.due {
    order: 1;
    flex: none;
    width: auto;
    font-size: 0.85rem;
  }
  /* The thing being read takes the slack, and leaves room at the right for the
     chevron to sit over. */
  table.tasks td.description {
    order: 2;
    flex: 1;
    min-width: 0;
    padding-right: 2rem;
  }
  table.tasks td.project {
    order: 3;
    flex-basis: 100%;
    min-width: 0;
    font-size: 0.85rem;
  }
  table.tasks td.act { order: 4; flex-basis: 100%; }

  /* **Shut is the arrival state**: the project and every control fold away and
     the row is a time and a description. Said after the rules above, which set
     a `display` on the same cells at the same specificity and would otherwise
     win on order alone. The reveal rules below do not care, because `:has()`
     lends them its argument's specificity. */
  table.tasks td.project,
  table.tasks td.act { display: none; }

  /* Line three. `td.act` carries `width: 1%; white-space: nowrap; text-align:
     right` for the table algorithm, and none of that means anything now. */
  table.tasks td.act {
    width: auto;
    white-space: normal;
    text-align: left;
    margin-top: 0.35rem;
  }
  table.tasks form.complete { flex: 1; min-width: 0; }
  table.tasks form.push { flex: none; }
  table.tasks form.complete .note { width: 100%; }

  /* **"Quiet until reached for" is a pointer idea, and a tap is not a hover.**
     `tr:hover form.complete .note` never resolves on a phone, so both boxes
     would spend their whole lives as a bottom rule with no edges -- an input
     nobody can see is not a quiet input. The task's own page already makes
     exactly this trade, and for the same reason. */
  td.act form.complete .note,
  td.act form.push .days { border-color: var(--rule); }

  /* The day card inset was on the first cell, which is `td.num` -- hidden at
     this width and still `:first-child`, so the indent was landing on nothing
     you can see. Stacked, it belongs to the row. */
  .day-card > table.tasks td:first-child { padding-left: 0; }
  .day-card > table.tasks tr { padding-left: 1rem; }

  /* And the notes leave the row entirely, taking a full-width line underneath
     it instead. As a sixth column at 380px there was room for four characters
     of a note; underneath there is room for all of it. */
  table.tasks.done td.notes { display: none; }
  tr.note-row { display: table-row; }
  tr.note-row td {
    padding: 0 0 0.5rem 0;
    color: var(--dim);
    font-size: 0.85rem;
  }
}

/* --- removing a task ---------------------------------------------------- */

/* The quietest control on the row, and red only once reached for. Deleting is
   recoverable -- the row survives and reopen restores it -- so this offers an
   undo afterwards rather than a confirmation before. */
/* Set further apart than the other two. They used to be told apart at a glance
   by width -- "d", "push", "rm" -- and identical icon boxes lose that cue,
   which matters most for the one that takes something away. */
.controls form.remove-form { margin-left: 0.5rem; }

td.act button.remove { border-color: transparent; }
tr:hover td.act button.remove { border-color: var(--rule); }
button.remove:hover { color: var(--red); border-color: var(--red); }

/* A real outline on focus, not just a colour. With no text, a 1px border
   change on a 13px glyph was the only sign the keyboard was on the control
   that deletes. */
button.remove:focus-visible {
  color: var(--red);
  outline: 2px solid var(--red);
  outline-offset: 1px;
}

/* The destructive action used to go on a phone too, so that a mis-tap on a
   small screen could not delete anything -- it lived only on the task's own
   page, where removing is a deliberate visit.

   **Chad asked for it back**, and the layout it returns to is a better answer
   than hiding it was: the controls take two lines now, so remove is not beside
   the check that completes. It is on the second line, at the far end, with the
   0.5rem that has always set it apart from its neighbours. A mis-tap was a
   width problem, and two lines is the width. Removing was never the
   irreversible thing the hiding implied either -- the row survives, `reopen`
   restores it, and the flash offers an undo. */
@media (max-width: 640px) {
  /* **The mobile pass --tap has been waiting for**, promised in the comment
     beside its definition and never written. At 1.7em a row action renders
     29.4 x 23px against Material's 48x48 guidance -- fine under a mouse,
     fiddly under a thumb.

     Height is free: the row simply gets taller. **Width is not** -- the 380px
     budget is what `row_width_test.rb` defends, so the padding rises only as
     far as that allows. Two controls survive at this width, which is what
     leaves the room to spend. */
  :root { --tap: 2.5rem; }
  /* **Height only.** Widening the controls was tried and the width tests said
     no: 0.7rem of padding on two buttons is 16px, and at 380px there is not
     16px to give -- the project and description cells are already at their
     floors. So a row action goes from 29x23 to 29x40, which is the half of
     the target that is free.
     The add card's summary rides on --tap and grows with it. */
  table.month td a { padding: 0.45rem 0.32rem; }

  /* **The actions take two lines**, which is what makes room for a third
     control without taking width back from the note:

         [ 30 minutes............... ]  check
         [1] push                          x

     `flex-basis: 100%` on the complete form gives it the whole first line, and
     the three that follow it in the DOM wrap onto the second. `.controls` is
     already a flex row with a 0.5rem gap that applies to both axes, so wrapping
     is the only thing that has to be said.

     **Both lines end at the same edge, which is what makes the icons a
     column**: the note takes the slack on the first line, so the check already
     sits hard right, and `td.act .controls` has justified to `flex-end` since
     it existed -- the row's controls hang off the right. So remove lands
     directly under the check with no second rule, and the eye has one edge to
     follow down. Left-aligning the second line instead put push under the note
     and the icons in two unrelated places. */
  td.act .controls { flex-wrap: wrap; }
  table.tasks form.complete { flex: 1 1 100%; }
  /* The pencil went for width rather than for safety: at 380px a third icon
     put the row over the edge, measured. **Two action lines are that width**,
     so it is back, between push and remove where the markup already puts it.
     Every control a task has is now on a phone. */
}

/* --- the add form ------------------------------------------------------- */

/* The echo line and the add control share a row. The summary is lifted out of
   flow to the right of it, so the closed control costs no vertical space at
   all, while the form it opens is still a full-width block underneath. */
.echo-row { position: relative; }
.echo-row > p.echo { padding-right: 2.5rem; }

details.add-card > summary {
  position: absolute;
  top: -0.15rem;
  right: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: var(--tap);
  padding: 0 0.45rem;
  color: var(--faint);
  background: var(--bg);
  border: 1px solid transparent;
  border-radius: 2px;
  cursor: pointer;
  list-style: none;
}
details.add-card > summary::-webkit-details-marker { display: none; }
details.add-card > summary:hover { color: var(--green); border-color: var(--green); }
details.add-card > summary:focus-visible {
  color: var(--green);
  outline: 2px solid var(--green);
  outline-offset: 1px;
}

/* Open, the plus becomes an x -- the same glyph turned, rather than a second
   one that would have to be drawn and kept in step. */
details.add-card[open] > summary { color: var(--dim); border-color: var(--rule); }
details.add-card[open] > summary svg { transform: rotate(45deg); }
/* The rename card wears a pencil, and a pencil turned 45 degrees is a pencil
   lying down rather than an x. Only the plus has a second meaning to turn into. */
details.rename-card[open] > summary svg { transform: none; }

/* The card is a card only once it holds something. */
details.add-card[open] {
  display: block;
  border: 1px solid var(--rule);
  margin: 0.4rem 0 1.25rem;
  padding: 0 0.7rem;
}

form.add-form {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem 0.9rem;
  align-items: flex-end;
  padding: 0.8rem 0 0.2rem;
}
form.add-form label { display: flex; flex-direction: column; gap: 0.2rem; }
form.add-form label.wide { flex: 1 1 16rem; }
form.add-form label span { color: var(--faint); font-size: 0.78rem; }
form.add-form input {
  font: inherit;
  color: var(--fg);
  background: var(--bg);
  border: 1px solid var(--rule);
  border-radius: 2px;
  padding: 0.25rem 0.4rem;
  min-width: 9rem;
}
form.add-form label.wide input { width: 100%; min-width: 0; }
form.add-form input:focus { outline: none; border-color: var(--green); }
form.add-form input::placeholder { color: var(--faint); }
/* The same bordered, transparent control the rows wear. These were browser
   default buttons -- light grey slabs in a dark page -- because `.do`'s box
   treatment is scoped to `.controls`. */
form.add-form .do,
form.add-form .cancel {
  font: inherit;
  height: var(--tap);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  line-height: 1;
  background: transparent;
  border: 1px solid var(--rule);
  border-radius: 2px;
  padding: 0 0.5rem;
  cursor: pointer;
}
form.add-form .do { color: var(--act-complete); }
form.add-form .do:hover, form.add-form .do:focus { color: var(--green); border-color: var(--green); }
form.add-form .do:focus-visible {
  outline: 2px solid var(--green);
  outline-offset: 1px;
}

/* Cancel is a way out, not a second action, so it stays quiet -- the same
   treatment reopen and the edit pencil get. */
form.add-form .cancel { color: var(--faint); }
form.add-form .cancel:hover { color: var(--cyan); border-color: var(--cyan); }
form.add-form .cancel:focus-visible {
  color: var(--cyan);
  outline: 2px solid var(--cyan);
  outline-offset: 1px;
}
details.add-card .note { padding-bottom: 0.7rem; }

/* --- the palette --------------------------------------------------------
   The command bar, enlarged and centred, opened with ctrl-k. Congo's is the
   same idea and close to the same rules; what differs is only the tokens.

   Nothing behind it scrolls while it is open, so the page you were reading
   stays where you left it. */
body.palette-open { overflow: hidden; }

.palette {
  position: fixed;
  inset: 0;
  z-index: 20;
  display: flex;
  justify-content: center;
  /* Near the top rather than centred: the box lands close to where the header
     bar already is, so the two feel like one thing in two sizes, and the page
     stays readable underneath. */
  align-items: flex-start;
  padding: 12vh 1.25rem 1.25rem;
  background: rgba(4, 6, 10, 0.72);
  backdrop-filter: blur(2px);
}

.palette-box {
  position: relative;
  width: 100%;
  max-width: 34rem;
  background: var(--bg-raised);
  border: 1px solid var(--rule);
  border-radius: 2px;
  box-shadow: 0 18px 50px rgba(0, 0, 0, 0.55);
}
.palette-box:focus-within { border-color: var(--green); }

.palette-prompt {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.55rem 0.75rem;
}
.palette-prompt input[type="text"] {
  font: inherit;
  font-size: 1.05rem;
  color: var(--fg);
  background: transparent;
  border: 0;
  outline: none;
  width: 100%;
  padding: 0;
}
.palette-prompt input::placeholder { color: var(--faint); }

/* The same menu the bar drops, seated rather than hanging. There is nothing
   below it to overlap here, so a seam reads better than a second shadow --
   and the box grows with the list instead of the list covering the page. */
.palette-box ul.completions {
  position: static;
  max-height: 50vh;
  border: 0;
  border-top: 1px solid var(--rule);
  border-radius: 0;
  background: transparent;
  box-shadow: none;
}

/* --- the landing page ---------------------------------------------------
   The page in front of the demo. Everything is scoped to `.landing` on the
   body: this stylesheet is shared with the app, and the app owns names like
   `.wordmark` and `.sub` that mean something else inside it.

   No new colours. The whole point is that this looks like the thing it is
   describing, so it uses the same tokens the app does and nothing besides. */
body.landing main { padding-bottom: 6rem; }

.landing .lede { padding: 4rem 0 2.5rem; }
.landing .wordmark {
  color: var(--green);
  font-weight: 600;
  letter-spacing: 0.02em;
  margin: 0 0 2rem;
}
.landing h1 {
  font-size: 1.9rem;
  line-height: 1.25;
  font-weight: 600;
  margin: 0 0 1rem;
  color: var(--fg);
}
/* **`--fg`, not `--dim`.** This is the page's body copy, and `--dim` measures
   4.19:1 on this ground -- under AA for text this size. The app uses `--fg`
   for what it means you to read and `--dim` for what it means you to skim,
   and a landing page is all the former. */
.landing .sub, .landing section > p {
  color: var(--fg);
  max-width: 34rem;
  line-height: 1.7;
  margin: 0 0 1.2rem;
}
.landing .sub { font-size: 1rem; }
.landing strong { color: var(--fg); font-weight: 600; }
.landing em { color: var(--fg); font-style: normal; }

/* The echo line, borrowed from every page in the app, because it is the one
   piece of furniture that says what this thing is without a sentence. */
.landing .echo { margin: 2rem 0 0.4rem; font-size: 1.05rem; }

/* Secondary, but still meant to be read -- these carry the caveats about the
   demo being shared and wiped. `--faint` is 2.02:1 here, which fails even the
   large-text threshold; it is for rules and disabled furniture, not sentences. */
.landing .aside { color: var(--dim); font-size: 0.9rem; margin: 0.4rem 0 0; line-height: 1.6; }

.landing .cta { margin: 2.5rem 0 0; display: flex; align-items: baseline; gap: 1rem; flex-wrap: wrap; }
.landing a.go {
  display: inline-block;
  border: 1px solid var(--green);
  color: var(--green);
  padding: 0.5rem 1.1rem;
  border-radius: 2px;
  text-decoration: none;
}
.landing a.go:hover { background: var(--green); color: var(--bg); }
.landing a.go:focus-visible { outline: 2px solid var(--green); outline-offset: 2px; }

.landing section { padding: 3rem 0 0; border-top: 1px solid var(--rule); margin-top: 3rem; }
.landing section h2 { font-size: 1.15rem; font-weight: 600; color: var(--fg); margin: 0 0 1rem; }
.landing section h3 { font-size: 1rem; font-weight: 600; color: var(--amber); margin: 0 0 0.8rem; }

/* The reference table is the help page's, unchanged. */
.landing table.reference { margin: 1.5rem 0; }

/* A sample of the real series rows, which are normally in a card. */
.landing .series-grid.is-sample {
  background: var(--bg-raised);
  border: 1px solid var(--rule);
  border-radius: 2px;
  padding: 0.8rem 1rem;
  margin: 1.5rem 0;
  columns: 1;
}

.landing .waitlist {
  background: var(--bg-raised);
  border: 1px solid var(--rule);
  border-radius: 2px;
  padding: 1.2rem 1.4rem;
  margin: 2rem 0 0;
  max-width: 34rem;
}
.landing .waitlist-form { display: flex; gap: 0.6rem; flex-wrap: wrap; }
.landing .waitlist-form input[type="email"] {
  flex: 1 1 16rem;
  font: inherit;
  color: var(--fg);
  background: var(--bg);
  border: 1px solid var(--rule);
  border-radius: 2px;
  padding: 0.45rem 0.6rem;
}
.landing .waitlist-form input[type="email"]:focus { outline: none; border-color: var(--green); }
.landing .waitlist-form input[type="email"]::placeholder { color: var(--faint); }
.landing .waitlist-form input[type="submit"] {
  font: inherit;
  color: var(--amber);
  background: transparent;
  border: 1px solid var(--amber);
  border-radius: 2px;
  padding: 0.45rem 1rem;
  cursor: pointer;
}
.landing .waitlist-form input[type="submit"]:hover { background: var(--amber); color: var(--bg); }

.landing .landing-foot { margin-top: 4rem; padding-top: 1.5rem; border-top: 1px solid var(--rule); }

@media (max-width: 640px) {
  .landing .lede { padding-top: 2.5rem; }
  .landing h1 { font-size: 1.5rem; }
}

/* The demo's standing notice. Amber rather than red: it is a caveat about what
   this instance is, not an error and not a destructive warning -- and red here
   would cry wolf on every page of a site whose whole job is to be poked at. */
.flash.is-demo {
  border-color: var(--amber);
  color: var(--amber);
}

/* The way back out of the demo. Underlined rather than merely coloured: it
   sits inside a band that is already entirely amber, so colour alone would not
   say "this one is a link". */
.flash.is-demo .demo-back {
  color: var(--amber);
  text-decoration: underline;
  white-space: nowrap;
  margin-left: 0.3rem;
}
.flash.is-demo .demo-back:hover { color: var(--fg); }
