/* A terminal. Dark, monospace, minimal.
   The house-style rule still governs everything: no text-transform anywhere,
   no capitalisation of any kind. Lyric text is monospace not for decoration
   but because it preserves the shape of the line -- indentation, trailing
   spaces, the alignment of a repeated phrase -- the way the file has it. */

:root {
  color-scheme: dark;

  /* The ground, and the palette above it, is black_is_the_color -- the vim
     scheme chad writes in (plainfingers/black_is_the_color, originally based on
     tir_black). The hexes below are its GUI values rather than the cterm-256
     approximations a terminal shows, which are noticeably more acid.

     Normal is literal black there. This is a hair off it, so the two dozen
     --bg-raised surfaces separate without a border on every one -- the same
     distance vim keeps between Normal and its #202020 StatusLine. */
  --bg:        #08090c;
  --bg-raised: #14161c;
  --bg-hover:  #1c1f27;
  --rule:      #2a2e39;
  /* The stronger hairline. --faint was drawing this on nine borders while also
     being the quietest text in the app, which is exactly why it could not be
     made readable: every form outline and button edge came up with it. */
  --edge:      #3f4450;

  /* AN EXPERIMENT, and the whole of it is these four lines plus the heading
     rules that cite them. The `main` branch is the palette this came from.

     The idea is the one the vim scheme actually follows, which the app did not:
     **grey is for comments and nothing else.** In black_is_the_color almost
     nothing on screen is grey -- Comment is, and everything else carries a hue.
     Here there were three levels of neutral doing the work of a hierarchy, and
     a label was told from a fact by being darker rather than by being a
     different kind of thing.

     So the two label tiers are chromatic now, both out of one family so they
     read as one thing at two volumes, and --comment keeps the grey on its own.

     The family is **teal**, and that was a second pass. The first tried the
     scheme's yellows, which is the obvious neighbour of a lime-green app and
     exactly the problem: --dim at neon_yellow sat deltaE 34 from --green, and
     in the header the nav read as a slightly-off version of the wordmark
     rather than as a different kind of thing. Teal is 80 from it, and it is
     the furthest from *every* accent rather than only from the green.

     It also puts the furniture in a temperature of its own. Warm means
     something here -- amber is a ?, --act-edit is a control -- and cool is the
     page talking about itself.

     Body text stays neutral and stays the brightest, which is the one place
     this could go wrong: a lyric is what the page is for, and a column heading
     that outshines it would be the page pointing at its own furniture. --fg is
     lifted to keep that order -- both label tiers sit under it. */
  --fg:        #e8e8ea;   /* 16.9 -- body text, a lyric, a filename */
  --dim:       #7fcaca;   /* 10.6 -- turquoise/Delimiter, lightened until it is
                                     readable. field labels, nav, second rank */
  --faint:     #6aa8a8;   /*  7.4 -- the same hue, dimmed. the quietest
                                     metadata, a gutter, a placeholder */
  /* The only grey left in the app, and it is vim's Comment exactly: a line
     opening with "#", and the headings that name a window or a card. */
  --comment:   #8a8a8a;   /*  5.8 */
  /* And one more neutral, cooler, for the two things that are neither a label
     nor a comment nor the text itself: a line number, and the crumb you are
     standing on. Both are an *address* rather than a fact or a gloss, and both
     want to be legible without joining the colour system -- a gutter running
     down the whole height of a lyric in a label colour is the loudest quiet
     thing on the page. */
  --soft:      #808a96;   /*  5.5 */

  --green:     #a8ff60;   /* String/lime  -- where you are, the prompt, success */
  --cyan:      #96cbfe;   /* Keyword/blue -- links, directories */
  --amber:     #fd971f;   /* orange       -- uncertainty, fuzzy matches */
  /* Back to the dusty mauve this app has always worn, rather than the scheme's
     purple. #ae81ff is a saturated violet, and a glossary term is a mark *on*
     a lyric -- it has to be visible without competing with the words it marks,
     and against a teal-and-white page the violet was winning. */
  --mauve:     #b48ead;   /*  7.0 -- glossary terms */
  /* A count of something that is here: songs, lines, a linked recording. The
     one colour added rather than retuned, and it has that single job -- the
     rule the long listing already states, that a count wears the colour of what
     it counts, applied where the count was grey. */
  --sage:      #99cc99;   /* Constant/seaweed */

  /* The row action, borrowed from tracker where it is --act-push and draws the
     orange ">>". Deliberately NOT --amber: amber means uncertainty here, and a
     button the colour of a ? sitting beside a column of them would be the page
     saying two things with one colour. vim's rubyControl, which is the colour
     on `do`, `end` and `if` -- the verbs of the language, which is what this
     marks here. Measured rather than eyeballed: it is ΔE 38 from --amber
     against the 19 the old #b5934f/#d9a441 pair managed, so they are further
     apart than they have ever been.

     And *below* both amber and the body text in lightness, which the first
     attempt at this was not: vim's Function colour cleared the separation test
     easily and then made the pencil at the end of a listing row the brightest
     thing on the page. A control you may press should not outshine the line
     you came to read. What still separates it from a ? on the same row is that
     this one is a bordered box and a count is bare text. */
  --act-edit:  #e18964;

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

  /* The site header is sticky, so anything else that sticks has to clear it.
     Measured, not guessed: the bar is 56px tall, and 3.1rem was tucking the
     pinned headings 7px underneath it. */
  --header-h: 3.55rem;
  /* Flush against the bar, with the breathing room inside the pinned element
     instead. A gap here is see-through: scrolling text slides through the
     sliver between the bar and whatever is pinned below it. */
  --sticky-top: var(--header-h);
  --measure: 52rem;

  /* An album's counted columns: songs, lines, ?, terms, linked, released.
     Named once because three separate grids lay them out and none of them can
     share a box -- the tree's header, the tree's rows (a <summary> cannot be a
     row of a table), and the listing the command bar prints. They had drifted
     in two places at once: the header stayed on the old count while `-t` gave
     the rows one more track, and the listing rule meant for a dated album was
     written with the undated selector, so it silently did nothing. */
  --album-cols: 5rem 5.25rem 3.25rem 4.75rem 5.5rem 5.5rem;

  /* A term row's counted columns: lines, songs, and the term itself. Named
     alongside the album's for the same reason -- the header is a separate
     element from the rows and the two can only agree by sharing a definition,
     which the album grid learned the hard way. */
  /* 11rem on the term because the longest in the glossary is "Seven African
     Powers" at twenty characters, and a clipped term name is unreadable in a
     way a clipped definition is not -- the definition continues inside, the
     name is the thing you came to find. Measured rather than guessed: a system
     test puts a name that long on the page and checks it is not cut. */
  --term-cols: 4.75rem 4.75rem 11rem;

  /* The player is one line and always the same height -- which is the whole
     reason it needs a token: the editor's foot and the page's last row both
     have to clear it, and neither can measure it. The iframe panel it replaces
     was an accordion, and everything that had to know its height had to be
     told twice. */
  --player-h: 2.1rem;
}

* { box-sizing: border-box; }

/* Author styles beat the browser's own [hidden] { display: none } rule
   regardless of specificity, so any element given `display` in this file stops
   hiding. That is not hypothetical: .palette sets display:flex, which left the
   overlay covering every page with only its input visible. !important here so
   the attribute always wins, whatever a rule below says. */
[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;
}

/* Derived rather than hand-mixed. The three tints below follow the same rule:
   a colour written out by hand duplicates a token, and the copy goes stale in
   silence the next time the token moves -- which is exactly what happened to
   the three rgba() values this pass replaced. */
::selection { background: color-mix(in srgb, var(--cyan) 28%, var(--bg)); color: var(--fg); }

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;
}

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

header.site {
  border-bottom: 1px solid var(--rule);
  background: var(--bg-raised);
  position: sticky;
  top: 0;
  z-index: 5;
  /* Content passes through the gap between this bar and a pinned heading. The
     shadow damps that sliver so it reads as passing behind the bar rather than
     as a stray line of text wedged above the heading. */
  box-shadow: 0 8px 12px -8px rgba(0, 0, 0, 0.75);
}

.bar {
  /* **Wider than the reading measure, and measured rather than chosen.** The
     bar needs about 840px to hold six items, an 18rem prompt that has its own
     asserted minimum, and the sign-out -- and `--measure` leaves 792px inside
     its padding, so the header silently became three rows 94px tall while
     `--header-h` went on saying 57. Every sticky heading in the app measures
     down from that number, so the whole app's pinning was half a bar out.

     52rem was never an alignment guarantee anyway: `main` is 76rem on a song
     page and 104rem in the editor, so the bar has always been narrower than
     the content on most of the app. This makes it narrower on fewer pages. */
  max-width: 60rem;
  margin: 0 auto;
  padding: 0.55rem 1.25rem;
  display: flex;
  align-items: center;
  gap: 1rem;
  flex-wrap: wrap;
}

/* **The bar is --cyan now, and that is the palette's own rule rather than a
   new taste.** --cyan is "links, directories" and the bar names the corpus's
   directories -- so it is not borrowing a meaning, it is finally wearing the
   one it has. It was --dim, the label tier, from when these were verbs: tools
   you reach for, which is furniture, which is what teal is for. `glossary` was
   in that bar because `g` is a command, not because it is a place.

   Chad asked about a soft orange. Measured, that is the one direction closed
   off: warm means something specific here -- --amber is a ?, --act-edit is a
   control -- and a soft orange lands ΔE 16.9 from --act-edit, against the 38
   this file records as the distance that made those two safe. A nav that
   colour reads as a row of buttons.

   --cyan is also simply better separated than what it replaces: 10.57:1
   against the raised ground where teal was 9.65, and ΔE 107.6 from --green
   where teal was 79.8. The thing this bar most has to say is which one item
   is green, and the gap doing that work got wider.

   --dim keeps every other job it had -- field labels, second rank. Only the
   nav leaves that tier, because only the nav stopped being furniture.

   Green means one thing: where you are. The mark keeps `font-weight`, which
   outlives the word it was written for and costs nothing if the word ever
   comes back. */
.wordmark { color: var(--cyan); font-weight: 800; display: inline-flex; align-items: center; }
.wordmark:hover { color: var(--fg); text-decoration: none; }
.wordmark.is-here { color: var(--green); }

/* The mark takes the wordmark's colour rather than having one -- so it is
   --dim at rest and --green at home, which is the same sentence the word
   used to say. `display: block` because an inline svg sits on the text
   baseline and leaves a descender's worth of space under it, which put the
   bar's items half a pixel out of line with each other. */
.wordmark .mark { display: block; }

/* One bar, and it moves. Drawn on every item and coloured on one -- if it were
   added and removed instead, the header would shift every time the section
   changed. That rule is older than this shape and survives it.

   **Under the word rather than before it**, because the header is a tab strip
   and was the only one in the app not drawn like one. `.pane-head.is-tabbed`
   marks its current buffer with a green bottom edge; this is the same
   question -- a row of places, exactly one of them current -- and it was
   answering it with a `▍` block instead. Two strips saying "current" two
   different ways is the inconsistency; this is not a new idiom, it is the
   app's own.

   Three things it turned out to buy, none of which was the reason:

   - **about 55px back**, five markers at ~11px each. The prompt is the
     immediate beneficiary: its placeholder was being clipped to "hel".
   - a wider mark. The edge is as long as the word, where the block was one
     glyph regardless -- so "where you are" is a bigger signal on a longer
     label, which is the one that needs it.
   - the two strips now agree at a glance, which is what makes a reader stop
     having to learn a second vocabulary for the same fact.

   The measured worry was that an underline would float mid-bar and read as a
   stray line above the header's own border. It sits 15px clear and does not:
   the header's rule is --rule and this is --green at 2px, so they are not the
   same kind of line. No tint band under it either, unlike the book tabs --
   those need one because their current tab only goes to --fg, and this one is
   already green text *and* a green edge. */
header.site .wordmark,
header.site nav a {
  border-bottom: 2px solid transparent;
  padding-bottom: 0.15rem;
}
header.site .is-here { border-bottom-color: var(--green); }

/* Flex at every width, which it was not: above 46rem the anchors were inline
   and spaced only by the whitespace the erb happened to collapse, plus the
   0.35rem the marker carries. Four items got away with it. Six do not -- an
   accidental space is not a gap, and `transcriptions books tarot` ran
   together. The narrow block below declared this already; it just never
   applied to the width the bar is usually read at. */
header.site nav { display: flex; align-items: center; gap: 0.9rem; }

header.site nav a { color: var(--cyan); }
header.site nav a:hover { color: var(--fg); text-decoration: none; }

/* Where you are: green, with the bar beside it. No underline -- the moving bar
   says it, and an underline as well was saying it twice. */
header.site nav a.is-here { color: var(--green); }
header.site .is-here:hover { text-decoration: none; }

/* Two labels ship in every anchor and css picks one. Declared *before* the
   media query that swaps them, never after: both rules are `header.site .short`
   at the same specificity, so a default placed afterwards wins on source order
   alone and hides every label at every width -- a header of five empty links.
   The same ordering mistake the narrow-screen block made with the listing
   headers, which is twice now in one stylesheet. */
header.site .short { display: none; }

/* Who you are, which is not where you are -- so it sits outside the nav and
   never wears green. Quiet by default: on most pages it is the least
   interesting thing in the bar. */
.whoami {
  display: flex;
  /* center, not baseline: the control is an icon in a <details> now rather
     than a word, and a baseline it has no text on puts it a few pixels low. */
  align-items: center;
  gap: 0.5rem;
  font-size: 0.78rem;
  color: var(--faint);
}
.whoami a { color: var(--dim); }
.whoami a:hover { color: var(--fg); text-decoration: none; }
.whoami .who { color: var(--dim); }
.whoami form { margin: 0; }
.whoami .sign-out {
  font: inherit;
  font-size: 0.78rem;
  color: var(--faint);
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
}
.whoami .sign-out:hover { color: var(--act-edit); }

/* --- the user menu -------------------------------------------------------

   A <details>, so it opens with no javascript -- and therefore a panel that
   must not take space in the bar's flex row. `--header-h` is one number every
   sticky heading in this app measures down from, and narrow_test asserts the
   bar is within 2px of it at 390px, so a panel in flow would push every
   pinned heading in congo down by its own height on every page.

   Absolute, anchored, z-index 20: the same three lines `.cmd-menu` uses, which
   is the command bar's completion dropdown and the only other menu here. */
.user-menu { position: relative; display: flex; }

/* The marker off, the way every other summary in this app takes it off. The
   pilgrim is the whole of the control. */
.user-menu > summary {
  list-style: none;
  cursor: pointer;
  display: flex;
  align-items: center;
  color: var(--faint);
}
.user-menu > summary::-webkit-details-marker { display: none; }
.user-menu > summary:hover,
.user-menu[open] > summary { color: var(--act-edit); }

/* The pilgrim takes its colour from the summary, which is what `currentColor`
   in the partial is for. A focus ring on the summary rather than on the svg:
   the svg is aria-hidden and is not the thing being focused. */
.user-menu > summary:focus-visible { outline: 1px solid var(--green); outline-offset: 2px; }

.user-menu-panel {
  position: absolute;
  top: calc(100% + 0.5rem);
  /* Right-aligned because the control is the last thing in the bar: a panel
     hung from its left edge would run off the window. */
  right: 0;
  z-index: 20;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.4rem;
  min-width: 9rem;
  padding: 0.6rem 0.75rem;
  background: var(--bg-raised);
  border: 1px solid var(--edge);
  border-radius: 2px;
  box-shadow: 0 8px 16px -8px rgba(0, 0, 0, 0.8);
}

/* The name, in the one place it costs the prompt nothing. */
.user-menu-panel .who {
  font-size: 0.78rem;
  padding-bottom: 0.35rem;
  border-bottom: 1px solid var(--rule);
  width: 100%;
}
/* An address rather than a name: quieter, because it is a fallback and not a
   thing anybody chose. It is also the only place in congo an email is drawn,
   and it is drawn only to the person it belongs to. */
.user-menu-panel .who.is-nameless { color: var(--faint); }

/* The search field is a prompt, and it takes the rest of the bar.
   It has to hold the longest thing tab can complete to -- at this size
   "v dont-lose-your-soul/inner-adversary" is 301px, and 17rem of box left
   about 200px for it, cutting the name off halfway. The corpus counts used to
   sit to the right of here; `ls -l ~` prints them per album and `?` prints the
   uncertainty total, so nothing was lost by giving the room to the prompt. */
.search {
  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: 18rem;
  max-width: 34rem;
}
.search:focus-within { border-color: var(--green); }
.search .prompt { color: var(--green); user-select: none; }

input[type="search"] {
  font: inherit;
  color: var(--fg);
  background: transparent;
  border: 0;
  outline: none;
  padding: 0.25rem 0;
  width: 100%;
}
/* What a box is asking for, never something already in it.
   **--soft, which its own definition already covers**: it is there for "the
   two things that are neither a label nor a comment nor the text itself", and
   a placeholder is exactly that -- legible without joining the colour system.
   It was --faint, the quietest *label* tier, which made the hint read as one
   more thing the form was telling you rather than as the shape of an answer.

   Said once for every box in the app rather than six times: this was repeated
   on the search bar, the palette, three forms, the editor bar and the shelf's
   find box, all saying the same thing. One rule at the lowest specificity
   that will do, which is the habit `.col` already records. */
::placeholder { color: var(--soft); }
input[type="search"]::-webkit-search-cancel-button { filter: invert(0.5); }

/* The header's own breakpoint, and after `.search` on purpose -- the minimum
   being lifted here is declared above, and a rule that gives something up has
   to come after the rule that asked for it.

   Below this the five words and a prompt that insists on 18rem cannot share a
   line and the bar wraps to two. So the labels become the commands that reach
   the same pages and the prompt takes whatever is left: one row, which is what
   the rest of the app measures from in --header-h. Still `flex-wrap: wrap`, so
   a browser at 200% text size gets two lines rather than a page that scrolls
   sideways. */
/* 56rem, up from 46rem. The measured point where six labels, the prompt and
   the sign-out stop sharing a line -- it was the measured point for five and
   a narrower bar. Below here the prompt takes a row of its own. */
@media (max-width: 56rem) {
  .bar { gap: 0.75rem 1rem; padding-left: 0.85rem; padding-right: 0.85rem; }
  /* Only the gap, now that the display is declared unconditionally above. */
  header.site nav { gap: 0.65rem; }

  /* The prompt takes a line of its own rather than a share of one. It is the
     thing you type into and the one control here worth a whole row -- the
     labels are somewhere to press. Two rows, so everything pinned under the
     header has to clear both of them, which is what --header-h is read for.

     `order: 3` rather than moving it in the markup: it is last in the bar
     already, and the order property is what says "below" without saying it
     twice. The basis is the whole 100% on purpose -- a wrapping flex container
     picks its line breaks from each item's *base* size before it shrinks
     anything, so this is the line break, not a hint towards one. */
  .search { min-width: 0; flex: 1 1 100%; order: 3; }
  /* 5.95rem, measured at 94.64px. It was 5.6rem, which is 89.6 -- so once the
     nav items grew a bottom edge and the padding under it, every sticky
     heading on a phone sat five pixels *underneath* the bar. Rounded up
     rather than to the nearest: overshooting leaves a hairline gap under a
     pinned heading, undershooting hides its first row, and only one of those
     is a bug. */
  :root { --header-h: 5.95rem; }
}

/* A second, lower breakpoint, because the two changes stop being needed at
   different widths. With the prompt on its own line the words have a whole row
   to themselves and keep it a long way down -- so they are not shortened until
   they stop fitting that row, which is here and not at 46rem. */
/* 33rem, up from 30rem. At 30rem the long labels needed 452px of a 453px row
   -- one pixel of slack, which is not a breakpoint, it is a coincidence. */
@media (max-width: 33rem) {
  header.site .long { display: none; }
  header.site .short { display: inline; }

}

/* --- headings and paths -------------------------------------------------- */

h1 {
  font-size: 1.05rem;
  font-weight: 600;
  margin: 1.25rem 0 0.2rem;
  color: var(--fg);
}
h1::before { content: "$ "; color: var(--green); }

/* EXPERIMENT: a heading names a thing rather than stating one, which is what
   a comment does too -- so the headings keep the grey along with the "#"
   lines. The five of them are h2, h3.group, a tradition band, and a card's
   own h2 and h3. */
h2 {
  font-size: 0.85rem;
  font-weight: 600;
  margin: 2rem 0 0.5rem;
  color: var(--comment);
}

/* A label over a group of rows, on the reference pages. Classed rather than a
   bare h3 rule: `h3` has no style of its own in here, two scoped copies already
   define it identically (.pane-term h3, section.card > h3), and a third rule
   reaching every h3 in the app would change pages this has no business
   touching. */
h3.group {
  font-size: 0.75rem;
  font-weight: 400;
  margin: 1.2rem 0 0.2rem;
  color: var(--comment);
}

.path, code {
  font-size: 0.8rem;
  color: var(--dim);
}
/* The crumbs are their own rule now. What the element's own colour reaches is
   exactly the last crumb -- the one that is not a link, because it is where you
   already are -- since the links and the separators both say otherwise below.
   It is an address rather than a label, so it takes --soft with the gutter. */
.crumbs {
  font-size: 0.8rem;
  color: var(--soft);
  margin-top: 0.5rem;
}
/* A crumb is a link and wears the colour a link wears, which leaves the
   separators and the last crumb -- the one that is not a link, because it is
   where you already are -- as the quiet part of the line. It used to be grey at
   rest and cyan only under the pointer, so the one part of the line you could
   act on was the one part that said nothing until you found it. Hover
   brightens to --fg, which is what every other link in the header does. */
.crumbs a { color: var(--cyan); }
.crumbs a:hover { color: var(--fg); }
.crumbs .sep { color: var(--faint); }

/* Prose asides read as comments, because that is what they are. */
.note {
  color: var(--comment);
  font-size: 0.8rem;
  margin: 0.4rem 0 1.5rem;
  max-width: 46rem;
}
.note::before { content: "# "; }

/* One line until it is wanted.
   **The class is put on by notes.js, never by markup**, and that is the point
   rather than an implementation detail: clamping here alone would put the rest
   of the sentence out of reach of a reader with no javascript. Applied from
   there, javascript off leaves every note reading in full.

   `white-space: nowrap` rather than `-webkit-line-clamp`, because one line is
   one line at every width and line-clamp needs a fixed count that reads
   differently on a phone. */
.note.is-clamped,
.candidate-why.is-clamped {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  cursor: pointer;
}
/* Interactive, said the way `.edit-cancel` says it -- the quiet thing comes up
   to the foreground under the pointer. The ellipsis is the other half: it is
   there only when there is more, because notes.js takes the class back off a
   note that already fits. */
.note.is-clamped:hover,
.candidate-why.is-clamped:hover { color: var(--fg); }
.note[tabindex]:focus-visible,
.candidate-why[tabindex]:focus-visible {
  outline: 2px solid var(--act-edit);
  outline-offset: 2px;
}
/* A count is a fact, not a label: how many terms are in a buffer is what tells
   you whether to open it. The group of them -- here, on a tab, on a pane head,
   on a hit, on a deck row -- sat at the quietest level in the app, which is
   where a label belongs and a figure does not. */
.count { color: var(--dim); font-size: 0.78rem; }

/* --- lists --------------------------------------------------------------- */

ul.albums, ol.tracklist, ul.terms { list-style: none; margin: 0; padding: 0; }

ul.albums li, ol.tracklist li, ul.terms li {
  display: flex;
  align-items: baseline;
  gap: 0.85rem;
  padding: 0.3rem 0.5rem;
  border-left: 2px solid transparent;
}
ul.albums li:hover, ol.tracklist li:hover, ul.terms li:hover {
  background: var(--bg-hover);
  border-left-color: var(--rule);
}

/* Track numbers are the ones in the filenames. A gap is real information. */
.track { color: var(--faint); flex: none; }
ol.tracklist .count, ul.albums .count, ul.terms .count { margin-left: auto; }

/* --- lyric lines --------------------------------------------------------- */

.lyric {
  margin: 1.25rem 0;
  border-left: 1px solid var(--rule);
}

.line {
  display: flex;
  gap: 0.9rem;
  align-items: baseline;
  padding: 0 0.5rem;
  min-height: 1.7em;          /* a blank line keeps its row, as in an editor */
}
.line:hover { background: var(--bg-hover); }

/* The gutter, like an editor's. Numbers are the real file line numbers, so
   they match what the editor shows and how a line is cited in the notes. */
.line-number, .gutter {
  color: var(--soft);
  flex: none;
  min-width: 3ch;
  text-align: right;
  user-select: none;
  font-size: 0.78rem;
}
a.line-number:hover { color: var(--amber); text-decoration: none; }

/* pre-wrap, so trailing spaces and indentation in the file survive. The
   string itself is never touched. */
.line-text {
  white-space: pre-wrap;
  overflow-wrap: break-word;
}

/* A "# chorus" line is markdown in the file and reads as a shell comment here.
   It is still not classified -- this is styling, not a claim about meaning. */
.line.is-heading .line-text { color: var(--comment); }

/* The control that makes the link, in the column under the recording. */
.share-control { margin: 1rem 0 0; }
.share-control .note { margin-top: 0.4rem; }
.share-form { margin: 0.5rem 0 0; }
.share-make, .share-revoke { font-size: 0.78rem; }
.share-make { color: var(--act-edit); border-color: var(--edge); }
.share-make:hover { background: var(--bg-hover); color: var(--fg); }

/* The address itself, which is the thing you came for once it exists. Readable
   and selectable rather than a link: it is going into a message to somebody
   who is not sitting at this machine. */
/* A note that is a caution rather than an explanation. Amber, which is what
   uncertainty already wears here -- nothing is broken, but what you are
   looking at will not do what you are about to ask of it. */
.note.is-warning { color: var(--amber); }

/* The address and the button on one line: the box is the thing you came for
   and the button is what you do with it. */
.share-address { display: flex; gap: 0.3rem; margin-top: 0.4rem; }
.share-copy {
  flex: none;
  font-size: 0.72rem;
  padding: 0.2rem 0.5rem;
  color: var(--act-edit);
  border-color: var(--edge);
  /* Wide enough for the longest thing it ever says, so saying it does not move
     the box beside it. */
  min-width: 6.5rem;
}
.share-copy:hover { background: var(--bg-hover); color: var(--fg); }

.share-url {
  width: 100%;
  font: inherit;
  font-size: 0.72rem;
  color: var(--green);
  background: var(--bg-raised);
  border: 1px solid var(--edge);
  border-radius: 2px;
  padding: 0.3rem 0.4rem;
}
.share-url:focus { outline: none; border-color: var(--green); }

/* Two buffers in one window, chosen by a tab. The head becomes the tab strip
   rather than gaining one: a window has one line that says what it is showing,
   and two would be a line about a line. */
.pane-head.is-tabbed {
  display: flex;
  gap: 0;
  padding: 0;
}
.pane-tab {
  display: inline-flex;
  align-items: baseline;
  gap: 0.35rem;
  padding: 0.2rem 0.6rem;
  color: var(--faint);
  border-bottom: 1px solid var(--rule);
}
.pane-tab:hover { color: var(--dim); text-decoration: none; background: var(--bg-hover); }

/* The current one is the only thing in the strip wearing the foreground, and
   its bottom edge is open -- the tab and the buffer under it are one shape,
   which is what makes the other one read as behind rather than beside. */
.pane-tab.is-current {
  color: var(--fg);
  border-bottom-color: transparent;
}
.pane-tab.is-current:hover { background: none; }
.pane-tab .count { color: var(--dim); }

/* Two spellings ship in the tab and css picks one, the way the header's nav
   already does -- "what is known" becomes "known", "filed under" becomes
   "filed". Four full labels want about 430px and a phone has 390, so they
   wrapped to a second row, which is a strip about a strip.

   **Declared before the media query that swaps them, never after.** Both rules
   are one class at the same specificity, so a default placed afterwards wins
   on source order alone and hides every label at every width. The header
   carries a comment saying that has already been got wrong twice in this
   file; this is the third place it could be. */
.pane-tab .short { display: none; }

@media (max-width: 34rem) {
  .pane-tab .long { display: none; }
  .pane-tab .short { display: inline; }
}

/* --- a shared song ------------------------------------------------------- */

/* The page chad sends a friend. It is the only page here written for somebody
   who cannot edit, and it reads differently on purpose: no gutter, no hover
   band, no rule down the side. Those are an editor's furniture and they are
   for citing a line to somebody who has the file. A reader who was sent a song
   is reading a song. */
/* Wider than the ordinary measure, for the reason the song page is: with a
   flank on either side, an ordinary-width page leaves the lyric narrower than
   the lyric needs -- it was 304px of a 1400px window, with the longest line at
   284, so lines were a few characters from wrapping.

   Not the song page's 104rem either. That page is a workbench with two
   working panes; this one is a lyric with a reference either side, and a
   column of short lines stretched across a wide monitor reads as a billboard
   rather than a song. */
.share {
  max-width: 76rem;
  margin: 0 auto;
  padding: 3rem 1.25rem 4rem;
}
.share-head h1 { font-size: 1.1rem; margin: 0; }
.share-album { color: var(--dim); font-size: 0.8rem; margin: 0.3rem 0 0; }

/* No frame down the left, because the head bar above already closes this
   column off against the glossary beside it. The lines keep the reading page's
   inset now that they have its gutter -- a number flush against the edge of
   the column reads as part of the border rather than as part of the line. */
.lyric.is-shared { border-left: none; margin: 2rem 0; }
.lyric.is-shared .line:hover { background: none; }

/* Where to go and hear it, which is the reason the page was sent -- so it is a
   panel of its own rather than the last thing below a lyric that can run to
   forty lines. Mirrors the glossary pane: same head, border on the other
   side. */
.share-listen {
  border-left: 1px solid var(--rule);
  padding-left: 1.25rem;
  font-size: 0.78rem;
}
.share-services { list-style: none; margin: 0.6rem 0 0; padding: 0; }
.share-services li { margin-bottom: 0.3rem; }

/* Three columns, the arrangement the reading page has: what the words mean,
   the song, and where to hear it. Either flank can be absent -- a song with no
   glossary words, or none linked yet -- so the grid asks which are there with
   :has() rather than being told by a class. One shape, not four.

   On a narrow screen, which is where a link somebody was sent usually gets
   opened, they stack in document order. */
.share-split {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 0 1.75rem;
  margin-top: 1.75rem;
}
/* The flanks are fixed and the song takes what is left, which is the reading
   page's arrangement: a reference column wants a predictable width and a lyric
   wants room. The listen panel is narrower than the glossary because a service
   name is one word and a description is a paragraph. */
.share-split:has(.share-pane) { grid-template-columns: minmax(0, 16rem) minmax(0, 1fr); }
.share-split:has(.share-listen) { grid-template-columns: minmax(0, 1fr) minmax(0, 12rem); }
.share-split:has(.share-pane):has(.share-listen) {
  grid-template-columns: minmax(0, 16rem) minmax(0, 1fr) minmax(0, 12rem);
}

/* The flanks and the gutters come to 34rem between them, so below about 60rem
   of window the song would get less room than its longest line wants. They
   stack in document order there: what the words mean, the song, where to hear
   it. */
/* The strip exists at every width and is shown at one. Which of the three is
   on screen is a question only a narrow window asks; on a wide one they are
   all on screen at once and a tab would be a control for nothing. */
.share-tabs { display: none; }

@media (max-width: 60rem) {
  .share-split,
  .share-split:has(.share-pane),
  .share-split:has(.share-listen),
  .share-split:has(.share-pane):has(.share-listen) {
    grid-template-columns: minmax(0, 1fr);
  }
  .share-pane {
    border-right: 0;
    padding-right: 0;
  }
  .share-listen {
    border-left: 0;
    padding-left: 0;
  }

  .share-tabs {
    display: flex;
    gap: 0;
    position: sticky;
    top: 0;
    z-index: 1;
    margin: 1.5rem 0 0;
    /* Opaque, or the lyric scrolls through it. */
    background: var(--bg);
    border-bottom: 1px solid var(--rule);
  }
  .share-tab {
    flex: 1;
    font: inherit;
    font-size: 0.8rem;
    color: var(--dim);
    background: none;
    border: none;
    border-bottom: 2px solid transparent;
    border-radius: 0;
    padding: 0.6rem 0.4rem;
  }
  /* A tap target rather than a click target: 44px is what a thumb wants, and
     these are the only controls on the page. */
  .share-tab { min-height: 2.75rem; }
  .share-tab .count { color: var(--dim); }
  .share-tab.is-current {
    color: var(--fg);
    border-bottom-color: var(--green);
  }

  /* Only once javascript has said so. Without it the strip stays hidden and
     all three panels stay in the page, which is the long scroll this replaces
     -- worse, and nothing is unreachable. */
  .share-split.is-tabbed [data-share-panel] { display: none; }
  .share-split.is-tabbed [data-share-panel].is-showing { display: block; }

  /* Stacked, each section wants the rule between them. Tabbed, only one is
     ever on screen and a border under it is a line to nowhere. */
  .share-split:not(.is-tabbed) .share-pane {
    border-bottom: 1px solid var(--rule);
    padding-bottom: 1rem;
  }
  .share-split:not(.is-tabbed) .share-listen {
    border-top: 1px solid var(--rule);
    padding-top: 1rem;
    margin-top: 1.5rem;
  }
}

/* The bar above the lyric, which is the reading page's minus the pencil. It
   is what makes this column and the glossary beside it start on the same line,
   and it is not sticky here: there is no site header for it to stick beneath. */
.share-lyric .pane-status.is-head {
  position: static;
  margin-bottom: 0.75rem;
}

.share-pane {
  border-right: 1px solid var(--rule);
  padding-right: 1.25rem;
  font-size: 0.78rem;
}
.share-pane-head {
  display: flex;
  gap: 0.5rem;
  align-items: baseline;
  color: var(--dim);
  padding-bottom: 0.5rem;
  border-bottom: 1px solid var(--rule);
}

.share-terms { list-style: none; margin: 0.6rem 0 1rem; padding: 0; }
.share-terms li { margin-bottom: 0.25rem; }
.share-terms a { color: var(--mauve); }
.share-terms .kind { color: var(--faint); font-size: 0.72rem; margin-left: 0.35rem; }

/* Every entry is here and one is shown, which is what lets a reader choose
   another without leaving the page. The reading page does that switch in
   javascript; this page has none, so it does it with :target -- a mark in the
   lyric is a link to #term-<slug>, and css shows whatever the browser has
   made the target.

   The first entry is the one open by default, and :has() is what lets another
   one replace it: without it there is no way to say "hide the default when
   something else is chosen" in css alone. This app already asks a browser for
   :has(), so that is not a new requirement. */
/* Two ways of deciding which entry is open, and only ever one of them at a
   time. share.js adds .is-live and takes over; until it does -- or forever, if
   javascript is off -- :target does the job and costs a jump to the anchor.

   Scoping them apart matters rather than being tidy: a :target rule can
   outrank a class this has just closed, so unscoped the fallback would reopen
   entries javascript had shut.

   The specificity inside the fallback is the whole of it, and getting it wrong
   hid every entry rather than switching between them. :has() takes the
   specificity of its argument, so a rule that hides while something is
   targeted outweighs the rule that shows the target. Said the other way round
   -- show the default only while nothing is targeted -- the two never
   compete. */
.share-term { display: none; }

.share-pane:not(.is-live):not(:has(.share-term:target)) .share-term.is-default { display: block; }
.share-pane:not(.is-live) .share-term:target { display: block; }

.share-pane.is-live .share-term.is-open { display: block; }

/* Which word the pane is showing, so the list is feedback and not just a set
   of links. The reading page's rows do the same. */
.share-terms li.is-current a { color: var(--fg); }
.share-terms li.is-current::marker { content: none; }

.share-term h3 {
  color: var(--mauve);
  font-size: 0.85rem;
  font-weight: 400;
  margin: 0;
}
/* The same mauve the marks in the lyric wear, so the word you saw and the word
   you are reading about are visibly the same thing. */
.share-tradition { color: var(--faint); font-size: 0.72rem; margin: 0.15rem 0 0.5rem; }
.share-description { color: var(--fg); line-height: 1.55; }
.share-description p { margin: 0 0 0.4rem; }
.share-undefined { color: var(--faint); }

/* The "?" convention, explained where it is being used. A page that shows a
   convention without explaining it is showing a stranger a typo. */
.share-note {
  color: var(--fg);
  font-size: 0.78rem;
  margin: 1.75rem 0 0;
  line-height: 1.6;
}
.share-foot {
  color: var(--faint);
  font-size: 0.72rem;
  margin-top: 2.5rem;
}
.share-foot p { margin: 0; }

/* --- glossary terms ------------------------------------------------------ */

/* A marker on the word. It must not change the letters -- no weight change, no
   transform, no substitution -- but colour is not a letter, and a 1px dotted
   underline under a four-character word at 13.5px was invisible in practice:
   present in the computed style, unreadable on the page. The word carries the
   colour now, and the underline stays as the second signal. */
/* `.term-link` and not `a.term-link`. The anchor qualifier was the rule's own
   scope leaking: `lyric_line(linked: false)` emits a <span class="term-link">,
   so the search hits, the term page and the concordance drew every glossary
   term in plain body grey while the same word beside the same lyric was
   marked. Three pages showing a mark the app says it always shows. Hover stays
   on the anchor, because a span is not something you can follow. */
.term-link {
  color: var(--mauve);
  text-decoration: none;
  border-bottom: 1px dotted var(--mauve);
}
a.term-link:hover {
  border-bottom-style: solid;
  background: color-mix(in srgb, var(--mauve) 13%, transparent);
  text-decoration: none;
}

/* --- uncertainty --------------------------------------------------------- */

/* A `?` is a note about what was heard, so it is marked the way an editor
   marks something to look at again: a wavy amber underline. The token keeps
   its own letters and its own `?`; nothing is corrected, and the marking is
   removable styling rather than an edit.

   Distinct from a glossary term on purpose -- dotted mauve is "this word means
   something", wavy amber is "this word may not be the word". Kept a cool hue
   against the amber so the two never read as the same kind of mark. */
.uncertain, .term-link.is-uncertain {
  text-decoration: underline wavy var(--amber);
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
}
/* The token carries the colour, for the same reason a glossary term does: a
   wavy underline alone reads as an artefact rather than a mark. */
.uncertain { color: var(--amber); }

/* Both at once: still linked as a term, still flagged as a guess. The word
   keeps the term colour and the wavy amber line; any ? sitting outside the
   link is its own span and goes amber by itself. */
.term-link.is-uncertain { border-bottom-color: var(--mauve); }

.uncertain:hover { background: color-mix(in srgb, var(--amber) 12%, transparent); }

/* --- hits: shaped like grep output --------------------------------------- */

.hits { margin-top: 0.75rem; }

.hit {
  padding: 0.55rem 0;
  border-bottom: 1px solid var(--rule);
}
.hit:last-child { border-bottom: 0; }

.hit-song {
  display: flex;
  gap: 0.6rem;
  align-items: baseline;
  font-size: 0.8rem;
  margin-bottom: 0.2rem;
}
.hit-song a { color: var(--cyan); }
.hit-song .count { color: var(--dim); }

.fuzzy { color: var(--amber); font-size: 0.75rem; }
.fuzzy::before { content: "~ "; }

.hit-line, .context-line {
  display: flex;
  gap: 0.9rem;
  align-items: baseline;
  padding: 0 0.5rem;
}
a.hit-line { color: var(--fg); }
a.hit-line:hover { background: var(--bg-hover); text-decoration: none; }

/* grep marks a match with ":" and a context line with "-". Same here. */
.hit-line .gutter { color: var(--amber); }
.context-line .gutter { color: var(--soft); }
/* EXPERIMENT: prose stays neutral. --dim is a label colour here, and these
   four are not labels -- a context line is a lyric, and a description and a
   note are sentences somebody reads. Drawn in the label colour, a surrounding
   line came out olive beside a white hit, which is the page colouring the
   corpus rather than its own furniture. */
.context-line .line-text { color: var(--fg); }

/* --- term pages ---------------------------------------------------------- */

.tradition-name { color: var(--green); font-size: 0.8rem; margin: 0.3rem 0 1rem; }
.tradition-name::before { content: "["; color: var(--faint); }
.tradition-name::after  { content: "]"; color: var(--faint); }

/* Acting on the page rather than moving around it, which is what --act-edit
   means everywhere else here -- the mkdir control wears it for the same
   reason. They sit under the entry rather than beside the heading: the
   heading is the term, and a control level with it would read as part of the
   name. */
/* The verbs that act on the thing being read, spelled the way you would type
   them. The glossary's; a card's went into its status bar beside the pencil,
   which is where controls that act on the named file belong. */

.description {
  border-left: 2px solid var(--rule);
  padding: 0.1rem 0 0.1rem 1rem;
  color: var(--fg);
  margin-bottom: 1.5rem;
}
.description p { margin: 0.3rem 0; }

section.tradition { margin-bottom: 1.75rem; }

/* The term page's card head, which is the one whose heading is the page's
   subject rather than a label for a section of it. `section.card > h1` is
   0.78rem and --comment, which is right for the book page's filename and
   wrong for the word this page is about -- so the band's shape is inherited
   and only the voice is put back.

   The band is also what puts the pencil and the x at the far right: they were
   already claiming `margin-left: auto`, but an auto margin needs a flex row
   to claim it in, and a plain h1 is not one -- so they sat against the end of
   the name. */
/* The band is a card head, so it is built by `section.card > h1` a thousand
   lines below; this puts back the one thing a card head gets wrong here. A
   card's heading is a label for a section of the page -- 0.78rem, --comment.
   This one is the word the page is *about*.

   `section.card.term-card`, and the two selectors it is not are both mistakes
   that were actually made and measured here:

     `.term-card > h1`          (0,1,1)  loses on specificity
     `section.term-card > h1`   (0,1,2)  *ties*, and loses on order -- one
                                         class, two elements, same as the rule
                                         it was meant to beat
     `section.card.term-card > h1`  (0,2,2)  wins

   Both failures were silent and looked fine: the term rendered at 12.48px in
   --comment, which is smaller and greyer than the "N lines in the corpus"
   heading beneath it -- the page's subject set quieter than a label for part
   of it. The cascade was read wrong twice, so the test measures the two
   against each other rather than trusting either reading. */
section.card.term-card > h1 {
  color: var(--fg);
  font-size: 1.05rem;
}
/* Scoped to the card's band, because `.term-name` is the glossary index's
   too -- the rows whose columns `glossary_head_test` measures against a head.
   A long name has to be allowed to break here or it pushes the controls off
   the right edge; over there it is a grid cell and sizes itself. */
.term-card > h1 .term-name { min-width: 0; overflow-wrap: anywhere; }
/* Whatever ends the card drops its bottom margin, which would otherwise land
   on top of the card's own padding as a band of nothing. Not named as the
   description, because a term with no meaning recorded ends on the tradition
   line instead and that carries 1rem of its own. Both hold those margins for
   the glossary index, where entries follow one another. */
.term-card > :last-child { margin-bottom: 0; }

/* --- command bar completion ---------------------------------------------- */

form.search { position: relative; }

.cmd-menu {
  position: absolute;
  top: calc(100% + 1px);
  left: -1px;
  right: -1px;
  margin: 0;
  padding: 0.2rem 0;
  list-style: none;
  background: var(--bg-raised);
  border: 1px solid var(--rule);
  max-height: 17rem;
  overflow-y: auto;
  z-index: 20;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.45);
}

.cmd-item {
  display: flex;
  align-items: baseline;
  gap: 0.75rem;
  padding: 0.15rem 0.6rem;
  cursor: pointer;
  border-left: 2px solid transparent;
}
.cmd-item:hover { background: var(--bg-hover); }
.cmd-item[aria-selected="true"] {
  background: var(--bg-hover);
  border-left-color: var(--green);
}
.cmd-item .kind { margin-left: auto; color: var(--faint); font-size: 0.75rem; }

/* --- command results: ambiguity and failure ------------------------------ */

.command-error {
  color: var(--amber);
  margin: 1rem 0 0.4rem;
}
.command-error::before { content: "! "; }

ul.choices { list-style: none; margin: 0.5rem 0 0; padding: 0; }
ul.choices li {
  display: flex;
  align-items: baseline;
  gap: 0.85rem;
  padding: 0.25rem 0.5rem;
  border-left: 2px solid transparent;
}
ul.choices li:hover { background: var(--bg-hover); border-left-color: var(--rule); }
ul.choices .kind { margin-left: auto; color: var(--faint); font-size: 0.75rem; }

dl.commands { margin: 0.75rem 0 0; }
dl.commands div { display: flex; gap: 1rem; align-items: baseline; padding: 0.15rem 0; }
dl.commands dt { color: var(--green); min-width: 11rem; flex: none; }
dl.commands dd { margin: 0; color: var(--dim); }
kbd {
  font: inherit;
  font-size: 0.78rem;
  color: var(--fg);
  border: 1px solid var(--rule);
  border-bottom-width: 2px;
  border-radius: 3px;
  padding: 0 0.3rem;
  background: var(--bg-raised);
}

/* --- ls output ----------------------------------------------------------- */

ul.listing { list-style: none; margin: 1rem 0 0; padding: 0; }
ul.listing li {
  display: flex;
  align-items: baseline;
  gap: 1rem;
  padding: 0.2rem 0.5rem;
  border-left: 2px solid transparent;
}
ul.listing li:hover { background: var(--bg-hover); border-left-color: var(--rule); }
/* The count is the fact this row is reporting and the noun is its label, so
   they are not the same colour. This is `ls -l`'s rule -- a count wears the
   colour of what it counts -- reaching the short listing, which had no way to
   say which half of "8 albums" was the count and drew both at the quietest
   level in the app. The home page is three rows of it. */
ul.listing .meta { margin-left: auto; color: var(--faint); font-size: 0.78rem; }
ul.listing .meta .n { color: var(--fg); }

/* ls -l: counts first, name last, the way ls prints it */
/* ls packs its columns close together; the name is what the eye is looking
   for, so it should not be pushed to the far side of the page. */
/* A row is a grid rather than a flex line, so the columns actually line up.
   Under flex each row sized itself, and a cell wider than its min-width shoved
   everything after it sideways -- it read as a table right up until you looked
   at one. Fixed tracks mean every row is measured identically.

   Not a <table>: an album on the home page is a <details> that opens to its
   songs, and a <summary> cannot be a table row. Not `display: contents` on the
   li either -- that takes the row's box away, and with it the hover and the
   left border that lights up on it.

   The grid applies only where the listing has said how many columns it carries.
   The concordance and the dump page build `listing is-long` by hand with their
   own shapes, and they keep the flex row they have always had. */
ul.listing.is-long li { gap: 0.75rem; }

ul.listing.is-long[class*="cols-"] li {
  display: grid;
  gap: 0 0.75rem;
  align-items: baseline;
}
ul.listing.is-long.cols-1 li,
.listing-head.cols-1 { grid-template-columns: 4.75rem minmax(0, 1fr); }
ul.listing.is-long.cols-2 li,
.listing-head.cols-2 { grid-template-columns: repeat(2, 4.75rem) minmax(0, 1fr); }
ul.listing.is-long.cols-3 li,
.listing-head.cols-3 { grid-template-columns: repeat(3, 4.75rem) minmax(0, 1fr); }
/* a song: lines, ?, terms, source, duration -- the last two are metadata and
   need only as much room as "sp" and "3:38" */
ul.listing.is-long.cols-4 li,
.listing-head.cols-4 { grid-template-columns: repeat(4, 4.75rem) minmax(0, 1fr); }
ul.listing.is-long.cols-5 li,
.listing-head.cols-5 { grid-template-columns: 5.25rem 3.25rem 4.75rem 4.5rem 3rem minmax(0, 1fr); }
/* an album: the same six columns the tree draws, on the same widths */
ul.listing.is-long.cols-6.is-albums li,
.listing-head.cols-6.is-albums { grid-template-columns: var(--album-cols) minmax(0, 1fr); }

/* On a phone a listing is the name and nothing else.
   `ls -l`'s columns are a scanning aid, and scanning five of them is not what
   a phone is for: at 390px a song's five leave about fifty pixels for the one
   thing you came to read. The head goes with them -- it is `aria-hidden`
   already and labels columns that are no longer there.

   **Nothing is lost, which is why they are hidden rather than folded into a
   second line.** Every count is on the row's own page, one press away and
   where the press was going anyway. A meta line under each name would keep the
   figures at the cost of the compactness that was the complaint. */
@media (max-width: 40rem) {
  /* Every rule here is scoped to a `cols-` listing, which is the one the
     shared partial builds. The concordance and the dump page build
     `listing is-long` by hand with shapes of their own -- reaching into those
     hid columns they need and put a 4.75rem track on cells that are not
     counts, which ran the export page 10px off the side of a phone. */
  ul.listing.is-long[class*="cols-"] li { grid-template-columns: minmax(0, 1fr); }
  /* **One figure stays, not none.** Hiding all four left a column of white
     titles with nothing to break it up, which is its own kind of unreadable --
     a row needs something the eye can land on besides the words.

     The first one, whichever the listing puts there: `pp` for a book, which
     every book has, and `lines` for a card. Chosen that way rather than by
     naming a class, so a listing with different columns still keeps its
     primary figure. Every row after the first `.col` goes. */
  ul.listing.is-long[class*="cols-"] .col ~ .col { display: none; }

  /* **An expanded album is the filenames alone**, which is the one listing
     where the kept figure is worth less than the width it costs. A song row's
     first column is `lines`, and inside the tree that row is already indented
     under a summary saying `6 songs 154 lines` for the album -- so the figure
     beside each name is a count of the same thing one level down, on the one
     listing whose rows are indented in from the page edge to begin with. At
     390px it
     took `01-jah-will-be-done` down to `01-jah-will...`, which is a filename
     that has stopped naming a file.

     Everywhere else the figure stays: the shelf's `pp`, the tarot's `lines`
     and the album page's are the only thing breaking up a column of white
     titles, and none of those rows is indented under a row already saying it.
     Above 40rem every column is drawn here as it always was. */
  .tree-album ul.listing.is-long[class*="cols-"] .col { display: none; }

  /* A fixed track for it, so every title starts at the same place. Left to
     size itself, "131 pp" and "1,044 pp" are different widths and the titles
     came out on a ragged edge -- which undoes most of what keeping the figure
     was for. The same 4.75rem the grid gives it with room. */
  ul.listing.is-long[class*="cols-"] .col { flex: none; width: 4.75rem; }

  .listing-head { display: none; }

  /* And the name takes the width they were holding. `flex: 1` rather than a
     grid property because these rows are laid out both ways depending on the
     listing, and it is inert in a grid -- where the item already stretches --
     while doing the work in a flex row. Without it the pencil sits against the
     end of the filename instead of at the row's right edge, which is what
     makes it an action column rather than something trailing the text. */
  ul.listing.is-long[class*="cols-"] li > .row-name { flex: 1; }

  /* One line each, with the rest elided.
     The wrapping itself was fine -- it breaks at the hyphens a slug is full of
     -- but a three-line entry beside a one-line entry, with the same gap
     between wrapped lines as between rows, gives nothing to tell one book from
     the next. Uniform rows cannot blur. Of 66 books the median name is 29
     characters and 15 are long enough to wrap, so this changes a minority of
     rows and makes the whole list scannable.

     Nothing is lost: the full name is the page the row opens, which is where
     the press was going, and the first thirty characters identify a book.

     Both shapes of row -- a name with a pencil beside it, and a bare link
     where there is nothing to edit. `min-width: 0` because an ellipsis needs
     the box to be allowed to be narrower than its text. */
  ul.listing.is-long[class*="cols-"] li > .row-name > a:first-child,
  ul.listing.is-long[class*="cols-"] li > a {
    min-width: 0;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }
}

/* What a counted column looks like, said once and at the lowest specificity
   that will do. It used to be said inside `ul.listing.is-long .col`, which
   outranks `.col.is-uncertain` -- so a rule saying "the ? column is amber" lost
   to the rule saying "a column is grey", and every column stayed grey. */
.col { color: var(--faint); font-size: 0.78rem; }

ul.listing.is-long .col {
  text-align: right;
  white-space: nowrap;
}

/* Each count wears the colour of the thing it counts, which is the colour that
   thing already wears in a lyric. Nothing is invented for the sake of it:
   green means where you are, cyan is a link, and both are spoken for. */
.col.is-lines,
.col.is-songs { color: var(--dim); }
.col.is-uncertain { color: var(--amber); }
.col.is-terms { color: var(--mauve); }
/* A recording is a hand-made assertion that this transcription is of that
   track, and it is the one column here counting something outside the corpus
   that is nonetheless here. --sage is the colour of a count of what exists; the
   zero rule below still quiets an album nothing is linked in. */
.col.is-linked { color: var(--sage); }

/* Metadata sits behind the transcription rather than beside it: what a song is,
   in front; what it was transcribed from, quietly after. */
.col.is-source,
.col.is-duration,
.col.is-released { color: var(--faint); }

/* A count of nothing says nothing. Colouring a zero would be colour asserting
   something that is not there, and a page of quiet zeros is what makes the one
   row with three ? in it visible. */
.col.is-none { color: var(--faint); }
/* The direct child only. This used to reach every link in a row, and a row has
   two now -- so it beat `.row-edit { margin-left: auto }` on specificity and the
   e sat against the filename instead of at the end of the line. */
ul.listing.is-long li > a,
ul.listing.is-long li > .row-name { margin-left: 0.5rem; }
ul.listing .orphan { color: var(--amber); }


/* The key that opens the bar, drawn in it. It swaps to `esc` once the bar has
   the cursor, so the hint is always the key that does something next.

   It used to be decoration on a bar that already had the cursor from page
   load. Now it is the only thing on screen saying how to reach it, which is
   why it is drawn at rest rather than on hover. */
.search .hint {
  color: var(--faint);
  font-size: 0.72rem;
  border: 1px solid var(--rule);
  border-radius: 2px;
  padding: 0 0.25rem;
  user-select: none;
  flex: none;
}
.search.is-focused { border-color: var(--green); }
.search.is-focused .prompt { color: var(--green); }
.search.is-focused .hint { color: var(--dim); border-color: var(--edge); }

/* --- the palette -------------------------------------------------------- */

body.palette-open { overflow: hidden; }

.palette {
  position: fixed;
  inset: 0;
  z-index: 50;
  display: flex;
  justify-content: center;
  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);
  box-shadow: 0 18px 50px rgba(0, 0, 0, 0.55);
}

.palette-prompt {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.85rem 1rem;
}
.palette-prompt .prompt { color: var(--green); font-size: 1.05rem; }
.palette-prompt input {
  font: inherit;
  font-size: 1.05rem;
  flex: 1;
  color: var(--fg);
  background: transparent;
  border: 0;
  outline: none;
  padding: 0;
}

/* In the palette the candidates are the surface, not a dropdown over it. */
.cmd-menu.is-inline {
  position: static;
  border: 0;
  border-top: 1px solid var(--rule);
  box-shadow: none;
  max-height: 46vh;
  padding: 0.25rem 0;
}
.cmd-menu.is-inline .cmd-item { padding: 0.25rem 1rem; }

/* --- vertical split ------------------------------------------------------ */

/* A song with the glossary open needs more room than a column of lyric does. */
main.is-wide { max-width: 76rem; }
/* the editor shares its width with the reference pane, and the longest line in
   the corpus is 95 characters */
main.is-widest { max-width: 104rem; }
.editor-split { align-items: start; }
.editor-split .pane-right { top: var(--sticky-top); }

.split {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 24rem);
  margin-top: 1rem;
}

/* One window either side of the lyric. Stacking them wasted the column when
   the counts were lopsided, and halving it wasted it when they were not, so
   each gets its own full-height column and the lyric keeps the middle.
   The lyric stays in the middle column while remaining first in the document,
   which is the order it should be read in. */
.split.is-flanked {
  /* 20rem rather than 18. Measured on chad's laptop: an 18rem window holding a
     suggestion's reason wrapped it to three or four words a line, which reads
     as a column of fragments rather than a sentence. The lyric can spare it --
     the longest line in the corpus is 95 characters and the middle column had
     room to spare at this page's 104rem. */
  grid-template-columns: minmax(0, 20rem) minmax(0, 1fr) minmax(0, 20rem);
  gap: 0 1.25rem;
}
.split.is-flanked > .pane-left { grid-column: 2; grid-row: 1; }
.split.is-flanked > .is-glossary { grid-column: 1; grid-row: 1; }
.split.is-flanked > .is-uncertain { grid-column: 3; grid-row: 1; }
.split.is-flanked > .pane-left { padding-right: 0; }
.split.is-flanked > .is-glossary { border-left: 0; border-right: 1px solid var(--rule); padding-left: 0; padding-right: 1.25rem; }

/* Below three columns' worth of room they stack: lyric, then glossary, then
   uncertain, which is the document order anyway. The threshold moved with the
   columns -- two 20rem windows leave 40rem of lyric at 82rem, which is the
   95-character line with nothing to spare. */
@media (max-width: 82rem) {
  .split.is-flanked { grid-template-columns: minmax(0, 1fr); }
  .split.is-flanked > .pane-left,
  .split.is-flanked > .is-glossary,
  .split.is-flanked > .is-uncertain { grid-column: 1; grid-row: auto; }
  .split.is-flanked > .is-glossary {
    border-right: 0;
    border-top: 1px solid var(--rule);
    padding-right: 0;
    padding-top: 1rem;
  }
}

/* A card has two windows, not four: what it is, and what its words mean. The
   glossary keeps the left column it has beside a lyric, so the two pages read
   the same way round -- and the card stays first in the document, which is the
   order it should be read in and the order it stacks in. */
.split.is-paired {
  grid-template-columns: minmax(0, 18rem) minmax(0, 1fr);
  gap: 0 1.25rem;
}
.split.is-paired > .pane-left { grid-column: 2; grid-row: 1; padding-right: 0; }
.split.is-paired > .is-glossary {
  grid-column: 1;
  grid-row: 1;
  border-left: 0;
  border-right: 1px solid var(--rule);
  padding-left: 0;
  padding-right: 1.25rem;
}

@media (max-width: 78rem) {
  .split.is-paired { grid-template-columns: minmax(0, 1fr); }
  .split.is-paired > .pane-left,
  .split.is-paired > .is-glossary { grid-column: 1; grid-row: auto; }
  .split.is-paired > .is-glossary {
    border-right: 0;
    border-top: 1px solid var(--rule);
    padding-right: 0;
    padding-top: 1rem;
  }
}

/* The picture of the card. Capped rather than full-width: it is a photograph of
   a physical thing about three inches tall, and blown across a desktop column
   it reads as a poster rather than as the card being written about. */
/* The picture and what is written about it, side by side.
   `:has()` asks whether there is a picture rather than being told: a card with
   none is one column, and one column is what a grid with nothing in its first
   track would be anyway -- but saying so keeps the gap from being spent on
   nothing. */
.card-body { display: grid; gap: 0 1.75rem; align-items: start; }
.card-body:has(.card-figure) { grid-template-columns: auto minmax(0, 1fr); }

/* It stays while the writing scrolls past it, which is the whole point of
   putting them beside each other -- a card you have scrolled away from is a
   card you are no longer reading with. */
.card-figure {
  margin: 1rem 0 1.25rem;
  position: sticky;
  top: var(--sticky-top);
}

/* Prose, not a lyric, and prose at 104rem is unreadable however wide the room
   is. The same 44rem the player is capped at on the song page. */
.card-body .lyric.is-prose { max-width: 44rem; }

/* Below this the two do not fit beside each other and the picture wins, because
   `auto` is its natural width and the text gets whatever is left -- measured at
   390px that was nineteen pixels of prose and fifty-six of the page hanging off
   the side. A picture and a column of text need about 620px between them, so
   under 48rem they stack, which is what a phone wants anyway.

   The figure stops sticking when it stacks: pinned above the text it would
   follow you down the page covering the thing you are reading. */
@media (max-width: 48rem) {
  .card-body:has(.card-figure) { grid-template-columns: minmax(0, 1fr); }
  .card-figure { position: static; }
}
.card-image {
  display: block;
  max-width: 18rem;
  width: 100%;
  height: auto;
  border: 1px solid var(--rule);
}

/* What is known about a picture that is not here yet.

   `.note`'s voice, because that is what it is -- a comment about the page
   rather than part of it. It claims no grid column: `.card-body:has(.card-figure)`
   asks whether there is a *picture*, and a sentence explaining that there is
   not should not hold open the space one would have taken. */
.picture-state { display: flex; align-items: baseline; gap: 0.5rem; }
/* Amber, which is what uncertainty already wears here: nothing is broken, and
   commons has said it will not vouch for a file. */
.picture-state .is-refused { color: var(--amber); }
/* Acting on the page rather than moving around it -- the share button's colour
   for the share button's reason. */
.picture-form { display: inline; }
.picture-ask { font-size: 0.78rem; color: var(--act-edit); border-color: var(--edge); }
.picture-ask:hover { background: var(--bg-hover); color: var(--fg); }

/* A card's text is prose, not sung lines: it wraps, and a wrapped line should
   put its number beside the first row rather than the last. */
.lyric.is-prose .line { align-items: start; }

/* The strip exists at every width and is shown at one. Which of the four
   buffers is on screen is a question only a narrow window asks -- on a wide one
   three of them are on screen at once and a tab would be a control for
   nothing. */
.song-tabs { display: none; }

@media (max-width: 60rem) {
  .song-tabs {
    display: flex;
    gap: 0;
    position: sticky;
    /* under the header, not under the top of the screen: this page has one */
    top: var(--sticky-top);
    z-index: 4;
    margin: 0.75rem 0 0;
    /* Opaque, or the lyric scrolls through it. */
    background: var(--bg);
    border-bottom: 1px solid var(--rule);
  }
  .song-tab {
    flex: 1;
    font: inherit;
    font-size: 0.8rem;
    color: var(--dim);
    background: none;
    border: none;
    border-bottom: 2px solid transparent;
    border-radius: 0;
    /* a tap target rather than a click target: 44px is what a thumb wants */
    min-height: 2.75rem;
    /* Narrower than it looks like it needs to be, and nowrap, because these
       are flex items that stretch: one label folding to a second line takes
       all four down with it and the strip goes from 45px to 66px. The editor
       is where that bites -- it is the taller page, so it has a scrollbar at
       320px and about fifteen fewer pixels to lay four tabs out in. */
    padding: 0.6rem 0.25rem;
    white-space: nowrap;
  }
  .song-tab .count { color: var(--dim); }
  .song-tab.is-current { color: var(--fg); border-bottom-color: var(--green); }

  /* Only once javascript has said so. Without it the strip stays hidden and all
     four buffers stay in the page, which is the long scroll this replaces --
     worse, and nothing is unreachable. */
  .split.is-tabbed [data-song-panel] { display: none; }
  .split.is-tabbed [data-song-panel].is-showing { display: block; }

  /* Everything pinned inside a panel has to clear the strip as well as the
     header -- the lyric's head bar and the right-hand window both stick. Said
     once, here, rather than by each of them adding the same number. */
  .split.is-tabbed { --sticky-top: calc(var(--header-h) + 2.75rem); }

  /* The window's own tab strip is the outer one's job now. Two strips choosing
     between overlapping sets of buffers is a line about a line. */
  .split.is-tabbed .pane-head.is-tabbed { display: none; }

  /* **Except the book page's**, and the exception is the rule read properly.
     On the reading page the two strips overlap -- uncertainties and links are
     on both -- which is what makes the inner one a line about a line. Here
     they overlap in nothing: the outer chooses notes, cover or glossary, and
     this chooses which of the four buffers the notes column shows. Hidden, it
     left three of the four unreachable on a phone, which is what it did until
     now.

     Told apart by where it sits: this strip is a direct child of the left
     column and the reading page's is inside the right-hand window. It wraps,
     because four labels are wider than 390px and a second row is better than
     a row that runs off the side. */
  .split.is-tabbed .pane-left > .pane-head.is-tabbed { display: flex; }

  /* A border between columns, with one column. */
  .split.is-tabbed > .pane-right {
    border-left: 0;
    border-top: 0;
    padding-left: 0;
    padding-top: 0;
  }
}

.pane { min-width: 0; }
.pane-left { padding-right: 1.5rem; }

/* The right window keeps its place while the lyric scrolls under it, which is
   how two vim windows behave: they scroll independently. */
.pane-right {
  border-left: 1px solid var(--rule);
  padding-left: 1.5rem;
  position: sticky;
  top: var(--sticky-top);
  align-self: start;
  max-height: calc(100vh - var(--sticky-top) - 1rem);
  /* Two windows, stacked. The lyric keeps the full height beside them; this is
     a :split inside the :vsplit. */
  display: flex;
  flex-direction: column;
  min-height: 0;
}

/* Sized by what is in them, not split down the middle. A song with five terms
   and nothing uncertain gave half the column to the word "nothing". Each window
   takes the height its contents need, shrinks when there is not enough room to
   go round, and never collapses below its own status line. */
.pane-window {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-height: 0;
}

/* each window scrolls its own contents, so one long list cannot push the other
   off the screen */
.pane-window > .pane-body { overflow-y: auto; flex: 1 1 auto; min-height: 0; }
.pane-window > .pane-status { margin-top: 0; }
.pane-window .note:first-child { margin-top: 0; }
/* The same on all three columns, so the lyric's bar, the recording above the
   glossary and the window opposite all start on one line. The lyric had none
   and began ten pixels higher than the things either side of it. */
.pane-right,
.pane-left { padding-top: 0.6rem; }

/* vim's statusline: one line per window, naming what is in it. */
.pane-status {
  position: sticky;
  bottom: 0;
  margin-top: 1rem;
  padding: 0.2rem 0.6rem;
  background: var(--rule);
  color: var(--fg);
  font-size: 0.75rem;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.pane-left .pane-status { margin-right: 0; }

/* The same band, at the top of the window rather than along its foot -- a vim
   winbar. The lyric frame had no top edge at all and the name of the file you
   were reading was the last thing you reached; this gives it both. Only the
   lyric's moves: the reference windows either side keep a head of their own and
   a status line naming what is selected in them, which is a different job. */
.pane-status.is-head {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  position: sticky;
  top: var(--sticky-top);
  z-index: 1;
  margin-top: 0;
  margin-bottom: 0.75rem;
  padding: 0.15rem 0.5rem;
}

/* The bar is the page's heading, so it has to stop looking like one: no prompt,
   no weight, no margin of its own. Every other h1 here is a command and wears a
   green $; this one is a filename, and a $ in front of it would be claiming it
   was run. */
h1.pane-status {
  font-size: 0.75rem;
  font-weight: 400;
  color: var(--fg);
  margin: 0 0 0.75rem;
}
h1.pane-status::before { content: none; }

/* At the far end of the bar. Sized to the band so the bar is no taller for
   holding it. */
.status-edit {
  margin-left: auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  padding: 0 0.2rem;
  border-radius: 2px;
  color: var(--act-edit);
}
.status-edit:hover { background: var(--bg-hover); text-decoration: none; }
.status-edit:focus-visible {
  outline: 2px solid var(--act-edit);
  outline-offset: 1px;
  text-decoration: none;
}
.status-edit svg { display: block; }

/* The x beside the pencil. Everything the pencil is, minus the auto margin --
   that one is claimed by whichever control comes first, and claiming it twice
   would push them apart rather than sit them together.
   Faint at rest and brighter on hover, which is what a removing control already
   does on a linked recording: a destructive thing should be the quietest mark
   in the band, not the loudest. */
.status-remove {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  padding: 0 0.2rem;
  border-radius: 2px;
  color: var(--faint);
}
.status-remove:hover { color: var(--fg); background: var(--bg-hover); text-decoration: none; }
.status-remove:focus-visible {
  outline: 2px solid var(--act-edit);
  outline-offset: 1px;
  text-decoration: none;
}
.status-remove svg { display: block; }

/* The name gives way rather than pushing the bar wider than the lyric. */
.status-path {
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

/* Which window is which. The status line at the foot names the selection; this
   names the window, and stays put while the pane scrolls -- with one either
   side of the lyric, a label only at the bottom is a label you have to go
   looking for. */
.pane-head {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  position: sticky;
  top: 0;
  z-index: 1;
  margin: 0 0 0.5rem;
  padding: 0.3rem 0.5rem;
  background: var(--bg-raised);
  border-bottom: 1px solid var(--rule);
}
.pane-name { font-size: 0.78rem; font-weight: 600; }
/* the same colours the marks take in a lyric, so the label and the thing it
   describes agree */
.is-glossary .pane-name { color: var(--mauve); }
.is-uncertain .pane-name { color: var(--amber); }
.pane-head .count { margin-left: auto; color: var(--dim); font-size: 0.75rem; }

/* The add control, in the head bar rather than at the foot of the column it
   used to hang under. `.pane-head` is sticky, so up here it is the one part
   of the window always on screen -- down there it was below the fold on any
   song with a few terms.

   Only the count claims `margin-left: auto`; claiming it twice pushes the two
   apart instead of sitting them together, which is the same note the card's
   head bar carries about its pencil and its x.

   Warm, for the reason the tree's plus is warm: `--act-edit` acts on the page
   and everything else in this bar is `--dim` or `--faint`, which move around
   it. Written `.pane-head a.pane-make` because `.pane-terms a` and friends are
   two-part selectors that would otherwise win, which is exactly how the tree's
   plus spent its life the wrong colour. */
.pane-head a.pane-make {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.25rem;
  height: 1.25rem;
  line-height: 1;
  font-size: 0.95rem;
  color: var(--act-edit);
  border: 1px solid transparent;
  border-radius: 2px;
}
.pane-head a.pane-make:hover {
  color: var(--fg);
  text-decoration: none;
  border-color: var(--act-edit);
  background: var(--bg-hover);
}
.pane-head a.pane-make:focus-visible {
  outline: 2px solid var(--act-edit);
  outline-offset: 1px;
}
.pane-head .kind { margin-left: auto; color: var(--faint); font-size: 0.72rem; }

/* Nothing to show is a result, so it is printed like one. */
.pane-echo {
  color: var(--comment);
  margin: 0.9rem 0 0;
  padding-left: 0.4rem;
}
.pane-echo::before { content: "$ "; color: var(--faint); }

/* every term in this song, the one being read marked */
ul.pane-terms { list-style: none; margin: 0.5rem 0 1rem; padding: 0; }
ul.pane-terms li {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  padding: 0.15rem 0.4rem;
  border-left: 2px solid transparent;
}
ul.pane-terms li:hover { background: var(--bg-hover); }
ul.pane-terms li.is-current {
  border-left-color: var(--mauve);
  background: var(--bg-hover);
}
ul.pane-terms a { color: var(--mauve); }
ul.pane-terms .kind { margin-left: auto; color: var(--faint); font-size: 0.72rem; }

.pane-term h2 { margin: 0.5rem 0 0.1rem; font-size: 0.95rem; color: var(--fg); }
.pane-term h3 {
  margin: 1.1rem 0 0.3rem;
  font-size: 0.75rem;
  color: var(--dim);
  font-weight: 400;
}

/* Where the term appears. Beside the lyric each row jumps to its line; beside
   the editor there is nowhere to jump to -- a textarea has no anchors -- so
   the row is a span and says where the word is without offering to go there.
   Both are laid out by the same declaration, or the two panes would part
   company over a hover. */
ul.pane-lines { list-style: none; margin: 0; padding: 0; }
ul.pane-lines a,
ul.pane-lines .pane-line {
  display: flex;
  gap: 0.6rem;
  align-items: baseline;
  color: var(--fg);
  padding: 0.1rem 0.4rem;
}
ul.pane-lines a:hover { background: var(--bg-hover); text-decoration: none; }
ul.pane-lines .gutter { color: var(--soft); }

/* Narrow screens have no room for two windows: the glossary goes underneath,
   which is what :split rather than :vsplit would have done anyway. */
@media (max-width: 62rem) {
  .split { grid-template-columns: minmax(0, 1fr); }
  .pane-left { padding-right: 0; }
  .pane-right {
    position: static;
    max-height: none;
    border-left: 0;
    border-top: 1px solid var(--rule);
    padding-left: 0;
    padding-top: 1rem;
    margin-top: 1.5rem;
  }
}

/* --- the marks ----------------------------------------------------------- */

/* Four drawings from digital pilgrim's mark bank: the cross on the home page,
   and one per directory under it -- the christogram on /transcriptions, the
   wheel on /tarot, the bible on the glossary.

   **One rule colours all four**, which is the whole reason they are inlined
   through `shared/_bank_mark` rather than being four images. Every drawing in
   that bank uses the same five literal hexes, so the same five classes carry
   the same five rungs in each of them. Adding a fifth mark is a file of paths
   and nothing here.

   The hexes are dropped and the ramp rebuilt from tokens, because this app
   never writes a colour by hand -- and because the ramp they came with is a
   *different* green (#7ee787 against --green's #a8ff60), which would have
   read as the header very slightly missing rather than as the same colour.

   Two of the three greens are `color-mix()` down toward the ground, which is
   how every tint here is already built, so the ramp is derived from the token
   and cannot drift away from it. The aura is genuinely dim by design -- it is
   a halo, and the subject has to come forward out of it. */
.mark-full .m-lit   { fill: var(--green); }
.mark-full .m-mid   { fill: color-mix(in srgb, var(--green) 52%, var(--bg)); }
.mark-full .m-deep  { fill: color-mix(in srgb, var(--green) 28%, var(--bg)); }
.mark-full .m-warm  { fill: var(--amber); }
.mark-full .m-ember { fill: color-mix(in srgb, var(--amber) 45%, var(--bg)); }

/* Pixel art. Smoothing it is the one thing that destroys it, and a browser
   will smooth an upscaled svg raster by default. */
.mark-full { display: block; image-rendering: pixelated; }

.mark-plate { margin: 0.5rem 0 -0.25rem; }

/* The way out of congo, on the home page only: an arrow and the name, where
   every other page has the tilde alone. Set apart from the listing above it by
   space and a rule, because it is about where this place sits rather than
   about anything in it. */
.homeward {
  margin: 2rem 0 1.5rem;
  padding-top: 0.9rem;
  border-top: 1px solid var(--rule);
  /* The size a note has, without being one: it is a credit with the way back
     inside it, and `.note::before` would draw a `#` in front of it -- a
     commented-out credit. */
  font-size: 0.8rem;
}

/* **The mark beside the heading, not above it**, on the four directory pages.

   Stacked, a 128px drawing plus its margins put roughly 250px between the top
   of the page and the first row of the listing -- and the listing is what
   every one of these pages is for. Beside it the head is the height of the
   mark alone, about 135px, because the crumb line, the heading and the note
   together come to less than the drawing is tall.

   So the mark is the far-left of a head block and the words sit to its right,
   which is the arrangement chad asked for. Nothing is smaller and nothing is
   hidden: the same drawing at the same 128px, and the same three lines of
   text. They are simply no longer stacked on each other.

   **The home page keeps the stacked arrangement**, and is the only one that
   does. Its mark is the app's own rather than a page's, the drawing sits over
   the name of the whole corpus, and the listing under it is three rows -- so
   nothing is being pushed down that matters. Stacked it reads as a masthead;
   the four below read as a place with an emblem, which is what they are. */
.page-head {
  display: flex;
  align-items: center;
  gap: 1.25rem;
  margin-bottom: 1.5rem;
}

/* The mark is a figure, so it never shrinks and never stretches. */
.page-head .mark-plate { margin: 0; flex: none; }

/* min-width:0 so a long heading wraps inside the column rather than pushing
   the row wider than the page -- the flex default is min-content, which for a
   monospace path with no spaces in it is the whole string. */
.page-head-text { min-width: 0; }

/* The three stacked margins are what the block's own spacing replaces: the
   crumb's top margin and the heading's would otherwise push the text off
   centre against the mark, and the note's bottom margin would sit inside the
   row rather than under it. */
.page-head-text > .crumbs { margin-top: 0; }
.page-head-text > h1 { margin-top: 0; }
.page-head-text > .note { margin-bottom: 0; }

/* Below 30rem it is a quarter of a phone's height above the listing, and the
   listing is what every one of these pages is for. 96px is under the 128px
   the halftone is measured to need, which is the trade: on a phone a slightly
   muddier mark beats a mark that has pushed the content off the screen. */
@media (max-width: 30rem) {
  .mark-full { width: 96px; height: 96px; }
}

/* --- the album tree ------------------------------------------------------ */

.tree-controls {
  display: flex;
  align-items: center;
  margin: 0.75rem 0 0.5rem;
  font-size: 0.8rem;
}
.tree-controls a { color: var(--dim); }
.tree-controls a:hover { color: var(--cyan); text-decoration: none; }
.tree-controls .sep { color: var(--faint); margin: 0 0.4rem; }

/* The two tools that read this directory -- uncertainties and words -- which
   were in the top bar until the bar became the corpus's directories.
   **--cyan, because they are links to somewhere else**, which is the one
   thing that colour means here, and the flags beside them are not: `-l` and
   `-lt` act on the listing below. A hairline says the same thing structurally,
   so the difference survives being glanced at rather than resting on a hue.
   Wrapping allowed on the row, because two more words is what finally made
   this line too long for a phone. */
.tree-controls { flex-wrap: wrap; row-gap: 0.35rem; }
.section-tools {
  margin-right: 1rem;
  padding-right: 1rem;
  border-right: 1px solid var(--rule);
}
.tree-controls .section-tools a { color: var(--cyan); }
.tree-controls .section-tools a:hover { color: var(--fg); }

/* One control that flips, rather than two links saying the two things it could
   do. Both glyphs ship inside the anchor and css picks one -- nothing builds
   markup in javascript.

   Scoped to the control and never to .tree-controls: that class is doing double
   duty as the disclosure bar here and as the sort and filter bar on the
   uncertainties and concordance pages, which have no toggle at all. */
.tree-toggle {
  display: inline-flex;
  align-items: center;
  /* pushed to the far end, so a row holding only the toggle still lands it
     where a row holding flags as well would */
  margin-left: auto;
  color: var(--faint);
  padding: 0.3rem;
  border-radius: 2px;
}
/* Pushed to the right end with the toggle, and given the margin the row has no
   gap to supply. --act-edit because making a directory is acting on the page
   rather than moving around it, which is what the sort flags beside it do.
   Scoped to its own class, never to .tree-controls: that class is the sort bar
   on three other pages that have nothing to make. */
/* **A `+` now, not a word**, which needs three things a word did not.
   A target: one character at 0.8rem is a four-pixel-wide thing to hit, so the
   box is sized rather than the glyph. A line-height of 1 and a fixed size, or
   the plus sits low in its own box. And a real focus ring, because on an
   icon-only control a colour change is not a signal that the keyboard is on
   the thing that makes something -- the same reasoning the recording's unlink
   carries.

   `--act-edit` is what tells it from the `+` every shut row below it draws:
   those are `--dim` and `--faint`. Warm acts on the page, cool moves around
   it, which is the palette's rule rather than a choice made here.

   **It has to be written `.tree-controls a.tree-make` or it does not apply.**
   `.tree-controls a` two hundred lines up is (0,1,1) and `.tree-make` is
   (0,1,0), so the token sat in this rule and lost every time -- the control
   has been --dim since it was written, which nothing noticed while it was a
   word. It stops being invisible the moment the word becomes a `+`: the same
   glyph in the same colour as the markers below it is a fourth disclosure
   marker, which is exactly what the album index's old comment feared. The
   test that found this measures the two against each other rather than
   naming either. */
.tree-controls a.tree-make {
  margin-left: auto;
  margin-right: 0.6rem;
  color: var(--act-edit);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.4rem;
  height: 1.4rem;
  line-height: 1;
  font-size: 1rem;
  border: 1px solid transparent;
  border-radius: 2px;
}
.tree-controls a.tree-make:hover {
  color: var(--fg);
  text-decoration: none;
  border-color: var(--act-edit);
  background: var(--bg-hover);
}
.tree-make:focus-visible { outline: 2px solid var(--act-edit); outline-offset: 1px; }
/* the toggle keeps the far end; with mkdir beside it, only one of them can
   claim the auto margin */
.tree-make ~ .tree-toggle { margin-left: 0; }
.tree-controls a.tree-make:focus-visible { outline: 2px solid var(--act-edit); outline-offset: 1px; }

.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; }

/* The glossary filters, on a row of their own. Eight names beside the flags and
   the toggle is a line nobody reads. It wraps rather than scrolls: the names are
   the control, and a name half off the edge is not one. */
.tree-controls.is-filters {
  flex-wrap: wrap;
  margin-top: 0;
  row-gap: 0.2rem;
}
.filter-label { color: var(--faint); margin-right: 0.6rem; }
/* --mauve is what a glossary term wears everywhere else in this app, and these
   are glossary categories -- so the row says what it belongs to before it is
   read. --green stays for the one in force, the way every other filter in the
   app marks its current choice. */
.tree-controls.is-filters a { color: var(--mauve); }
.tree-controls.is-filters a:hover { color: var(--fg); }
.tree-controls.is-filters a.is-current { color: var(--green); }

/* A band of the help page: the four parts the cards are grouped into.

   The label is drawn rather than headed. Every topic on that page is an h2 and
   the search page renders the same partials flat, so a band heading would have
   to sit at the same level as the things it groups or move them on one page and
   not the other. The <section> carries an aria-label instead, and this draws
   the word with a rule running off it the way a legend does. */
.tree-band + .tree-band { margin-top: 1.2rem; }
.band {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  margin: 0 0 0.5rem;
  color: var(--comment);
  font-size: 0.75rem;
}
.band::after {
  content: "";
  flex: 1;
  border-top: 1px solid var(--rule);
}

/* A topic on the help page: the same card an album is, with its heading as the
   row that opens it. Not sticky the way an album's is -- fifteen of them
   pinning in turn while you scroll a reference is busier than it is useful. */
.tree-topic {
  border: 1px solid var(--rule);
  margin-bottom: 0.9rem;
  padding: 0 0.6rem;
}
.tree-topic > summary {
  display: flex;
  align-items: baseline;
  gap: 0.75rem;
  margin: 0 -0.6rem;
  padding: 0.55rem 0.6rem 0.4rem;
  background: var(--bg-raised);
  cursor: pointer;
  border-left: 2px solid transparent;
  list-style: none;
}
.tree-topic > summary::-webkit-details-marker { display: none; }
.tree-topic > summary:hover { background: var(--bg-hover); border-left-color: var(--rule); }
/* the +/- every other disclosure in this app draws */
.tree-topic > summary::before {
  content: "+";
  color: var(--faint);
  flex: none;
  width: 1ch;
}
.tree-topic[open] > summary::before { content: "\2212"; color: var(--dim); }
.tree-topic[open] > summary { border-bottom: 1px solid var(--rule); }

/* the heading is the summary's whole content, so it carries none of its own
   spacing */
.tree-topic > summary h2 { margin: 0; }
.tree-topic > summary h2::before { content: none; }
.tree-topic[open] > summary h2 { color: var(--fg); }

/* first and last children inside a card should not double its padding. The
   foot is excluded: it spans the padding on purpose. */
.tree-topic > :nth-child(2) { margin-top: 0.7rem; }
.tree-topic > :nth-last-child(2) { margin-bottom: 0.7rem; }

/* The elbow that closes a card, the same one .album-foot and .tradition-foot
   draw. A third copy of nine lines, and deliberately so: this stylesheet is
   organised by the thing being styled rather than by the shape of the rule, and
   the other two live beside the album tree and the glossary for the same
   reason. Named here so anyone consolidating the three can find them. */
.topic-foot {
  margin: 0.5rem -0.6rem 0;
  padding: 0.15rem 0.6rem;
  border-top: 1px solid var(--rule);
  background: var(--bg-raised);
  color: var(--dim);
  font-size: 0.75rem;
  text-align: right;
}
.topic-foot::before { content: "\2514 "; color: var(--faint); }

/* Each album is a card, and its row is the card's header: it pins under the
   site header so the album is named for as long as its songs are on screen.
   Same treatment as a tradition on the glossary. */
.tree-album {
  border: 1px solid var(--rule);
  margin-bottom: 0.9rem;
  padding: 0 0.6rem;
}

.tree-album > summary {
  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, and the
     top padding is the clearance from the search bar */
  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-album > summary::-webkit-details-marker { display: none; }
.tree-album > summary:hover { background: var(--bg-hover); border-left-color: var(--rule); }

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

/* The album row has its own columns, so its own tracks: the +/- marker, the
   counts, the name, and the arrow that opens the album. It does not line up
   with the songs beneath it and should not -- they are different tables, and
   the songs are deliberately indented under their directory. */
.tree-album > summary {
  display: grid;
  grid-template-columns: 1ch var(--album-cols) minmax(0, 1fr) auto;
  gap: 0 0.75rem;
}
.tree-album > summary .col {
  text-align: right;
  white-space: nowrap;
}
.tree-name { color: var(--cyan); margin-left: 0.5rem; }
.tree-open {
  margin-left: auto;
  color: var(--faint);
  padding: 0 0.4rem;
}
.tree-open:hover { color: var(--cyan); text-decoration: none; }

/* The songs sit under their directory, indented and ruled, so the nesting is
   visible without a second colour. */
.tree-album > ul.listing {
  margin: 0 0 0.5rem 1.5ch;
  border-left: 1px solid var(--rule);
  padding-left: 1rem;
}

/* --- directories are not files ------------------------------------------- */

/* `ls --color` draws directories bold and blue and leaves regular files plain,
   which is the distinction that was missing: an album and a song were both
   cyan, so a fully expanded tree read as one long list. */
/* An artist is a directory of the corpus -- `transcriptions/` holds them the
   way the root holds `transcriptions/`, `tarot/` and `books/` -- so it wears
   what every other directory here wears. */
ul.listing li.is-artist a,
ul.listing li.is-section a,
ul.listing li.is-album a,
.tree-name {
  color: var(--cyan);
  font-weight: 600;
}
ul.listing li.is-song a { color: var(--fg); font-weight: 400; }
/* A section of the corpus is a directory, so it wears what a directory wears --
   the home page lists two of them. A card is a file in one, like a song. */
ul.listing li.is-card a { color: var(--fg); font-weight: 400; }
ul.listing li.is-book a { color: var(--fg); font-weight: 400; }
ul.listing li.is-term a { color: var(--mauve); }
ul.listing li.is-heading a { color: var(--comment); }

/* The name beside the directory, which is the one thing a slug cannot say:
   `art-tawanghar` is an address and `Art Tawanghar` is a person. Quiet,
   because the row is a listing of directories and this is the gloss on one --
   the same relationship a completion's hint has to its value. */
.row-hint {
  margin-left: 0.6rem;
  color: var(--faint);
  font-size: 0.78rem;
  font-weight: 400;
}

/* Whose record it is, on the one listing where two artists are mixed -- the
   tradition filter across everybody. A link, because the page it opens is the
   rest of their work. It sits between the name and the arrow, and takes the
   `margin-left: auto` so the arrow stays pinned at the row's edge. */
.tree-whose {
  margin-left: auto;
  color: var(--faint);
  font-size: 0.78rem;
}
.tree-whose:hover { color: var(--cyan); }
/* and the arrow gives the margin up, or the two push each other apart */
.tree-whose + .tree-open { margin-left: 0.6rem; }

/* an uncertain token keeps the colour it has in a lyric */
ul.listing li.is-trailing a,
ul.listing li.is-bare a,
ul.listing li.is-leading a,
ul.listing li.is-parenthetical a,
ul.listing li.is-trailing .orphan,
ul.listing li.is-bare .orphan,
ul.listing li.is-leading .orphan,
ul.listing li.is-parenthetical .orphan { color: var(--amber); }

/* An open card bands its header and rules it off from the songs below. */
.tree-album[open] > summary {
  background: var(--bg-raised);
  border-left-color: var(--cyan);
  border-bottom: 1px solid var(--rule);
}
.tree-album[open] > summary:hover { background: var(--bg-hover); }

.album-foot {
  margin: 0.5rem -0.6rem 0;
  padding: 0.15rem 0.6rem;
  border-top: 1px solid var(--rule);
  background: var(--bg-raised);
  color: var(--dim);
  font-size: 0.75rem;
  text-align: right;
}
.album-foot::before { content: "\2514 "; color: var(--faint); }

/* --- the glossary tree --------------------------------------------------- */

/* The header over a tradition's terms, one per card -- the way an opened album
   heads its songs. On --term-cols like the rows, and in a row's box: the same
   0.5rem padding and the same 2px transparent border a row lights up on hover.
   Matching the grid alone is not enough, which the album header proved by
   sitting six pixels off its own figures. */
.tradition-head {
  display: grid;
  grid-template-columns: 1ch var(--term-cols) minmax(0, 1fr) auto;
  gap: 0 0.75rem;
  padding: 0.1rem 0.5rem 0.2rem;
  border-left: 2px solid transparent;
  border-bottom: 1px solid var(--rule);
  margin-bottom: 0.2rem;
}
.tradition-head span {
  color: var(--faint);
  font-size: 0.7rem;
  text-align: right;
}
/* the two that name text rather than figures */
.tradition-head .head-term { text-align: left; margin-left: 0.5rem; }
.tradition-head .head-meaning { text-align: left; }

.tree section.tradition h2 {
  color: var(--comment);
  font-size: 0.8rem;
  margin: 1.25rem 0 0.3rem;
}

/* The row a term opens from. A flex line by default, because the same class is
   the term in the pane beside the editor and the uncertainty beside that -- two
   or three things on one narrow line with no columns to keep. */
.tree-term > summary {
  display: flex;
  align-items: baseline;
  gap: 0.75rem;
  padding: 0.2rem 0.5rem;
  cursor: pointer;
  border-left: 2px solid transparent;
  list-style: none;
}

/* On the glossary index, and only there, it is a grid: that page is a table
   with a header over it, and flexed every definition began wherever its term
   happened to end. Scoped to .tradition because this rule was not, and the
   editor's panes -- two children where the index has four -- were laying their
   term out in the track meant for a line count, four and a half rem wide, with
   the tradition stranded beside it. */
.tradition .tree-term > summary {
  display: grid;
  grid-template-columns: 1ch var(--term-cols) minmax(0, 1fr) auto;
  gap: 0 0.75rem;
}
.tree-term > summary::-webkit-details-marker { display: none; }
.tree-term > summary:hover { background: var(--bg-hover); border-left-color: var(--rule); }
/* The marker has to read as "this opens". It was the faintest colour in the
   palette next to a term name styled like a link, which is the wrong signal
   twice over. */
.tree-term > summary::before {
  content: "+";
  color: var(--dim);
  flex: none;
  width: 1ch;
}
.tree-term > summary:hover::before { color: var(--mauve); }
.tree-term[open] > summary::before { content: "\2212"; color: var(--mauve); }
.tree-term[open] > summary { background: var(--bg-raised); border-left-color: var(--mauve); }
.tree-term[open] > summary:hover { background: var(--bg-hover); }
.tree-term[open] { margin-bottom: 0.75rem; }

.tree-term > summary .col {
  color: var(--faint);
  font-size: 0.78rem;
  text-align: right;
  /* the flex case needs a width to line up in; the grid gives it a track */
  flex: none;
  min-width: 4.75rem;
}
.tradition .tree-term > summary .col { min-width: 0; }

/* a term keeps the colour it has in a lyric */
/* a term keeps the colour it has in a lyric */
.term-name { color: var(--mauve); font-weight: 600; margin-left: 0.5rem; }

/* Clipped only on the glossary index, where the name has a track of its own and
   one long term would push every definition on the page sideways. It must not
   be clipped anywhere else, and for a while it was: this class is also the term
   in the pane beside the editor, which is a narrow flex column with nothing to
   protect -- so names there lost their ends to a rule written for a table on
   another page. */
.tradition .term-name {
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

/* The definition, on the row. --comment because it reads as a gloss rather than
   as data: the counts beside it are figures and this is prose, and the app
   already says prose in the colour of a shell comment.

   It takes what is left of the row and clips: a definition that pushed the
   arrow off the end, or wrapped a one-line row onto two, would cost more than
   it gave. min-width 0 because a flex item will not shrink below its content
   without it, which is the whole reason the ellipsis appears at all. */
.term-gloss {
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  color: var(--comment);
}

.tree-body {
  margin: 0.35rem 0 0 1.5ch;
  border-left: 1px solid var(--rule);
  padding: 0.1rem 0 0.1rem 1rem;
}
.tree-body .description { border-left: 0; padding-left: 0; margin-bottom: 0.4rem; }
.tree-body .note { margin: 0.3rem 0; }

/* --- tradition: boxed, with a heading that holds its place --------------- */
/* Two problems, one block. The box bounds the category, so a term is visibly
   inside it. The heading pins under the site header, so the category stays
   named for as long as you are in it. And they are better together than
   either alone: a sticky element stops at its container's edge, so the pinned
   heading hands off cleanly at the box boundary instead of floating on over
   the next tradition.

   Boxes without the sticky heading are on the glossary-boxed branch. The
   sticky heading without boxes is commit 189a908 on this one. */

.tree section.tradition {
  border: 1px solid var(--rule);
  margin: 1.25rem 0 1.5rem;
  padding: 0 0.6rem;
}

.tree section.tradition h2 {
  position: sticky;
  top: var(--sticky-top);           /* clears the site header, itself sticky */
  z-index: 2;
  /* Negative side margins so the pinned band spans the box edge to edge.
     Without them the box padding shows terms sliding past behind it. */
  margin: 0 -0.6rem 0.35rem;
  /* The top padding is the clearance from the site header: it keeps the
     category name off the search bar without opening a gap for text to
     scroll through. */
  padding: 0.85rem 0.6rem 0.45rem;
  background: var(--bg-raised);
  border-bottom: 1px solid var(--rule);
  color: var(--cyan);
}

.tradition-foot {
  margin: 0.5rem -0.6rem 0;
  padding: 0.15rem 0.6rem;
  border-top: 1px solid var(--rule);
  background: var(--bg-raised);
  color: var(--dim);
  font-size: 0.75rem;
  text-align: right;
}
.tradition-foot::before { content: "\2514 "; color: var(--faint); }

/* --- the workbench ------------------------------------------------------- */

.tree-controls a.is-current { color: var(--green); }

ul.uncertain-list { list-style: none; margin: 1rem 0 0; padding: 0; }
ul.uncertain-list > li {
  border: 1px solid var(--rule);
  margin-bottom: 0.6rem;
  padding: 0 0.6rem 0.4rem;
}
/* a reading that moved when the file changed is the first thing to look at */
ul.uncertain-list > li.needs-review { border-color: var(--amber); }

.uncertain-row {
  display: flex;
  align-items: baseline;
  gap: 0.75rem;
  margin: 0 -0.6rem;
  padding: 0.4rem 0.6rem;
  background: var(--bg-raised);
  color: var(--fg);
}
.uncertain-row:hover { background: var(--bg-hover); text-decoration: none; }
.uncertain-row .col {
  color: var(--faint);
  font-size: 0.78rem;
  flex: none;
  min-width: 6.5rem;
}
.uncertain-row .kind { color: var(--dim); }
.uncertain-row .token { color: var(--amber); font-weight: 600; }
.uncertain-row .cite { margin-left: auto; color: var(--faint); font-size: 0.78rem; }
.resolved { color: var(--green); }

.uncertain-context { padding: 0.4rem 0 0.1rem 0.2rem; }

/* --- a panel of evidence ------------------------------------------------- */

section.card {
  border: 1px solid var(--rule);
  margin: 1.25rem 0;
  padding: 0 0.75rem 0.6rem;
}
/* A flex row so a control can sit at its right end. The band was only ever
   text until a card gained a pencil; `margin-left: auto` on the control is what
   pins it, which is the arrangement `.pane-status.is-head` already has above
   the lyric -- and the rule CLAUDE.md states for the tarot card, that the
   controls live in the bar that names the file. */
/* `> h1` as well, for the book page: its notes card carries the filename and
   is the page's only heading, the bar above the column having been what it
   replaced. Identical treatment, because they are the same band -- the level
   is about what the document says, not about how the band looks. */
section.card > h1,
section.card > h2 {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin: 0 -0.75rem 0.5rem;
  padding: 0.45rem 0.75rem 0.35rem;
  background: var(--bg-raised);
  border-bottom: 1px solid var(--rule);
  color: var(--comment);
  font-size: 0.78rem;
}

/* `.status-edit`'s twin, one level down: the same mark, the same colour, the
   same corner. Only the first control claims the auto margin -- if a second
   one ever joins it here, it does not. */
.card-edit {
  margin-left: auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  padding: 0 0.2rem;
  border-radius: 2px;
  color: var(--act-edit);
}
.card-edit:hover { background: var(--bg-hover); text-decoration: none; }

/* The removing control, beside the pencil and quieter than it. `--faint` at
   rest and up to `--fg` under the pointer, because a destructive thing should
   not be the loudest mark in the line -- the rule `.status-remove` follows in
   a card's head bar. No auto margin: the pencil ahead of it already claimed
   one, and claiming it twice pushes them apart rather than sitting them
   together. */
.card-remove {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  padding: 0 0.2rem;
  border-radius: 2px;
  color: var(--faint);
}
.card-remove:hover { color: var(--fg); background: var(--bg-hover); text-decoration: none; }
.card-remove:focus-visible {
  outline: 2px solid var(--act-edit);
  outline-offset: 1px;
  text-decoration: none;
}
.card-edit:focus-visible {
  outline: 2px solid var(--act-edit);
  outline-offset: 1px;
  text-decoration: none;
}

/* Beside `save`, and quieter than it: leaving the form is not the thing you
   came to the form to do. */
.edit-cancel { margin-left: 0.8rem; font-size: 0.8rem; color: var(--faint); }
.edit-cancel:hover { color: var(--fg); }
section.card > h3 {
  margin: 1rem 0 0.3rem;
  font-size: 0.75rem;
  font-weight: 400;
  color: var(--comment);
}
section.card .cite { color: var(--faint); font-size: 0.75rem; padding: 0 0 0.5rem 3.5ch; }

/* the line being worked on, among its neighbours */
.line.is-focus { background: var(--bg-hover); }
.line.is-focus .line-number { color: var(--amber); }

ul.candidates { list-style: none; margin: 0.3rem 0 0; padding: 0; }
ul.candidates li {
  display: flex;
  align-items: baseline;
  gap: 0.75rem;
  padding: 0.2rem 0.4rem;
  border-left: 2px solid transparent;
}
ul.candidates li.is-chosen { border-left-color: var(--green); background: var(--bg-hover); }
.candidate-text { color: var(--fg); }
ul.candidates li.is-chosen .candidate-text { color: var(--green); }
.candidate-why { color: var(--comment); font-size: 0.8rem; }

/* the help page shows the marks themselves rather than describing them */
dl.commands dt.mark-term {
  color: var(--mauve);
  border-bottom: 1px dotted var(--mauve);
  width: fit-content;
}
/* A dt naming a fact rather than a thing to type. The column is green because
   green here means "type this"; a label that is a statement must not borrow
   that or the list reads as five commands, two of which do not exist. */
dl.commands dt.is-fact { color: var(--fg); }

/* The pencil, shown as the row wears it: same colour, same small box, so the
   help is demonstrating the mark rather than describing it -- which is what
   the two marks below already do for a term and a guess. */
dl.commands dt.mark-edit { color: var(--act-edit); }
/* The box goes on a span inside the dt, not on the dt: the dt is the column
   every row's description starts after, and a box the width of that column is
   a rectangle with a pencil lost in the middle of it. */
dl.commands dt.mark-edit span {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 1.7em;
  padding: 0 0.45rem;
  border: 1px solid var(--rule);
  border-radius: 2px;
}
dl.commands dt.mark-edit svg { display: block; }

dl.commands dt.mark-uncertain {
  color: var(--amber);
  text-decoration: underline wavy var(--amber);
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
}

/* --- writing: the only input this app takes ------------------------------ */

.flash {
  margin: 1rem 0 0;
  padding: 0.35rem 0.6rem;
  border-left: 2px solid var(--green);
  background: var(--bg-raised);
  color: var(--fg);
  font-size: 0.85rem;
}
.flash::before { content: "$ "; color: var(--green); }
.flash.is-alert { border-left-color: var(--amber); }
.flash.is-alert::before { content: "! "; color: var(--amber); }

.reading-form { margin-top: 0.9rem; }
.reading-form .field {
  display: flex;
  align-items: baseline;
  gap: 0.75rem;
  margin-bottom: 0.4rem;
}
.reading-form label {
  color: var(--dim);
  font-size: 0.78rem;
  flex: none;
  min-width: 9rem;
}
/* A box you type in, named once for every form in the app. There were three
   copies of this and they had already drifted -- two backgrounds and two
   focus colours between them -- which is how a form comes to look like
   another app's form on the next page.

   The border is --faint and not --rule. A form is the one place a page asks
   the reader for something, and at --rule against the page it was very nearly
   not there: you had to already know to click. --faint is the grey the line
   numbers wear, which is the quietest thing here that is still plainly a
   thing.

   Not the command lines. The search bar, the palette and the editor's `:`
   line are prompts rather than boxes -- their frame belongs to the bar around
   them, and they already hold the cursor when the page loads. A box has to
   say it is a box because nothing else on the page does. */
.reading-form input[type="text"],
.recording-form input,
.mkdir-form input,
.mkdir-form select,
.mkdir-form textarea {
  font: inherit;
  color: var(--fg);
  background: var(--bg-raised);
  border: 1px solid var(--edge);
  border-radius: 2px;
  padding: 0.3rem 0.5rem;
  outline: none;
}

/* Green is what focus wears everywhere else here: .search and .editor both
   take it on focus-within. So a field being typed into says so in the same
   colour a buffer being typed into does. */
.reading-form input[type="text"]:focus,
.recording-form input:focus,
.mkdir-form input:focus,
.mkdir-form select:focus,
.mkdir-form textarea:focus { border-color: var(--green); }

.reading-form input[type="text"] { flex: 1; }


/* A meaning is several lines in glossary.md -- definition first, elaboration
   after -- so the box is one too. It resizes downward only: a field growing
   wider than the form would break the label column the fields are laid on. */
.mkdir-form textarea { resize: vertical; min-height: 4.5rem; }

/* The list exists because a glossary heading is never invented here, so it
   has to look like the boxes beside it rather than like the browser's own
   control. */
.mkdir-form select { appearance: none; padding-right: 1.6rem; }

.form-actions {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  margin-top: 0.6rem;
  padding-left: 9.75rem;
}
.form-actions .note { margin: 0; padding-left: 0; }
.form-actions .note + form { margin-left: auto; }

/* **The indent above is for a label column this form does not have.**
   `.form-actions` is shared with `.reading-form`, whose labels sit in a 9rem
   column to the left of its boxes -- so the button lines up under the boxes
   rather than under the labels. `.mkdir-form` overrides its labels to
   `display: block` and stacks them *above* the boxes, which left the button
   indented past the left edge of every field on the page, lined up with
   nothing.

   Chad noticed it on the settings page; it was every form in the app. */
.mkdir-form .form-actions { padding-left: 0; }

button {
  font: inherit;
  font-size: 0.8rem;
  color: var(--dim);
  background: var(--bg-raised);
  border: 1px solid var(--rule);
  border-radius: 2px;
  padding: 0.2rem 0.7rem;
  cursor: pointer;
}
button:hover { color: var(--fg); border-color: var(--edge); }
button.is-primary { color: var(--green); border-color: var(--edge); }
button.is-primary:hover { background: var(--bg-hover); }

/* an action that reads as a link but has to be a form, because it writes */
.candidate-actions { margin-left: auto; display: flex; gap: 0.4rem; }
.candidate-actions form { display: inline; }
.as-link button, button.as-link {
  background: none;
  border: 0;
  padding: 0 0.2rem;
  color: var(--cyan);
  font-size: 0.78rem;
}
.as-link button:hover { color: var(--fg); text-decoration: underline; }
.is-quiet button, button.is-quiet { color: var(--faint); }

.uncertain-link { color: var(--amber); }
ul.pane-terms li.is-current .uncertain-link { color: var(--amber); }

/* The form has a 9rem label column, which does not fit a 24rem window.
   Beside a song it stacks instead. */
.pane-right .reading-form .field { display: block; }
.pane-right .reading-form label { display: block; margin-bottom: 0.15rem; min-width: 0; }
.pane-right .reading-form input[type="text"] { width: 100%; }
.pane-right .form-actions { padding-left: 0; flex-wrap: wrap; }
.pane-right .form-actions .note + form { margin-left: 0; }
.pane-right .candidate-actions { gap: 0.2rem; }

/* --- the concordance ----------------------------------------------------- */

ul.stats-list li { border-left: 0; }
ul.stats-list .col { min-width: 5rem; color: var(--fg); }
.stat-name { color: var(--dim); font-size: 0.8rem; }

ul.concordance li.is-word a { color: var(--fg); }
ul.concordance li.is-word a:hover { color: var(--cyan); }
ul.concordance .col.has-uncertain { color: var(--amber); }
/* the unknown slots are not a word, and do not read as one */
ul.concordance li.is-unknown a { color: var(--amber); font-weight: 600; }
/* --faint, not --comment. This asked for the quieter of the two and --comment
   used to be it; under the ramp above a gloss is the brighter one, because a
   gloss is a sentence somebody reads. */
ul.concordance li.is-unknown .meta { color: var(--faint); }

/* --- the editor ---------------------------------------------------------- */

.editor {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  border: 1px solid var(--rule);
  background: var(--bg);
  margin-top: 1rem;
}

/* The gutter, the mirror and the buffer must agree about a line's height to
   the pixel, or the numbers drift away from the text they belong to and the
   colours drift away from the letters. Declared once for all three, because
   three copies of one set of metrics is how they come apart. */
.editor-gutter,
.editor-mirror,
.editor textarea {
  font: inherit;
  font-size: 13.5px;
  line-height: 1.7;
  padding: 0.6rem 0.75rem;
  margin: 0;
}

.editor-gutter {
  color: var(--soft);
  text-align: right;
  user-select: none;
  overflow: hidden;
  border-right: 1px solid var(--rule);
  background: var(--bg-raised);
}

/* The buffer and the marks behind it, stacked. The wrapper is sized by the
   textarea alone -- the mirror is taken out of flow -- so `resize: vertical`
   still works and the mirror follows it without being watched. */
.editor-buffer {
  position: relative;
  min-width: 0;
  display: grid;
}

.editor textarea {
  color: var(--fg);
  background: transparent;
  border: 0;
  outline: none;
  resize: vertical;
  min-height: 60vh;
  /* pre, not pre-wrap: a wrapped line would occupy two rows and the gutter
     would stop lining up. horizontal scroll is the honest trade. */
  white-space: pre;
  overflow-x: auto;
  tab-size: 4;
  position: relative;
  z-index: 1;
  /* No bars, and not for the look of it. A scrollbar takes its width out of
     the buffer's content box and the mirror behind it has none, so the two
     stop being able to scroll the same distance -- the buffer reaches the end
     of the longest line and the mirror is fifteen pixels short of it, which is
     the marks sliding off the letters exactly where a long line ends. The
     gutter says where you are, the way it does in vim. */
  scrollbar-width: none;
}
.editor textarea::-webkit-scrollbar { display: none; }

/* The letters you read are the mirror's; the letters you edit are the
   textarea's, and they go transparent -- but only once highlight.js has drawn
   something behind them and said so. A stylesheet cannot know whether the
   mirror is there, and a buffer hidden with nothing behind it is an editor you
   cannot see. The caret is named separately or it would go with them. */
.editor.is-marked textarea {
  color: transparent;
  caret-color: var(--fg);
}

/* Selected text is transparent too, and chrome paints it in the element's own
   colour unless told otherwise -- so the band has to be faint enough to read
   the mirror through it. */
.editor.is-marked textarea::selection {
  color: transparent;
  background: color-mix(in srgb, var(--green) 22%, transparent);
}

/* Where the platform picks the colours there is no mirror to tell it what the
   marks are, so the buffer takes its own letters back. */
@media (forced-colors: active) {
  .editor.is-marked textarea { color: CanvasText; }
  .editor-mirror { display: none; }
}

/* Behind the buffer, at the same origin and the same size, whatever that
   becomes. overflow: hidden and no scrollbar of its own -- it is scrolled by
   hand from highlight.js, the way the gutter is. pointer-events: none so every
   click, drag and selection still lands in the textarea. */
.editor-mirror {
  position: absolute;
  inset: 0;
  overflow: hidden;
  white-space: pre;
  tab-size: 4;
  color: var(--fg);
  pointer-events: none;
  user-select: none;
}

/* A "# chorus" line is markdown in the file and reads as a shell comment in
   the lyric. It reads as one here too. */
.editor-mirror .is-heading { color: var(--comment); }

/* The mirror's own copy of the term and uncertainty marks is gone. It existed
   only because the rules above said `a.term-link`, and the mirror's marks are
   spans -- so the copy held the same declarations at the same values, written
   out twice to avoid changing three other pages. Those pages are changed on
   purpose now, and `.term-link` reaches the mirror the same way it reaches
   them. */

.editor:focus-within { border-color: var(--green); }

.editor-ruler {
  display: flex;
  gap: 0.75rem;
  margin-top: 0;
  position: static;
}
.editor-scope { margin-left: auto; color: var(--faint); }

/* The bar at the bottom, the same instrument as the one at the top. */
/* The foot: what is playing, then the command line, pinned as one block. Two
   independently sticky elements would have to know each other's heights, and
   the player's height changes when it is opened. */
.editor-foot {
  position: sticky;
  bottom: 0;
  z-index: 4;
}

/* The bar no longer pins itself -- the foot around it does. */
.editor-bar {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem 0.6rem;
  background: var(--bg-raised);
  border: 1px solid var(--rule);
  border-top: 0;
}
.editor-bar .prompt { color: var(--green); flex: none; }
.editor-bar input[type="text"] {
  font: inherit;
  flex: 1;
  /* a flex item will not shrink below its own min-content without this, and
     this one's placeholder names all three commands -- so on a narrow screen
     the field held the bar open and pushed the buttons off the page */
  min-width: 0;
  color: var(--fg);
  background: transparent;
  border: 0;
  outline: none;
  padding: 0.15rem 0;
}
.editor-buttons { display: flex; gap: 0.4rem; flex: none; }

/* The three commands, each wearing what it does, so a hand reaching for one
   does not have to read all three. Coloured by the command rather than by a
   class, because the command is the meaning -- the value posted is the word
   you would have typed in the bar beside them.

   Green writes and leaves you here: the prompt's colour, and the only one of
   the three that does not take the page away. --act-edit writes and goes, and
   that token already means "the way to act on this". Cyan goes and writes
   nothing, which is what cyan means everywhere else here -- a way out.

   Amber is not among them on purpose. Amber is uncertainty, and there is a
   column of it in the window beside this one; a button in that colour would
   be the page saying two things with one colour, which is the argument
   --act-edit was invented to settle. */
.editor-buttons button[value="w"] { color: var(--green); border-color: var(--edge); }
.editor-buttons button[value="wq"] { color: var(--act-edit); }
.editor-buttons button[value="q"] { color: var(--cyan); }
/* the colours are the whole signal, so hovering moves the ground, not them */
.editor-buttons button:hover { background: var(--bg-hover); border-color: var(--edge); }

/* --- a resolved mark is out of the way, not gone -----------------------------

   A resolved uncertainty is finished work, and the buffer beside the lyric is
   where the unfinished work is read. Left among the outstanding ones it wears
   the same shape as them, so a song with five marks and three answers reads as
   five things still to do.

   **One rule, both pages.** The editor's entries are `<details>` and the
   reading page's are `<li>`, and neither is named here -- `.is-resolved` is on
   whichever the page drew, and the panel around it is the same
   `[data-pane-panel="uncertainties"]` on both. That is the whole of the
   hiding: no javascript, no query parameter, no controller.

   Said as "the panel is not showing them" rather than as "the box is
   unchecked", because `:has()` takes the specificity of its argument and this
   file has been bitten by that once already -- on the share page, where
   `.share-pane:has(.share-term:target) .share-term { display: none }` outweighed
   the rule meant to reveal one and hid every entry. Here nothing competes:
   there is one declaration, and it stops applying when the box is ticked.

   **`.is-resolved` means something else further down**, on `.listen-item a`,
   where it is a recording a resolver found rather than one chad pasted. The
   two never meet -- that one lives in the links buffer and this rule reaches
   only into the uncertainties one -- but the word is doing two jobs in one
   stylesheet, so the scoping above is load-bearing rather than tidy. */
[data-pane-panel="uncertainties"]:not(:has(.resolved-switch input:checked)) .is-resolved {
  display: none;
}

/* The control itself, which is a label wrapping its own checkbox -- so the
   words are as pressable as the box, the way the filing rows already are. It
   is `--act-edit` on hover rather than at rest: revealing is acting on the
   page, but a control offering to show finished work should not be louder than
   the work that is left. */
.resolved-switch {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  margin: 0.25rem 0 1rem;
  padding: 0.25rem 0;
  color: var(--faint);
  font-size: 0.78rem;
  cursor: pointer;
}
/* **The number follows the switch.** Revealing the resolved rows puts marks on
   screen the count was not counting, and a 4 over five rows is the same
   disagreement as the 5 over four it replaced. Both figures are rendered and
   this swaps them -- no javascript, no request, the same mechanism as the rows
   themselves.

   Scoped on `body` rather than on the pane, which is the one thing here that
   had to be wider than it looks: the narrow tab strip's `?` count is drawn in
   `nav.song-tabs` at the top of the page, outside the aside the switch lives
   in. There is only ever one of these switches on a page, so the root is
   unambiguous. */
.n-all { display: none; }
body:has(.resolved-switch input:checked) .n-open { display: none; }
body:has(.resolved-switch input:checked) .n-all { display: inline; }

/* **Settled, and said as a fact rather than as a kind.** The row used to print
   "resolved" *instead of* the kind, in the kind's own slot and the kind's own
   grey -- so it read as a fifth sort of uncertainty beside trailing, bare and
   leading, and the two were genuinely hard to tell apart. Both are drawn now,
   which is what `/uncertainties` has always done.

   Green, because `.resolved` is already green on that page and green already
   means a success here. Giving the same word a second colour in a second place
   is the drift this palette's one-meaning-per-accent rule exists to stop -- so
   the grey this was nearly built in would have had to change `/uncertainties`
   too, and then green would mean nothing.

   A border as well as a colour, because a brightness step on its own reads as
   too subtle -- the same reason the current row does not rely on one. Tinted
   off the token with color-mix rather than written out, so it cannot go stale
   when --green moves. */
.pane-terms .resolved,
.is-uncertain-entry > summary .resolved {
  color: var(--green);
  border: 1px solid color-mix(in srgb, var(--green) 35%, transparent);
  border-radius: 2px;
  padding: 0 0.3rem;
  font-size: 0.68rem;
  flex: none;
}
/* the kind keeps the row's quiet grey and gives up `margin-left: auto` when a
   badge follows it, or the two are pushed to opposite ends of the row */
.pane-terms li:has(.resolved) .kind,
.is-uncertain-entry > summary:has(.resolved) .kind { margin-left: auto; }
.pane-terms li .resolved,
.is-uncertain-entry > summary .resolved { margin-left: 0.4rem; }

.resolved-switch:hover { color: var(--act-edit); }
.resolved-switch input { accent-color: var(--act-edit); margin: 0; }
/* the figure is what the row is reporting, so it wears --fg and the words
   beside it stay quiet -- `ls -l`'s rule about a count, kept here too */
.resolved-switch .count { color: var(--fg); }
.resolved-switch:hover .count { color: var(--act-edit); }

/* an uncertainty in the editor's pane keeps the amber it has in a lyric */
.tree-term.is-uncertain-entry[open] > summary { border-left-color: var(--amber); }
.tree-term.is-uncertain-entry > summary:hover::before,
.tree-term.is-uncertain-entry[open] > summary::before { color: var(--amber); }
.pane-right ul.candidates li { padding-left: 0; }
/* **A row, not a column, everywhere but here.** `ul.candidates li` is a flex
   row -- the reading, its reason and the two controls side by side -- which is
   right on the workbench, where the page is as wide as the app gets. In a
   20rem window those three items share the width, and the reason ends up in a
   sliver about a dozen characters across: a sentence set one word to a line.
   Stacked, each gets the whole window. */
.pane-right ul.candidates li { flex-direction: column; align-items: stretch; gap: 0.15rem; }
.pane-right .candidate-why { display: block; margin-left: 0; }

/* What the transcription is of. Above the split, so it touches none of the
   three-column geometry. Neutral tokens only: green means where you are, mauve
   belongs to the glossary and amber to uncertainty, and a player is none of
   those things. */
/* Capped, and flush left like the h1 and the path note above it. Uncapped it
   stretches the full 104rem of is-widest, which is a billboard rather than a
   citation. */
/* The cap is for the song page, where main runs to 104rem and an uncapped
   player is a billboard rather than a citation. The album page is the ordinary
   measure, and there the cap only made the panel 8rem narrower than the table
   under it -- two boxes on one page that nearly line up read worse than two
   that plainly do not. */
.recordings { margin: 0.75rem 0 0; }

/* In the glossary's column it takes the column, and it is the first thing in
   it -- so no top margin, or its top would sit below the lyric's rather than
   level with it. The old cap was for when this spanned the page above the
   split; there is nothing left to cap. */
.is-glossary .recordings { margin: 0 0 0.9rem; }

/* A recording is a panel, the way a curses box is one: a title sitting in the
   top rule, contents, and a status line along the foot. The contents are
   spotify's iframe, which is theirs -- their button, their artwork, their font,
   none of it reachable from here. Framing what we did not draw is the honest
   division, and it is what a terminal file manager does with a preview. */
.recording {
  border: 1px solid var(--rule);
  background: var(--bg-raised);
  margin-bottom: 0.9rem;
}

/* Shut, the panel is one line: a name and what it is. Open, the same line
   becomes the panel's title bar with spotify's player under it. The marker is
   the +/- every other disclosure in this app draws, because that is what this
   is -- a thing that opens, not a play button. */
.recording > summary {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  padding: 0.35rem 0.6rem;
  cursor: pointer;
  list-style: none;
  border-left: 2px solid transparent;
}
.recording > summary::-webkit-details-marker { display: none; }
.recording > summary::before {
  content: "+";
  color: var(--faint);
  flex: none;
  width: 1ch;
}
.recording[open] > summary::before { content: "\2212"; color: var(--dim); }
.recording > summary:hover { background: var(--bg-hover); border-left-color: var(--rule); }
.recording[open] > summary { border-bottom: 1px solid var(--rule); }

/* One line, whatever the column is. In the glossary's column there is not room
   for a long title and its length together, and a summary that wraps to two
   lines is not the one line this was made to be -- so it clips, the way a term's
   definition does, and the whole title stays in the page for the browser's own
   find. min-width:0 because a flex item will not shrink below its content
   without it, which is the whole reason an ellipsis ever appears. */
.recording-brief {
  color: var(--faint);
  font-size: 0.72rem;
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}
.recording[open] .recording-brief { color: var(--comment); }

/* The length, beside the title and never giving way to it. */
.recording-time { color: var(--faint); font-size: 0.72rem; flex: none; }
.recording[open] .recording-time { color: var(--comment); }

/* The title overlaps the border and paints the page behind itself, so the rule
   appears to run through it -- the same thing a box-drawn title does by leaving
   the line out. */
.recording-name { color: var(--dim); font-size: 0.78rem; font-weight: 600; }

/* An iframe carries a 2px inset border by default and there has never been a
   reset in this file, because there has never been an iframe. */
/* no border of its own: the panel around it has one now */
.recording iframe {
  display: block;
  width: 100%;
  border: 0;
}

/* Places to go, on one line. Not an accordion each: opening a panel to find a
   single link is a press in front of a press, and with five services a song
   that was one panel became a third of the page. */
.listen {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.15rem 0.6rem;
  margin: 0.5rem 0 0;
  font-size: 0.78rem;
}
.listen-label { color: var(--dim); }
.listen-item { display: inline-flex; align-items: baseline; gap: 0.15rem; }

/* A resolved link is not one chad wrote down. It is dimmer rather than absent
   -- the same claim a "?" makes about a lyric -- and a dotted underline says
   "this is asserted" without changing the letters or borrowing a colour that
   already means something here. */
.listen-item a.is-resolved {
  color: var(--dim);
  text-decoration-style: dotted;
}
.listen-item a.is-resolved:hover { color: var(--cyan); }

/* The unlink, quiet until it is reached for, exactly as the one in the meta
   band is. No colour of its own: congo has no token for "this takes something
   away" and inventing one to sit beside amber is not a decision to make in
   passing. */
.listen-item .unlink-form { display: inline-flex; }
.listen-item .unlink {
  color: var(--faint);
  background: none;
  border: none;
  padding: 0 0.1rem;
  font-size: 0.7rem;
  line-height: 1;
}
.listen-item .unlink:hover { color: var(--fg); }

.recording-link { margin: 0; }
.recording-link a { word-break: break-all; }

/* The foot is a status line, like the one under every other window here: solid
   rather than floating, so the panel has a bottom edge to sit on. */
/* The status line, which exists only while the panel is open -- shut, the
   summary is already saying what this is. */
.recording-meta {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin: 0;
  padding: 0.2rem 0.6rem;
  background: var(--rule);
  font-size: 0.72rem;
  color: var(--dim);
}

.recording-meta .col { color: var(--dim); }
.recording-meta .faint { color: var(--faint); }

.recording-meta .unlink-form { margin-left: auto; }

/* Sized to the line and quiet until reached for. An icon-only control needs a
   real focus ring rather than a colour change: on a 13px glyph a colour is not
   a signal that the keyboard is on the thing that unlinks. */
.recording-meta .unlink {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: none;
  border: 1px solid transparent;
  border-radius: 2px;
  color: var(--faint);
  cursor: pointer;
  padding: 0 0.3rem;
}
.recording-meta .unlink svg { display: block; }
.recording-meta .unlink:focus-visible {
  color: var(--fg);
  border-color: var(--rule);
  outline: 2px solid var(--dim);
  outline-offset: 1px;
}

.recording-meta .unlink:hover { color: var(--fg); }

.recording-form {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin-top: 0.5rem;
}

/* Layout only. The chrome these wear is declared with every other form field,
   beside .reading-form, so the three cannot drift apart again. */
.recording-form input,
.mkdir-form input,
.mkdir-form select,
.mkdir-form textarea {
  flex: 1 1 auto;
  min-width: 0;
  font-size: 0.8rem;
}

/* The mkdir form is the same input in a different arrangement: two fields
   stacked with their labels rather than one on a line, because a url you
   cannot see the end of is one you cannot check before pressing create. The
   chrome is named in the declaration above rather than described again. */
.mkdir-form .field { margin-bottom: 1.1rem; }
.mkdir-form label {
  display: block;
  color: var(--dim);
  font-size: 0.78rem;
  margin-bottom: 0.25rem;
}
/* Every kind of box you type in, not just the ones spelled `text`.
   `email_field` and `password_field` emit type="email" and type="password", so
   a rule written for text alone missed them -- and a missed input does not
   collapse or overflow, it quietly falls back to the browser's default `size`,
   which is a different number for each type. That is how the sign-in form came
   to have an email box and a password box of two different lengths. */
.mkdir-form input[type="text"],
.mkdir-form input[type="email"],
.mkdir-form input[type="password"],
.mkdir-form input[type="url"],
.mkdir-form textarea { width: 100%; }
/* Not 100%: a list is as wide as its longest heading and no wider, where a
   box you type into cannot know how much you will type. */
.mkdir-form select { min-width: 12rem; }
.mkdir-form .note { margin-top: 0.35rem; }

.recording-form button {
  background: var(--bg-raised);
  border: 1px solid var(--rule);
  border-radius: 2px;
  color: var(--dim);
  cursor: pointer;
  font-family: var(--mono);
  font-size: 0.8rem;
  padding: 0.3rem 0.7rem;
}

.recording-form button:hover { color: var(--fg); border-color: var(--dim); }

/* Below the flanked breakpoint the page is one column, so the cap comes off and
   the paste field takes a line of its own. */
@media (max-width: 62rem) {
  .recordings { max-width: none; }
  .recording-form { flex-wrap: wrap; }
  .recording-form input { flex: 1 1 100%; }
}

/* Which ls flags are in force. Marked with --fg and brackets rather than
   colour: green means where you are, and a sort order is not a location. */
.tree-controls .sort-flags a { color: var(--dim); }
.tree-controls .sort-flags a[aria-current] { color: var(--fg); }
.tree-controls .sort-flags a[aria-current]::before { content: "["; color: var(--faint); }
.tree-controls .sort-flags a[aria-current]::after { content: "]"; color: var(--faint); }

/* Every file here is .md, so the extension is the same fact on every row. It
   steps back and leaves the name to be read. On the link rather than on it, so
   the link keeps the colour that tells a file from a directory. */
ul.listing li a .ext { color: var(--faint); }


/* A heading that carries a fact out to the right of itself -- a release date
   beside an album's name. Metadata grey, and never the size or weight of the
   heading it sits in: it is an aside, not a second title. */
h1.has-aside {
  display: flex;
  align-items: baseline;
  gap: 1rem;
}
h1.has-aside .aside {
  margin-left: auto;
  color: var(--dim);
  font-size: 0.8rem;
  font-weight: 400;
}

/* The aside holds a path and the way into it, so it is a row of its own rather
   than a run of text. */
h1.has-aside .aside {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
}

/* The way into the editor, on a heading rather than in a row.
   The same box the listing's `e` wears -- a quiet border, sized to the text
   line -- and the same --act-edit, because that token already means "the way to
   act on this" here and two edit affordances in two colours would read as two
   different actions. Its own class rather than .row-edit: that one carries
   margin-left:auto for a listing cell and is measured by the listing's tests,
   and a heading control should not be able to break them.

   Tracker draws this pencil in --faint and deliberately gives it no action
   colour, its three having been computed together under simulated colour-vision
   deficiency. The mark is borrowed; the colour is congo's own. */
.aside-edit {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 1.7em;
  padding: 0 0.4rem;
  border: 1px solid var(--rule);
  border-radius: 2px;
  color: var(--act-edit);
}
.aside-edit:hover { border-color: var(--act-edit); text-decoration: none; }
.aside-edit:focus-visible {
  outline: 2px solid var(--act-edit);
  outline-offset: 1px;
  text-decoration: none;
}
.aside-edit svg { display: block; }

/* The header names the columns, since the nouns are no longer in the cells.
   Deliberately not a heading: it is the faintest thing on the page, ruled off
   from the rows so the eye can skip it once it has been read. Lower case, like
   everything else here. */
/* The gap to the first row is set here rather than by a negative margin
   cancelling the list's own. That worked on a page where the list carries
   `margin-top: 1rem` and overlapped row one inside an album card, where the
   nested list carries none for it to cancel. */
.listing-head {
  display: grid;
  gap: 0 0.75rem;
  margin: 1rem 0 0;
  padding: 0 0.5rem 0.15rem;
  border-bottom: 1px solid var(--rule);
}
.listing-head + ul.listing { margin-top: 0.2rem; }
.listing-head .col,
.listing-head .head-name { color: var(--faint); font-size: 0.7rem; text-align: right; }
.listing-head .head-name { text-align: left; padding-left: 0.5rem; }

/* The album header, on the same tracks as the summary rows below it -- the same
   --album-tracks, so the two cannot drift the way they had. A <summary> cannot
   be a row of a table, so the header is a separate element and the alignment is
   a shared custom property rather than a shared box. */
.tree-head {
  display: grid;
  /* 1ch for the +/- marker, then the columns, then the name and the arrow */
  grid-template-columns: 1ch var(--album-cols) minmax(0, 1fr) auto;
  gap: 0 0.75rem;
  /* The header sits outside the cards, so it has to reproduce a card's box to
     line up with one: 1px for the card's own border, the 2px transparent border
     a row lights up on hover, and the same 0.6rem of padding. Matching the grid
     alone is not enough and was not -- the tracks agreed while every column sat
     six pixels to the right of the numbers it labelled, because the two boxes
     started in different places. */
  margin: 0 1px;
  padding: 0 0.6rem 0.2rem;
  border-left: 2px solid transparent;
  border-bottom: 1px solid var(--rule);
}
.tree-head .col,
.tree-head .head-name { color: var(--faint); font-size: 0.7rem; text-align: right; }
/* margin, not padding: the name beside it is nudged in with a margin, and two
   boxes that start in different places cannot be checked against each other
   even when the text lines up. The text sits identically either way. */
.tree-head .head-name { text-align: left; margin-left: 0.5rem; }

/* The album's own page draws the same card the home page draws when a directory
   is opened: a banded header naming it, the files inside, an elbow closing it.
   Not a <details> -- there is nothing to collapse on the page that is already
   about this one album. */
section.card.album-card { padding: 0 0.6rem 0; }
section.card.album-card > h2 {
  display: flex;
  align-items: baseline;
  gap: 0.75rem;
  margin: 0 -0.6rem;
  padding: 0.55rem 0.6rem 0.4rem;
  color: var(--cyan);
  font-weight: 600;
}
/* what is known about the release, out at the right of the band the way a date
   sits at the right of an ls -l row */
.release-info { margin-left: auto; color: var(--faint); font-size: 0.72rem; font-weight: 400; }
section.card.album-card .listing-head { margin-top: 0.6rem; }

/* --- the player on the last line ----------------------------------------- */

/* cmus and ncmpcpp keep the player pinned to the bottom row while the list
   scrolls above it. Anchored at the bottom, the panel grows upward, so
   column-reverse puts the clickable status line on the true bottom edge with
   spotify's player opening above it rather than below. */
.recordings.is-bar {
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  /* under the command menu (20) and the palette (50), over everything on the
     page itself */
  z-index: 4;
  margin: 0;
  background: var(--bg-raised);
  border-top: 1px solid var(--rule);
  box-shadow: 0 -8px 12px -8px rgba(0, 0, 0, 0.75);
}

/* the bar spans the window; what is in it keeps the page's measure */
.recordings.is-bar > .recording,
.recordings.is-bar > .pane-echo,
.recordings.is-bar > .recording-form {
  max-width: var(--measure);
  margin-left: auto;
  margin-right: auto;
}

.recordings.is-bar .recording {
  display: flex;
  flex-direction: column-reverse;
  border: 0;
  background: none;
  margin: 0;
}
.recordings.is-bar .recording > summary { border-left: 0; }
.recordings.is-bar .recording[open] > summary { border-bottom: 0; border-top: 1px solid var(--rule); }
.recordings.is-bar .recording-meta { background: none; }
.recordings.is-bar .pane-echo { margin: 0; padding: 0.35rem 0.6rem; }
.recordings.is-bar .recording-form { padding: 0 0.6rem 0.4rem; }

/* the last row of the listing must not sit under the bar */
body:has(.recordings.is-bar) { padding-bottom: 3rem; }

/* A track the linked release has and this directory does not. It is not a file,
   so it does not wear a filename's colour -- it is the faintest row in the
   listing, and its name is spotify's spelling rather than a slug. */
ul.listing li.is-absent a { color: var(--comment); }
ul.listing li.is-absent a:hover { color: var(--fg); }

/* The row's offer, said with a character the way this app says everything: a
   file that could exist. Without it a faint row is just a faint row, and there
   is nothing to tell you it can be clicked. */
/* The + is gone: the box at the head of the row says whether it is here, and
   two marks for one fact is one too many. */
ul.listing li.is-absent a:hover { color: var(--fg); }
.col.is-absent-note { color: var(--faint); font-style: normal; }

/* --- the tracks that are not here ---------------------------------------- */

/* Beside the directory rather than mixed into it: one window says what has been
   transcribed, the other what there is to transcribe. Different questions. */
/* Both windows start on the same line, and the card ends where its contents do.
   A grid child stretches to the tallest item in its row by default, so a
   directory of five files beside a record of sixteen tracks had its border
   running on past the elbow that closes it -- a box drawn round nothing.
   .editor-split solves the same thing the same way. */
.split > section.card.album-card { margin-top: 0; align-self: start; }
.is-absent-pane { padding-top: 0; }

/* Its foot reads like the card's beside it: the name at the right-hand end,
   where the elbow puts it on the directory. Two boxes on one row should close
   the same way. */
.is-absent-pane .pane-status { text-align: right; }
.is-absent-pane .pane-name { color: var(--comment); }
.is-absent-pane .note { margin: 0.4rem 0 0; }
.is-absent-pane ul.listing { margin-top: 0.5rem; }

/* --- the deck -------------------------------------------------------------
   The window beside the tarot. It is the record pane's idea, so it wears the
   record pane's marks -- brackets for state, --comment for a row that is not
   here yet -- but its rows are its own, because an empty one is a button that
   starts the card rather than a link to it. */

/* Seventy-eight rows against a listing that is usually one. The window scrolls
   rather than the page, which is what the two panes beside a lyric already do
   and what makes them read as windows. Capped against the viewport so the foot
   stays on screen with the header and the status line accounted for. */
.is-deck .pane-body {
  max-height: calc(100vh - var(--header-h) - 6rem);
  overflow-y: auto;
}

/* Which arcana, and then which suit. Not a heading anybody reads twice -- it is
   there so seventy-eight names arrive in five runs instead of one. */
.deck-group {
  font-size: 0.72rem;
  font-weight: 400;
  color: var(--faint);
  margin: 0.9rem 0 0.2rem;
  border-bottom: 1px solid var(--rule);
  padding-bottom: 0.15rem;
}
.deck-group:first-of-type { margin-top: 0.6rem; }

ul.deck { list-style: none; margin: 0; padding: 0; }
ul.deck li {
  display: grid;
  /* the mark, the numeral, the name -- the record pane's three tracks, minus
     the duration a card does not have */
  grid-template-columns: 2rem 2.5rem minmax(0, 1fr);
  gap: 0 0.5rem;
  align-items: baseline;
  padding: 0.1rem 0.4rem;
  border-left: 2px solid transparent;
}
ul.deck li:hover { background: var(--bg-hover); border-left-color: var(--rule); }

.deck-number { color: var(--dim); font-size: 0.78rem; }

/* A card that is written reads like a file, because it is one. */
ul.deck li.is-written a { color: var(--fg); }
ul.deck li.is-written a:hover { color: var(--cyan); text-decoration: none; }

/* And one that is not reads like an absent track, because it is the same
   absence. The button is a button -- it posts -- and looks like the row it
   sits in, because dressing it as a control would put seventy-seven controls
   on a page that has one real one. */
.deck-form { margin: 0; }
ul.deck li.is-absent button {
  font: inherit;
  text-align: left;
  color: var(--comment);
  background: none;
  border: 0;
  border-radius: 0;
  padding: 0;
  width: 100%;
}
ul.deck li.is-absent button:hover { color: var(--act-edit); cursor: pointer; }
ul.deck li.is-absent button:focus-visible { outline: 1px solid var(--act-edit); }

/* whether it is here, the track number, how long it is, then what it is called */
ul.listing.is-long.cols-3 li,
.listing-head.cols-3 { grid-template-columns: 2rem 1.75rem 3rem minmax(0, 1fr); }
.col.is-track { color: var(--faint); }

/* A box, ticked or not. Brackets are already how this app marks state without
   spending a colour -- the ls flags in force wear them too. */
.col.is-done { color: var(--faint); text-align: left; }
li.is-song .col.is-done { color: var(--dim); }

/* Whether a card has its picture, in the mark the deck pane one window over
   already uses for "this one is here, that one is not". Left-aligned because it
   is a mark rather than a figure, and `:not(.is-none)` because an empty `[ ]`
   is a claim about nothing and goes quiet -- .col.is-none says so first and is
   declared earlier, so without this the brighter rule would win on source
   order. */
.col.is-picture { text-align: left; }
.col.is-picture:not(.is-none) { color: var(--dim); }

/* The name, and at the far end of its cell the way into the editor.

   It was the letter `e` for a while, on the argument that the row was spelling
   a command the app already understands -- `e a-door` opens the buffer. The
   command is still there and still spelled that way; what changed is that the
   song page grew a pencil for the same action, and one action wearing two marks
   in one app is worse than either mark is good. The pencil won because a row is
   read in passing and a drawing is quicker than a letter you have to know. */
.row-name { display: flex; align-items: baseline; gap: 0.5rem; min-width: 0; }
.row-name > a:first-child { min-width: 0; overflow-wrap: anywhere; }
/* The same button tracker puts on a row: sized to the text line, a quiet border
   at rest, the mark carrying the colour. It was built to hold a letter and to
   be the same size once that letter became an icon, which is why the swap moved
   nothing -- the height is the line's, not the glyph's. */
.row-edit {
  margin-left: auto;
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 1.7em;
  line-height: 1;
  padding: 0 0.45rem;
  border: 1px solid var(--rule);
  border-radius: 2px;
  background: transparent;
  color: var(--act-edit);
  font-size: 0.78rem;
}
.row-edit:hover {
  color: var(--act-edit);
  border-color: var(--act-edit);
  text-decoration: none;
}
.row-edit:focus-visible {
  outline: 2px solid var(--act-edit);
  outline-offset: 1px;
  text-decoration: none;
}
.row-edit svg { display: block; }

/* The player in the editor's foot. It is a strip above the command line rather
   than a panel: the buffer is the page, and this is a thing to reach for while
   typing in it. Opened, it grows upward over the buffer and the command line
   does not move. */
/* The player is the buffer's width. `main.is-widest .recordings { max-width:
   44rem }` caps the player on the song page, and it outranks the bar's own
   rules on specificity -- so in here it was leaving 25px of the pane showing
   past the player's right edge, which is a gap that reads as something behind
   it. */
.editor-foot .recordings.is-bar {
  position: static;
  max-width: none;
  border: 1px solid var(--rule);
  border-top: 0;
  box-shadow: none;
}
.editor-foot .recordings.is-bar > .recording,
.editor-foot .recordings.is-bar > .pane-echo { max-width: none; }
/* column-reverse, as the album bar has: the clickable line stays on the bottom
   row and spotify's player opens above it rather than below. */
.editor-foot .recordings.is-bar { border-bottom: 0; border-top: 0; }
.editor-foot .recordings.is-bar .pane-echo { padding: 0.3rem 0.6rem; font-size: 0.75rem; }


/* ------------------------------------------------------------ the player ---

   One line, the last line, on every page. cmus and ncspot both put the
   transport on the bottom row and let the work happen above it, and that is
   exactly the relationship here: the lyric is the page, this is the strip that
   says what is sounding.

   Nothing in here is green. --green means "where you are" in this app and a
   system test enforces it; a playing track is not a place. */
.player {
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  /* over the page, under the command menu (20) and the palette (50) -- the same
     storey the recording bar occupies */
  z-index: 4;
  height: var(--player-h);
  background: var(--bg-raised);
  border-top: 1px solid var(--rule);
  box-shadow: 0 -8px 12px -8px rgba(0, 0, 0, 0.75);
  font-size: 0.75rem;
}

/* the bar spans the window; what is in it keeps the page's measure */
.player-inner {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  height: 100%;
  max-width: var(--measure);
  margin: 0 auto;
  padding: 0 0.6rem;
}

.player-transport {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  /* the transport takes the room; what follows it is pushed to the right edge */
  flex: 1 1 auto;
  min-width: 0;
}

/* A bordered box, the way every pressable thing in this app is -- and the same
   shape as the row action, so a third button style is not invented. */
.player-key {
  flex: none;
  font: inherit;
  font-family: var(--mono);
  color: var(--act-edit);
  background: none;
  border: 1px solid var(--rule);
  border-radius: 2px;
  padding: 0 0.35rem;
  line-height: 1.4;
  cursor: pointer;
}
.player-key:hover { border-color: var(--act-edit); background: var(--bg-hover); }

/* The title spotify has for it, which is not the transcription's title and is
   not lowercased here -- it is a quotation, and the house style governs what
   this corpus writes rather than what a record company called a track. */
.player-track {
  color: var(--fg);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
  flex: 0 1 auto;
}

/* While the pointer is over the line this is not the time we are at, it is the
   time a press would land on. Two numbers that mean such different things must
   not look the same, so it borrows the colour everything else about to happen
   wears. */
.player-pos.is-preview { color: var(--act-edit); }

.player-pos,
.player-dur {
  flex: none;
  color: var(--dim);
  /* so the line either side does not jump a pixel every second */
  font-variant-numeric: tabular-nums;
}

/* Flat, two colours, no thumb: the part that has played and the part that has
   not. A progress line rather than a control -- pressing it does nothing yet. */
.player-line {
  flex: 1 1 6rem;
  min-width: 3rem;
  height: 2px;
  background: var(--edge);
}
.player-line {
  position: relative;
  cursor: pointer;
}

/* The line is 2px tall, which is a fine thing to look at and an impossible
   thing to hit. This gives it a 16px band to be clicked in without changing
   what is drawn: a pseudo-element's box is hit-tested and the click is
   dispatched to the element that owns it. Horizontally it stays inside the
   line, so it steals nothing from the times either side. */
.player-line::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: -7px;
  bottom: -7px;
}
.player-line i {
  display: block;
  height: 100%;
  width: 0;
  background: var(--fg);
}

/* The stretch that is cycling, underlined rather than boxed.

   A bracket around it was the first try and it was unreadable: six seconds of a
   three-minute song is three percent of the line, so two uprights that close
   together read as one tick. Underneath, the loop is its own mark and the
   playhead stays fully visible running through the line above it -- which
   matters, because watching the position cross the window is how you can tell
   the loop is doing anything at all.

   --act-edit because this is an action in force, the same thing the row's "e"
   means. Not --amber: amber is uncertainty here, and a loop is how a `?` gets
   resolved rather than a kind of one. */
.player-line b {
  position: absolute;
  top: 4px;
  height: 3px;
  /* a short loop is still a loop, and must be visible as more than a hairline */
  min-width: 5px;
  background: var(--act-edit);
}

.player-key.is-armed { color: var(--act-edit); border-color: var(--act-edit); }

.player-connect { color: var(--cyan); flex: none; }

/* Said plainly rather than left blank, the way "no metadata fetched" is on a
   recording. Amber because this is the app being uncertain about something,
   which is what amber means here. */
.player-say { color: var(--amber); flex: 1 1 auto; }
.player-device { color: var(--faint); flex: none; margin-left: auto; }

/* The last row of anything must not sit under the bar, and the editor's foot --
   which pins itself -- has to come to rest on top of it rather than behind it.
   Both need the height, and neither can measure it, which is what --player-h
   is for. */
body.has-player { padding-bottom: var(--player-h); }
body.has-player .editor-foot { bottom: var(--player-h); }

/* Two pinned bars would fight for the bottom row. The recording panel gives it
   up and rejoins the page: with a session its iframe is gone anyway, and
   without one it is a fallback rather than the thing being used. */
body.has-player .recordings.is-bar {
  position: static;
  box-shadow: none;
}
body.has-player .recordings.is-bar > .recording { flex-direction: column; }

/* --- books ------------------------------------------------------------------
   The shelf. A book's page is the card page with the pdf where the picture
   goes, so nearly everything here is inherited: .card-body does the grid,
   .card-figure does the sticky-and-then-stacking. What is left is the slab
   that stands in for a cover, and the filing controls.

   Placed before the phone block on purpose -- that block is last in the file
   because it has to outrank grids declared later at equal specificity. */

/* The picture of the book, which is page one of the book's own pdf, rendered
   by `rake congo:books:covers`. This block used to open by saying a pdf has no
   thumbnail without rendering one "and rendering pages is exactly what this app
   does not do" -- poppler was already in both images for the page count, so it
   turned out to cost one shell command.

   Only the width is fixed, and it is the slab's own 9rem, so the column does
   not shift when a book gains a cover. The height is left alone deliberately:
   these are scans of books that are not all one shape (400x619 against 400x518
   among the first four on the shelf), and forcing them into the slab's 3:4
   would be the app asserting a format the files do not have. */
.book-picture {
  display: block;
  width: 9rem;
  height: auto;
  border: 1px solid var(--edge);
  border-radius: 2px;
}

/* What stands in until one is rendered -- and the slab *is* the link. It was a
   grey box with "pdf" written on it and the control underneath, which read as a
   cover image that had failed to load rather than as something to press. So the
   largest thing in the column is the thing you open, and it says the book's
   name: "pdf" was the one fact the page could already see from the filename.

   The heavier left border is a spine, and it is what makes the box read as a
   book rather than as a card. */
.book-slab {
  width: 9rem;
  height: 12rem;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  padding: 0 0.75rem;
  border: 1px solid var(--edge);
  border-left-width: 3px;
  border-radius: 2px;
  background: var(--bg-raised);
  color: var(--dim);
  font-family: var(--mono);
  text-align: center;
  text-decoration: none;
}

/* Only a pressable one lights up. A book whose file is missing, or one being
   read by somebody who is not the owner, keeps the same box without the
   affordance -- so it still looks like a book rather than like a different kind
   of thing, and the caption underneath says which of the three it is. */
a.book-slab:hover,
a.book-slab:focus-visible,
.book-cover a:hover .book-picture,
.book-cover a:focus-visible .book-picture { border-color: var(--act-edit); }

a.book-slab:hover { background: var(--bg); text-decoration: none; }

.book-title { color: var(--fg); font-size: 0.85rem; overflow-wrap: anywhere; }
.book-pages { font-size: 0.8rem; color: var(--dim); }

/* The control, and it is only ever in the slab. A rendered cover is the control
   itself -- the border lights on hover and the cursor changes -- so it needs no
   words, while a slab without them is a grey box with nothing to suggest it
   does anything. */
.book-act { color: var(--act-edit); font-size: 0.8rem; }

.book-cover figcaption { max-width: 9rem; }
.book-cover figcaption .note { margin: 0.5rem 0 0; font-size: 0.8rem; }

/* Finding one book of sixty-six. One line: a label, a box and the way out.
   `.tree-controls` sits under it carrying the traditions, so this stays as
   quiet as it can while still being the first thing on the page you can act
   on. */
/* The find box, and the shelf's counts at the far end of the same line.
   They used to be the tail of the note above the card, which held them to the
   46rem prose measure on a page whose listing runs the full 76rem -- so the
   line clipped at "1 written ab…" and put the filing link behind the expand.
   The measure was widened for it; this removes the reason to.

   `margin-left: auto` on the counts rather than `space-between` on the row, so
   that a narrow screen which wraps them onto their own line still ends them at
   the right edge rather than starting them at the left. */
.shelf-bar {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 0.3rem 1.5rem;
  margin: 0.2rem 0 0.9rem;
}
/* The form gives up its own margin to the row, and takes the space the counts
   do not want. The basis is what decides when they stop sharing a line. */
.shelf-bar .shelf-find { flex: 1 1 18rem; margin: 0; }
.shelf-counts {
  margin: 0;
  margin-left: auto;
  text-align: right;
  color: var(--faint);
  font-size: 0.78rem;
}
/* The figure is what the row is reporting and the noun beside it is the label:
   `ul.listing .meta` draws the same split, and this is the same claim. */
.shelf-counts .n { color: var(--fg); }

.shelf-find {
  display: flex;
  align-items: baseline;
  gap: 0.6rem;
  margin: 0.2rem 0 0.9rem;
}
.shelf-find label { color: var(--dim); font-size: 0.78rem; }
.shelf-find input {
  font: inherit;
  font-size: 0.85rem;
  color: var(--fg);
  background: var(--bg-raised);
  border: 1px solid var(--edge);
  border-radius: 2px;
  padding: 0.2rem 0.5rem;
  min-width: 0;
  flex: 1;
  max-width: 22rem;
  outline: none;
}
/* Green is what focus wears everywhere here -- the search box and the editor
   both take it. */
.shelf-find input:focus { border-color: var(--green); }
.shelf-clear { font-size: 0.78rem; color: var(--faint); }
.shelf-clear:hover { color: var(--fg); }

.book-prose { min-width: 0; }

/* The strip naming the four buffers, which is the window's head rather than a
   bar above it -- `.pane-head.is-tabbed`, the arrangement the song page's
   right-hand window already has.

   The one thing it cannot inherit is where it sticks. `.pane-head` pins at
   `top: 0` because on that page it sits inside a `.pane-body` that scrolls, so
   zero is the top of its own window. Here the column does not scroll on its
   own, so zero is the top of the viewport -- and the site header is sticky
   there at z-index 5, which would slide this underneath it and leave the tabs
   invisible the moment the page moved.

   `--sticky-top` is what everything else on this page clears the header with,
   and it already widens on a narrow screen to account for the song-tabs bar
   (`.split.is-tabbed` sets it), so the strip follows a long notes buffer down
   without being told about either. */
.pane-left > .pane-head.is-tabbed { top: var(--sticky-top); }

/* Which buffer you are in.
   The text going to --fg was the whole of the mark, and one step of brightness
   is a thin thing to carry "you are here" -- which this app says in green
   everywhere else it says it: the prompt, the crumb you are standing on, the
   edge under the active nav item. (That one was a `▍` block before the word
   when this was written; it is a bottom edge now, drawn like this strip.) A tab strip says it with its bottom edge,
   exactly as `.song-tab.is-current` already does on a narrow screen.

   **The song window deliberately does the opposite**, and that is worth saying
   rather than looking like an oversight: `.pane-tab.is-current` makes its
   bottom edge *transparent*, so the tab and the buffer under it read as one
   shape. That pays there, where the buffer is the next thing down inside the
   same window. It does not pay here -- the strip is followed by the cover
   grid, and the panel is a bordered `section.card` with a margin of its own,
   so an open edge joins the tab to a gap rather than to its buffer. The marker
   earns the place the seam cannot.

   The band is `color-mix()` off the same token rather than a second green
   written by hand, which is the rule wherever a tint is drawn here. The
   `:hover` is named because `.pane-tab.is-current:hover` sets `background:
   none` -- this out-specifies it either way, but a rule that only works by
   out-specifying another is one raise away from silently losing. */
.pane-left > .pane-head.is-tabbed .pane-tab { border-bottom-width: 2px; }

.pane-left > .pane-head.is-tabbed .pane-tab.is-current,
.pane-left > .pane-head.is-tabbed .pane-tab.is-current:hover {
  border-bottom-color: var(--green);
  background: color-mix(in srgb, var(--green) 8%, transparent);
}

/* Facts about the book, each of them either read off the file or typed by hand.
   Nothing here is inferred. */
.book-meta {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: 0.15rem 0.9rem;
  margin: 0 0 1rem;
  font-size: 0.9rem;
}
.book-meta dt { color: var(--faint); }
.book-meta dd { margin: 0; color: var(--fg); }

/* Filed under, which is an assertion rather than a count -- so it is stated in
   the same weight as the rest of the page rather than dimmed like a derived
   figure. */
.book-filings { margin: 0 0 1.25rem; }
/* Filed under, which is an assertion rather than a count -- so it is stated in
   the same weight as the rest of the page rather than dimmed like a derived
   figure. It is not a .note: that prefixes "# " and pitches the line as a
   comment, and this is chad saying what the book is. */
.book-filed { margin: 0.2rem 0 0.7rem; }

/* Both forms are always open now. They sat behind disclosures holding three
   fields and a checkbox list -- nothing worth a press -- and a control you have
   to open before you can tell it is there is a control that is hidden. The
   sections naming them are enough, and `section.card > h3` already labels a
   group inside a card.

   **Those two summaries were the only --act-edit on those buffers**, and losing
   the warmth with them is a real loss rather than a tidy-up. So it moves to
   where the palette says it belongs rather than to the tab strip: --act-edit is
   "acting on the page rather than moving around it", and the thing that acts
   here is the button -- the editor's `wq` wears it for exactly that reason. A
   tab is *moving around* the page, so the strip keeps its green, which already
   means "where you are", which is what a current tab is. */
/* The forms lost their headings -- "describe it" over three fields named
   author, published and publisher, and "add traditions" over a list of
   traditions, were each the third thing saying what the section already said.
   What the heading was also doing, unnoticed, was holding the form off the
   line above it: `.mkdir-form` has no margin of its own and `.pane-echo` has
   no bottom one, so without this they meet.

   Sibling margins collapse, so this is a floor rather than an addition -- the
   1rem under `.book-meta` still wins where it is larger. */
.book-prose .mkdir-form { margin-top: 0.9rem; }

.book-prose .mkdir-form button { color: var(--act-edit); }
.book-prose .mkdir-form button:hover { border-color: var(--act-edit); }
.filing-choices { list-style: none; padding: 0; margin: 0.6rem 0; }
.filing-choices li { margin: 0.2rem 0; }
.filing-choices label { display: inline-flex; gap: 0.5rem; align-items: baseline; }


/* --- what a catalogue suggested --------------------------------------------
   Every proposal says where it came from and links to the record, because that
   is the whole difference between a catalogue's answer and a guess. The
   rationale is dim and the value is not: what is being offered should read
   first, and the provenance second. */
.book-proposals { margin: 0 0 1.25rem; }
.book-proposals h3 { font-size: 0.85rem; color: var(--faint); margin: 0 0 0.4rem; font-weight: 400; }
.book-proposals ul { list-style: none; padding: 0; margin: 0 0 0.6rem; }

/* Three different kinds of thing on one row, and they were not telling
   themselves apart: the field being proposed for and the sentence explaining
   the match were both --faint, so a label and a gloss read as one another.

   The palette already had the answer. --dim is documented as the field-label
   tier, and --comment is the grey kept for a gloss -- a line opening with "#"
   and nothing else. So they now differ by hue as well as by size, which is the
   difference that survives being glanced at. */
.proposal-what {
  color: var(--dim);
  font-size: 0.8rem;
  font-family: var(--mono);
}
.proposal-value { color: var(--fg); }

/* Where the suggestions came from, above the values rather than riding on each
   of them. One line per provenance, so three proposals out of one lookup say
   it once between them. */
.book-proposals .proposal-sources { margin: 0 0 0.9rem; }
.book-proposals .proposal-sources li {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  align-items: baseline;
  padding: 0.1rem 0;
}

/* Which of them said so. A label rather than a gloss, so it takes the label
   tier and not the comment grey -- and on a value row it is the only thing
   telling two answers to one field apart. */
.proposal-source {
  color: var(--dim);
  font-size: 0.78rem;
  font-family: var(--mono);
}
.proposal-why { color: var(--comment); font-size: 0.78rem; }

/* A grid rather than a wrapping flex row, so the control lands in the same
   place on every row. `margin-left: auto` put it at the right of whatever line
   it happened to end up on, and the rationale is long enough -- `openlibrary
   matched "..." (1995), 1 result(s)` -- that where it broke decided where the
   button went. The middle column takes whatever is left and the press is
   always at the far right. */
.book-proposals .proposal-field li {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  gap: 0.5rem;
  align-items: baseline;
  margin: 0.25rem 0;
}
/* The last column explicitly, not "wherever auto-placement puts it". A row
   carries two children or three depending on whether there is more than one
   source to name, and landing in the second column left the control a gap's
   width -- 8px -- shy of the edge on the two-child rows. */
.proposal-actions { grid-column: -2 / -1; justify-self: end; }
/* It is a gloss in a column of controls, so it keeps the voice and drops the
   block spacing `.note` carries for standing on its own. */
.proposal-actions .note { margin: 0; font-size: 0.78rem; }

/* Under this the three columns have no room to be three. The control goes to
   its own line rather than squeezing the reading it is about. */
@media (max-width: 42rem) {
  .book-proposals .proposal-field li { grid-template-columns: auto minmax(0, 1fr); }
  .proposal-actions { grid-column: 1 / -1; justify-self: start; }
}

/* An accepted one stays on the page rather than disappearing: it is how the
   page says where the value came from, which is worth more than the tidiness
   of hiding it. */
.book-proposals .proposal-field li.is-chosen .proposal-value { color: var(--green); }

/* --- filing the shelf -------------------------------------------------------
   Sixty-six books, each with a row of traditions to tick. A matrix with the
   traditions as columns would scan faster, but at fifteen of them it needs
   rotated headers and a horizontal scroll, and this page is used once in anger
   and then rarely -- so the rows read plainly instead. */
.filing-row {
  display: grid;
  grid-template-columns: minmax(0, 18rem) minmax(0, 1fr);
  gap: 0.5rem 1.25rem;
  align-items: baseline;
  padding: 0.5rem 0;
  border-bottom: 1px solid var(--rule);
}

.filing-book { min-width: 0; overflow-wrap: anywhere; }
.filing-book .meta { color: var(--faint); font-size: 0.8rem; }

/* Which of the two a row is showing, said on the row. "yours" is a filing that
   already existed; "suggested" is a proposal that will become one if it is
   left ticked -- and the difference is the whole reason this page says
   anything at all rather than just drawing boxes. */
.filing-yours { color: var(--dim); }
.filing-suggested { color: var(--act-edit); }

.filing-choices {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 0.15rem 0.9rem;
}
.filing-choices li { margin: 0; }
/* The tradition's own name, so --fg rather than the label tier it was
   inheriting from `.mkdir-form label`. That tier is for a field caption --
   "author", "published" -- and these are not captions, they are the values.

   It also opens the gap to the suggestions below. Both sat mid-lightness
   before (--dim at 10.6 against --comment at 5.8), so the only thing telling
   them apart was hue, which at 0.85rem in italics is nearly nothing. Against
   --fg at 16.9 the difference is one you cannot miss. */
.filing-choices label {
  display: inline-flex;
  gap: 0.35rem;
  align-items: baseline;
  font-size: 0.85rem;
  color: var(--fg);
}
/* A tradition the glossary has not got yet, so it is visibly not one of the
   eight until something is left under it. */
/* A tradition the glossary has not got yet: suggested for this book, and real
   only if it is left ticked.

   Grey rather than the teal label tier, because it is not a label -- it is the
   page saying something *about* the choice beside it, which is what --comment
   is for and the only thing left wearing grey here. In the teal it read as one
   more tradition that happened to be in italics. */
.filing-choices label.is-new { color: var(--comment); font-style: italic; }

.filing-save { position: sticky; bottom: 0; padding: 1rem 0; background: var(--bg); }

@media (max-width: 52rem) {
  .filing-row { grid-template-columns: minmax(0, 1fr); }
}

/* --- a phone ---------------------------------------------------------------
   Last in the file on purpose, and this is not tidiness. `.tree-head` and
   `.listing-head` declare their grids further up but *after* where this block
   used to sit, so `display: none` here and `display: grid` there were the same
   specificity and the later one won -- the block was correct, ran, and changed
   nothing that mattered. Anything that overrides a layout for a narrow screen
   belongs after every layout it overrides. */

/* The first thing in this app that can genuinely run out of sideways room.
   Below this a row stops pretending to be a table: the name takes a line of its
   own and the counts wrap underneath it. Nothing is hidden -- a column dropped
   on a narrow screen is a fact the page has and will not say. */
@media (max-width: 40rem) {
  ul.listing.is-long[class*="cols-"] li,
  .tree-album > summary,
  /* `.tradition` on the front of every terms-tree selector below, because
     that is the specificity the rule being overridden carries -- the glossary
     scopes its grid that way so the editor's pane does not get it. A bare
     .tree-term here lost to it and the tree stayed a table. */
  .tradition .tree-term > summary {
    display: flex;
    flex-wrap: wrap;
    column-gap: 0.75rem;
  }
  ul.listing.is-long[class*="cols-"] li > a { flex: 1 1 100%; order: -1; }
  ul.listing.is-long .col,
  .tree-album > summary .col,
  .tradition .tree-term > summary .col { text-align: left; }
  .tree-album > summary .tree-name,
  .tradition .tree-term > summary .term-name { flex: 1 1 100%; order: -1; margin-left: 0; }

  /* The meaning goes under the row rather than beside it. It is clipped with an
     ellipsis where it does not fit, and a third of a phone's width of a
     definition is not a definition. */
  .tradition .tree-term > summary .term-gloss { flex: 1 1 100%; order: 1; white-space: normal; }

  /* The header names columns the rows above have stopped having, and it is the
     one thing on these pages that genuinely cannot wrap: six fixed widths
     adding up to wider than a phone, which is where most of the overflow was.
     It goes -- and the labels it was carrying travel on the cells instead, so
     the rule above still holds and no column is dropped, only the row that
     said each one's name once. */
  .listing-head, .tree-head, .tradition-head { display: none; }
  .col[data-label]::after { content: " " attr(data-label); color: var(--comment); }

  /* Two more rows that hold something out to the right with margin-left: auto.
     Both are sentences rather than counts -- a citation, and what the editor
     does not edit -- so neither shortens to fit and both take a line. */
  .uncertain-row { flex-wrap: wrap; }
  .uncertain-row .cite { flex: 1 1 100%; margin-left: 0; }
  /* This one is a .pane-status, which is nowrap with an ellipsis on purpose --
     a status line is one line. The editor's ruler is the exception: it is
     `position: static` rather than pinned, and what it holds out to the right
     is a sentence, so on a phone it takes a second line instead of losing its
     last four words. */
  .editor-ruler { flex-wrap: wrap; white-space: normal; }
  .editor-bar { flex-wrap: wrap; }
  .editor-scope { flex: 1 1 100%; margin-left: 0; }

  /* A label beside its field costs a field the room to be typed in. The pane
     beside the lyric worked this out at 62rem and the same three lines say it
     here, for the page where the form has the width to itself. */
  .reading-form .field { display: block; }
  .reading-form label { display: block; margin-bottom: 0.15rem; min-width: 0; }
  .reading-form input[type="text"] { width: 100%; }

  /* The glossary filters, which are eight words a thumb has to hit. Scoped to
     `.is-filters` and never to `.tree-controls`, which is also the sort bar on
     the uncertainties and concordance pages -- the file says so twice already.

     Three things, and the first is the one that matters: the targets were 22px
     tall, which is half what a thumb wants and the tab strip above already
     gives it. A word is not a smaller thing to press than a tab.

     The separators go. They are punctuation for a run of words on one line,
     and this run is two or three lines on a phone -- a dot then hangs at the
     end of a line separating nothing from nothing. The column gap says the
     same thing and says it in every direction the row wraps.

     The label stays on the line rather than taking one of its own. It reads as
     a prefix either way, and this row is above the album list on the page you
     land on -- a line of heading here is a line of records you have to scroll
     past to reach. */
  .tree-controls.is-filters { gap: 0.1rem 0.9rem; }
  .tree-controls.is-filters .sep { display: none; }
  .tree-controls.is-filters .filter-label { margin-right: 0; align-self: center; }
  .tree-controls.is-filters a {
    display: inline-flex;
    align-items: center;
    min-height: 2.25rem;
  }
}

/* A spelling the corpus has that a term does not. The phrase wears `.fuzzy`,
   which is --amber, for the reason amber means everywhere here: this is a
   guess. The control beside it is --act-edit, because following it is acting
   on the term rather than moving around the page -- and it is the share
   button's colour for the share button's reason. */
.suggest-alias {
  margin-left: auto;
  font-size: 0.78rem;
  color: var(--act-edit);
}
.suggest-alias:hover { color: var(--fg); }

/* The other answer to a suggestion. Quiet -- `--faint` until the pointer is on
   it -- for the reason the card's remove control is: saying no to a guess
   should not be the loudest thing in the row. A button rather than a link
   because it writes a row, and the form is inline so it sits in the band
   rather than breaking it. */
.spelling-form { display: inline; }
.spelling-no {
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  font-size: 0.78rem;
  color: var(--faint);
  cursor: pointer;
}
.spelling-no:hover { color: var(--act-edit); }
.dismissed-note { display: flex; gap: 0.6rem; align-items: baseline; flex-wrap: wrap; }

/* The line, or lines, the address is pointing at. `#L49` scrolls by itself and
   said nothing; `#L49-L51` is a range and the browser cannot see it at all --
   `:target` styles exactly one element by definition. See lines.js.

   Green, because green means where you are, and a linked line is where the
   address says you are. A band rather than a block: the words have to stay
   easier to read than the highlight. It is deliberately not `.is-focus`, which
   the workbench uses for the line being worked on -- two states that meant
   different things and looked the same would be worse than either. */
.line.is-linked { background: color-mix(in srgb, var(--green) 10%, transparent); }
.line.is-linked .line-number { color: var(--green); }

/* A fragment link puts its target at the very top of the viewport, and the top
   of the viewport is behind the header. Nothing said so until a line had an
   address worth arriving at: `#L16` landed the line under the bar, which reads
   as the page having scrolled too far. One declaration, every page with a
   gutter -- a song, a card, a book's notes, a shared lyric. */
.line { scroll-margin-top: calc(var(--header-h) + 0.75rem); }
