/* 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;

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

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

  --green:     #7ee787;   /* prompt, wordmark */
  --cyan:      #79c0ff;   /* links */
  --amber:     #d9a441;   /* uncertainty, fuzzy matches */
  --mauve:     #b48ead;   /* glossary terms */
  --comment:   #4f5b6b;   /* # headings, which read as shell comments */

  /* 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. This is a muted orange next to that, and
     what really separates them is that this one is a bordered box and a count
     is bare text. */
  --act-edit:  #b5934f;

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

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

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

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

/* --- 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 {
  max-width: var(--measure);
  margin: 0 auto;
  padding: 0.55rem 1.25rem;
  display: flex;
  align-items: center;
  gap: 1rem;
  flex-wrap: wrap;
}

/* Green means one thing: where you are. The wordmark stays heavier than
   anything else in the bar so it still reads as the app while it is grey. */
.wordmark { color: var(--dim); font-weight: 800; }
.wordmark:hover { color: var(--fg); text-decoration: none; }
.wordmark.is-here { color: var(--green); }

/* One bar, and it moves. The marker is drawn beside both items but coloured on
   only the active one -- if it were added and removed instead, the header would
   shift sideways every time the section changed. */
header.site .wordmark::before,
header.site nav a::before {
  content: "▍";
  color: transparent;
  margin-right: 0.35rem;
}
header.site .is-here::before { color: var(--green); }

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

/* 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;
  align-items: baseline;
  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 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%;
}
input[type="search"]::placeholder { color: var(--faint); }
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. */
@media (max-width: 46rem) {
  .bar { gap: 0.75rem 1rem; padding-left: 0.85rem; padding-right: 0.85rem; }
  header.site nav { display: flex; 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; }
  :root { --header-h: 5.6rem; }
}

/* 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. */
@media (max-width: 30rem) {
  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); }

h2 {
  font-size: 0.85rem;
  font-weight: 600;
  margin: 2rem 0 0.5rem;
  color: var(--dim);
}

/* 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(--faint);
}

.path, .crumbs, code {
  font-size: 0.8rem;
  color: var(--dim);
}
.crumbs { margin-top: 0.5rem; }
.crumbs a { color: var(--dim); }
.crumbs a:hover { color: var(--cyan); }
.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: "# "; }
.count { color: var(--faint); 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(--faint);
  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(--faint); }
.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(--faint);
  /* 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(--faint);
  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(--faint); }

/* --- 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(--faint); }
  .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(--dim); 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(--dim);
  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. */
a.term-link {
  color: var(--mauve);
  text-decoration: none;
  border-bottom: 1px dotted var(--mauve);
}
a.term-link:hover {
  border-bottom-style: solid;
  background: rgba(180, 142, 173, 0.13);
  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, a.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. */
a.term-link.is-uncertain { border-bottom-color: var(--mauve); }

.uncertain:hover { background: rgba(217, 164, 65, 0.12); }

/* --- 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(--faint); }

.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(--faint); }
.context-line .line-text { color: var(--dim); }

/* --- 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. */
.term-controls {
  display: flex;
  gap: 1.2rem;
  font-size: 0.8rem;
  margin: 0 0 1.5rem;
}
.term-controls a { color: var(--act-edit); }
.term-controls a:hover { color: var(--fg); text-decoration: none; }

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

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

/* --- 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); }
ul.listing .meta { margin-left: auto; color: var(--faint); font-size: 0.78rem; }

/* 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); }

/* 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 new is invented: 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); }

/* 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-linked,
.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 bar holds the cursor from the moment a page loads, so it reads as the
   live prompt rather than a field you have to go and find. */
.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(--faint); }

/* --- 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;
}
.palette-prompt input::placeholder { color: var(--faint); }

/* 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 {
  grid-template-columns: minmax(0, 18rem) minmax(0, 1fr) minmax(0, 18rem);
  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. */
@media (max-width: 78rem) {
  .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);
}

/* 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(--faint); }
  .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; }

  /* 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(--faint); font-size: 0.75rem; }
.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(--faint); }

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

/* 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. */
.tree-make {
  margin-left: auto;
  margin-right: 0.6rem;
  color: var(--act-edit);
}
.tree-controls a.tree-make:hover { color: var(--fg); text-decoration: none; }
/* 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-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 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 -- seven 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. */
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); }

/* 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(--dim);
  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;
}
section.card > h2 {
  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(--dim);
  font-size: 0.78rem;
}
section.card > h3 {
  margin: 1rem 0 0.3rem;
  font-size: 0.75rem;
  font-weight: 400;
  color: var(--dim);
}
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(--faint);
  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; }

/* Faint, so what the box is asking for never reads as something already in it. */
.reading-form input::placeholder,
.recording-form input::placeholder,
.mkdir-form input::placeholder,
.mkdir-form textarea::placeholder { color: var(--faint); }

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

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(--faint); }
button.is-primary { color: var(--green); border-color: var(--faint); }
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; }
ul.concordance li.is-unknown .meta { color: var(--comment); }

/* --- 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(--faint);
  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: rgba(126, 231, 135, 0.22);
}

/* 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 same marks the lyric carries. Written out here rather than by dropping
   the `a` from the rule above, which would be the same declaration in one
   place instead of two -- but would also newly mark terms on the search hits,
   the term page and the concordance, three pages that pass linked: false and
   are unmarked today. That is a change worth making on purpose, in its own
   commit, and not as a side effect of the editor. It is in TODO.md. */
.editor-mirror .term-link {
  color: var(--mauve);
  border-bottom: 1px dotted var(--mauve);
}
.editor-mirror .term-link.is-uncertain {
  text-decoration: underline wavy var(--amber);
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
}

.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-bar input::placeholder { color: var(--faint); }
.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(--faint); }
.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(--faint); }

/* 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; }
.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(--faint); 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); }

/* 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(--faint);
}
.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. */

/* No cover images: a pdf has no thumbnail without rendering one, and rendering
   pages is exactly what this app does not do. So the slab says what the file is
   and how long it is, and the link below opens it. */
.book-slab {
  width: 9rem;
  height: 12rem;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.4rem;
  border: 1px solid var(--rule);
  border-radius: 2px;
  background: var(--bg-raised);
  color: var(--faint);
  font-family: var(--mono);
}

.book-ext { font-size: 1.4rem; letter-spacing: 0.1em; }
.book-pages { font-size: 0.8rem; color: var(--dim); }

.book-open {
  display: inline-block;
  margin-top: 0.6rem;
  color: var(--act-edit);
}

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

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

/* 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; }
.book-refile summary { color: var(--act-edit); cursor: pointer; font-family: var(--mono); }
.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; }


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