/* notes-md.
 *
 * One hand-written stylesheet, no framework and no build step, the way congo
 * and tracker each carry one. The palette is theirs, so the three apps read as
 * siblings: a dark terminal ground, one accent per kind of thing, and a
 * monospace face throughout because every page here is a file.
 *
 * Dark only, deliberately -- `color-scheme: dark` is declared and there is no
 * light variant to keep in step.
 */

:root {
  --bg:        #0b0e14;
  --bg-raised: #121721;
  --bg-sunken: #080a10;
  --line:      #1e2634;

  --fg:        #c7d0e0;

  /* The note's own text, as distinct from the app's chrome.
   *
   * --fg is a cool grey-blue and reads well for crumbs, counts and row names.
   * Against it, a line of prose looked muted -- which is the wrong emphasis
   * when the note is the entire point of the page. This is near-white and is
   * used for exactly two things: the rendered body, and the editor's text.
   *
   * Nearly every line in this corpus is a `- ` bullet, so "white for the lines
   * with a dash" and "white for the note" are the same request; doing it as a
   * content colour keeps the editor and the rendered page agreeing. */
  --ink:       #f0f3f8;
  --fg-soft:   #8b97ab;
  /* Lifted from #6b7688, which measured 4.21:1 on --bg and 3.91:1 on
   * --bg-raised -- under AA, and used for the things most worth being able to
   * read at a glance: timestamps, counts, and the matching line under a grep
   * hit. test/lib/palette_test.rb is what noticed. */
  --fg-faint:  #7a8598;

  /* One accent per kind of thing, as in congo: green is where you are, cyan is
   * a link out, mauve is a heading, amber is something that needs looking at. */
  --green:  #8ec07c;
  --cyan:   #7fc1d6;
  --mauve:  #c29ce0;
  --amber:  #e0b060;
  --red:    #e07a70;

  /* The list marker. Nearly every line in this corpus is a `- ` bullet under a
   * `# heading`, so this is the colour you see most of -- which is why it is
   * the marker that is coloured and not the text beside it. vim-markdown does
   * the same, and a page of fully yellow bullets would be unreadable.
   *
   * The pale yellow chad's vim paints note text with, sampled out of a
   * screenshot of it: #ffffaf, 18.6:1 on --bg.
   *
   * It colours the **whole bullet line**, marker and words alike, which is not
   * a compromise so much as the only thing reachable -- lezer-markdown tags a
   * list item's content and its `-` separately, and the `-` shares its tag
   * with the heading `#`, so the two cannot be told apart from a highlight
   * style. See note_editor.js. Since almost every line in this corpus is a
   * bullet, this is effectively the note's colour and the headings are what
   * stand out against it.
   *
   * Tried and rejected, with the numbers that killed them:
   *   #d9a96a  muted orange -- 1.07:1 from --amber, the same colour to the eye
   *                           as the dated headings and the conflict warning
   *   #8fb0d0  slate blue   -- 1.02 from --mauve, 1.13 from --cyan
   *   #bd6b93  plum         -- liked, but read as dim against the headings
   *   #f0f3f8  near-white   -- correct and dull
   *
   * Tried and rejected: muted orange #d9a96a, which measured 1.07:1 against
   * --amber -- the same colour, to the eye, as the `## m/d/yy` headings and
   * the conflict warning. And slate blue #8fb0d0, which was worse: 1.02
   * against --mauve (the `##` headings) and 1.13 against --cyan (every one of
   * the 448 bare urls in this corpus).
   *
   * One token: change it here and the editor follows. */
  --list:   #ffffaf;

  /* The caret, sampled from chad's vim: pure #ffff00, for the block in normal
   * mode and the bar in insert. It is not text, so it is not held to the
   * contrast floor the ink colours are -- but at 19.4:1 on --bg it clears it
   * anyway, which is the point of a cursor. */
  --caret:  #ffff00;
  /* The character sitting under the block cursor. Black rather than --bg,
   * because the point is maximum legibility against the caret for the one
   * glyph you are looking straight at. */
  --caret-ink: #000000;

  /* The line the cursor is on. `set cursorline` in .vimrc.
   *
   * **#1c222e rather than the #1c1c1c chad's vim shows.** That value is
   * ctermbg=234 out of black_is_the_color -- the terminal approximation, not
   * the `guibg` in the colorscheme file -- and it is a lift of +0.0116
   * luminance from pure black. This ground is #0b0e14 rather than black and
   * already carries some of that, so pasting the same swatch on would be a
   * fainter line than he has. #1c222e reproduces the lift instead of the
   * colour, and keeps the blue the rest of the palette is built on. */
  --cursorline: #1c222e;

  /* A visual selection.
   *
   * **#364178, not the #262D51 chad's vim uses for Visual.** That one measures
   * 1.20:1 against --cursorline -- the same brightness, near enough -- so a
   * selection on the line the cursor is on simply disappeared, which is how
   * this got noticed. It works in his vim because there is no cursorline
   * underneath it there; here there is.
   *
   * First try was #364178 at 1.66:1 against the cursorline, which cleared the
   * arithmetic and was still too faint to notice -- `vw` genuinely selected a
   * word and looked like it had done nothing. A background highlight needs
   * more separation than a contrast floor implies: #4a5bb0 is 3.14:1 against
   * the page and 2.59:1 against the cursorline, with the text on it at
   * 5.53:1. */
  --selection: #4a5bb0;

  --mono: ui-monospace, "JetBrains Mono", "DejaVu Sans Mono", SFMono-Regular, Menlo, monospace;

  /* **The buffer is bigger than the chrome.** The app's 0.875rem is right for
   * breadcrumbs, lists and the palette -- dense, glanced at. The note itself
   * is read and written for minutes at a time, and it was set at the same size
   * as its own navigation.
   *
   * One token, because two editors have to agree on it: CodeMirror's theme
   * sets its own font size in javascript, and the no-javascript textarea sets
   * it here. The rendered view is chrome-sized on purpose -- it is scanned,
   * not written in. */
  --editor-size: 1rem;

  --bar: 2.75rem;
}

* { box-sizing: border-box; }

html {
  color-scheme: dark;
  /* Safari inflates monospace in landscape without this. */
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--fg);
  font: 0.875rem/1.55 var(--mono);
}

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

/* --- the header ------------------------------------------------------- */

header.site {
  position: sticky;
  top: 0;
  z-index: 5;
  background: var(--bg-raised);
  border-bottom: 1px solid var(--line);
}

.bar {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  height: var(--bar);
  padding: 0 0.75rem;
  max-width: 78rem;
  margin: 0 auto;
}

.wordmark { color: var(--green); font-weight: 600; }
.wordmark .short { display: none; }

.search {
  flex: 1;
  display: flex;
  align-items: center;
  gap: 0.35rem;
  background: var(--bg-sunken);
  border: 1px solid var(--line);
  border-radius: 3px;
  padding: 0 0.5rem;
  /* The completion menu hangs off this. */
  position: relative;
}

/* --- what tab is offering ---------------------------------------------- *
 *
 * zsh's menu rather than bash's listing: a tab that cannot fill anything in
 * shows these and each further tab takes the next. See command_bar.js.
 */
.bar-menu {
  position: absolute;
  top: calc(100% + 3px);
  left: -1px;
  right: -1px;
  z-index: 10;
  max-height: 16rem;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  padding: 0.2rem 0;
  background: var(--bg-raised);
  border: 1px solid var(--line);
  border-radius: 3px;
  box-shadow: 0 6px 18px rgb(0 0 0 / 45%);
}

.bar-menu-item {
  display: block;
  width: 100%;
  text-align: left;
  background: none;
  border: 0;
  padding: 0.25rem 0.6rem;
  color: var(--fg-soft);
  font: inherit;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  cursor: pointer;
}
.bar-menu-item:hover { color: var(--ink); }

/* The one tab has landed on, which is also the one now in the field. */
.bar-menu-item.is-here {
  background: var(--line);
  color: var(--ink);
  box-shadow: inset 2px 0 0 var(--green);
}

.bar-menu-rest {
  padding: 0.25rem 0.6rem;
  color: var(--fg-faint);
  font-size: 0.8125rem;
  border-top: 1px solid var(--line);
  margin-top: 0.2rem;
}

.search .prompt { color: var(--green); }

.search input {
  flex: 1;
  background: none;
  border: 0;
  color: var(--fg);
  font: inherit;
  padding: 0.35rem 0;
  /* 16px minimum, or iOS zooms the page on focus and never zooms back. Not
   * user-scalable=no, which breaks pinch zoom for everyone. */
  font-size: max(1rem, 0.875rem);
}
.search input:focus { outline: none; }
.search:focus-within { border-color: var(--green); }

main {
  max-width: 78rem;
  margin: 0 auto;
  padding: 1rem 0.75rem 4rem;
}

/* --- crumbs ----------------------------------------------------------- */

.crumbs {
  color: var(--fg-faint);
  margin-bottom: 0.75rem;
  overflow-x: auto;
  white-space: nowrap;
}
.crumbs a { color: var(--fg-soft); }
.crumbs a:last-child { color: var(--green); }
.crumbs .sep { padding: 0 0.3rem; color: var(--line); }

/* --- the listing ------------------------------------------------------ */

.listing {
  border: 1px solid var(--line);
  border-radius: 3px;
  overflow: hidden;
}

.row {
  display: grid;
  /* name · title · lines · size or count · when. The number columns are fixed
     and right-aligned so they line up down the page whether the row above was
     a directory or a note. */
  grid-template-columns: minmax(8rem, 18rem) 1fr 3rem 3.5rem 4rem;
  gap: 0.75rem;
  align-items: baseline;
  padding: 0.4rem 0.75rem;
  border-bottom: 1px solid var(--line);
  color: var(--fg);
}
.row:last-child { border-bottom: 0; }
.row:hover { background: var(--bg-raised); text-decoration: none; }

.row.is-dir .name { color: var(--root-ink, var(--green)); }

/* **The cursor row**, for walking the tree with j/k. The same inset bar the
   palette's rows use, in the same family colour, so a row means the same thing
   on the page as it does in the overlay. */
.row.is-here {
  background: var(--cursorline);
  box-shadow: inset 2px 0 0 var(--root-ink, var(--green));
}
.row.is-here .name { color: var(--ink); }

/* A directory row is a control, not a link to read. `pre` keeps the ▸ from
   collapsing against the name. */
.row.is-dir { cursor: pointer; }
.row.is-dir .name { white-space: pre; }
.row .title { color: var(--fg-faint); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* `wc -l`, so the unit is legible without a header row the listing does not
   have. */
.row .lines:not(:empty)::after { content: "L"; opacity: 0.55; }
.row .count,
.row .lines,
.row .size,
.row .when {
  color: var(--fg-faint);
  font-size: 0.8125rem;
  text-align: right;
  font-variant-numeric: tabular-nums;
}

/* --- a row's own actions ----------------------------------------------- *
 *
 * `m` and `d` are the fast way to rename and remove, and a phone has neither.
 * So every row carries a `⋯` -- a character, like the `▸` beside it, which is
 * the house idiom -- and behind it a menu that spells its actions out as
 * words. One mark rather than two icons: it takes one column instead of two on
 * the screen with the least to spare, and "remove" gets to be a word rather
 * than an x, which is the argument in shared/_icon for why `rm` is not a bin.
 *
 * The wrapper exists so the anchor stays an anchor and the button stays a
 * button. A span pretending to be a button inside a link would have been
 * fewer lines and worse: middle-click and ctrl-click on a row are why the
 * href is real in the first place.
 */
.row-line { position: relative; }
.row-line:last-child .row { border-bottom: 0; }
/* Room for the mark, so a long filename truncates against it rather than
   running underneath it. */
.row { padding-right: 2.25rem; }

.row-more {
  position: absolute;
  top: 0;
  right: 0;
  height: 100%;
  width: 2.25rem;
  display: flex;
  align-items: center;
  justify-content: center;
  background: none;
  border: 0;
  padding: 0;
  color: var(--fg-faint);
  font: inherit;
  line-height: 1;
  cursor: pointer;
}
.row-more:hover { color: var(--ink); }

/* **Always there where there is no pointer.** A control that appears on hover
   does not exist on a phone, which is the device this was built for. Where
   there *is* a pointer the listing stays quiet: the mark comes up for the row
   under the cursor, the row the keyboard is on, and itself when tabbed to. */
@media (hover: hover) {
  .row-more { opacity: 0; transition: opacity 0.1s; }
  .row-line:hover .row-more,
  .row-line:has(.row.is-here) .row-more,
  .row-more:focus-visible { opacity: 1; }
}

/* Fixed and on the body: `.listing` is `overflow: hidden` so its rows stay
   inside its rounded corners, which clipped a menu hung inside it to one
   visible item. nerd_tree.js does the arithmetic. */
.row-menu {
  position: fixed;
  z-index: 20;
  min-width: 9rem;
  display: flex;
  flex-direction: column;
  padding: 0.2rem 0;
  background: var(--bg-raised);
  border: 1px solid var(--line);
  border-radius: 3px;
  box-shadow: 0 6px 18px rgb(0 0 0 / 45%);
}
.row-menu-item {
  display: block;
  width: 100%;
  text-align: left;
  background: none;
  border: 0;
  padding: 0.4rem 0.75rem;
  color: var(--fg);
  font: inherit;
  cursor: pointer;
}
.row-menu-item:hover,
.row-menu-item:focus-visible {
  background: var(--line);
  color: var(--ink);
  text-decoration: none;
  outline: none;
}
/* The one that cannot be undone by doing it again. It still goes to a page
   that asks; this is so the eye knows which one it is before tapping. */
.row-menu-item[href$="/rm"] { color: var(--red); }

.empty { color: var(--fg-faint); padding: 0.75rem; }

/* --- a note ----------------------------------------------------------- */

/* The same winbar on the reading page, so a note looks like the same window
   whichever of its two pages you are on. */
.note-head {
  background: var(--line);
  border-radius: 3px;
  padding: 0.25rem 0.6rem;
  margin-bottom: 0.9rem;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
}
.note-head h1 { font-size: 0.8125rem; margin: 0; color: var(--fg); font-weight: 600; }
.note-acts { display: flex; align-items: center; gap: 0.75rem; flex: none; font-size: 0.8125rem; }

/* The one mark on the band, and the one thing on this page you came to do. It
   is the same size as the words beside it and reads louder, which is the
   point. */
.to-edit { display: inline-flex; color: var(--fg-soft); }
.to-edit:hover { color: var(--green); }

/* Two columns on a wide screen: the note, and its heading index. Below 60rem
 * the index becomes a details block above the body -- see the media query. */
body:has(.index) main {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 12rem;
  gap: 1.5rem;
  align-items: start;
}
body:has(.index) main > .crumbs { grid-column: 1 / -1; }

.index {
  position: sticky;
  top: calc(var(--bar) + 1rem);
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  border-left: 1px solid var(--line);
  padding-left: 0.75rem;
  font-size: 0.8125rem;
}
.index a { color: var(--fg-soft); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.index .h2 { padding-left: 0.6rem; }
.index .h3 { padding-left: 1.2rem; }

.body {
  overflow-wrap: anywhere;
  color: var(--ink);
}

/* `#` is the workhorse in this corpus -- 1,144 of them against 464 `##` -- so
 * a heading is a section label, not a shout. Never titlecased and never
 * text-transformed: the file is lowercase on purpose. */
.body h1, .body h2, .body h3, .body h4, .body h5, .body h6 {
  color: var(--mauve);
  font-size: 0.875rem;
  font-weight: 600;
  margin: 1.4rem 0 0.4rem;
}
.body h1 { color: var(--green); }
.body h1:first-child { margin-top: 0; }

/* `## m/d/yy` entries read as dates rather than as section titles. */
.body .dated { color: var(--amber); }

/* `#---------------` is a divider in this corpus, not a heading full of
 * hyphens. Notes::Markdown turns it into this. */
.body hr.divider {
  border: 0;
  border-top: 1px solid var(--line);
  margin: 1.6rem 0;
}

.body p { margin: 0 0 0.8rem; }
.body ul, .body ol { margin: 0 0 0.8rem; padding-left: 1.2rem; }
.body li { margin: 0.1rem 0; }
/* The whole item, marker included, so the rendered page and the editor agree
 * about what a bullet looks like. */
.body li { color: var(--list); }
.body li::marker { color: var(--list); }
.body a { color: var(--cyan); }
.body code {
  background: var(--bg-raised);
  border-radius: 2px;
  padding: 0.05rem 0.25rem;
}
.body pre {
  background: var(--bg-sunken);
  border: 1px solid var(--line);
  border-radius: 3px;
  padding: 0.6rem 0.75rem;
  overflow-x: auto;
}
.body pre code { background: none; padding: 0; }
.body blockquote {
  margin: 0 0 0.8rem;
  padding-left: 0.75rem;
  border-left: 2px solid var(--line);
  color: var(--fg-soft);
}
.body table { border-collapse: collapse; margin-bottom: 0.8rem; display: block; overflow-x: auto; }
.body th, .body td { border: 1px solid var(--line); padding: 0.25rem 0.5rem; text-align: left; }

pre.raw {
  background: var(--bg-sunken);
  border: 1px solid var(--line);
  border-radius: 3px;
  padding: 0.75rem;
  overflow-x: auto;
  white-space: pre-wrap;
  color: var(--fg-soft);
}

/* --- flashes ---------------------------------------------------------- */

.flash {
  border: 1px solid var(--green);
  border-left-width: 3px;
  border-radius: 3px;
  padding: 0.5rem 0.75rem;
  margin: 0 0 1rem;
  color: var(--fg);
}
.flash.is-alert { border-color: var(--amber); color: var(--amber); }

.missing { color: var(--fg-soft); }
.missing h1 { color: var(--fg); font-size: 1rem; }

/* --- narrow ----------------------------------------------------------- */

@media (max-width: 60rem) {
  /* **The listing sheds a column at a time.** Five is a desk layout. On a
     narrow window the title and the line count go first: the filename is what
     the eye is scanning for, and the size and date are what tell you whether
     this is the note you were just in. Below 40rem even those go -- see the
     phone block at the foot of this file. */
  .row {
    grid-template-columns: 1fr 3.5rem 4rem;
  }
  .row .title,
  .row .lines { display: none; }

  /* The heading index stops being a column. The tree is a *page*, not a
   * drawer: `/` is the tree, `/n/...` is the note, and back goes up -- so
   * there is no overlay, no focus trap and no javascript in the navigation. */
  body:has(.index) main {
    display: block;
  }

  .index {
    position: static;
    border-left: 0;
    border-top: 1px solid var(--line);
    padding: 0.75rem 0 0;
    margin-top: 1.5rem;
  }

  .wordmark .long { display: none; }
  .wordmark .short { display: inline; }
}

/* --- search ----------------------------------------------------------- */

.hit {
  border: 1px solid var(--line);
  border-radius: 3px;
  margin-bottom: 0.75rem;
  overflow: hidden;
}
.hit-path {
  display: block;
  padding: 0.4rem 0.75rem;
  background: var(--bg-raised);
  color: var(--green);
}
.hit-line {
  display: grid;
  grid-template-columns: 3rem 1fr;
  gap: 0.5rem;
  padding: 0.15rem 0.75rem;
  color: var(--fg-soft);
  overflow-wrap: anywhere;
}
.hit-line .no { color: var(--fg-faint); text-align: right; font-size: 0.8125rem; }

/* What the archive is holding back. Quiet, but present: the point is that a
   result count never silently omits things. */
.in-archive { color: var(--fg-faint); }
.in-archive a { color: var(--amber); }
.hit-line.is-heading .text { color: var(--mauve); }
.hit-line.is-name .text { color: var(--fg); }

/* --- signing in ------------------------------------------------------- */

.signin { max-width: 22rem; margin: 3rem auto 0; }

.signin-form { display: flex; flex-direction: column; gap: 0.75rem; }
.signin-form label { display: flex; flex-direction: column; gap: 0.25rem; }
.signin-form span { color: var(--fg-soft); font-size: 0.8125rem; }

.signin-form input[type="email"],
.signin-form input[type="password"],
.signin-form input[type="text"] {
  background: var(--bg-sunken);
  border: 1px solid var(--line);
  border-radius: 3px;
  color: var(--fg);
  font: inherit;
  /* 16px minimum, or iOS zooms on focus and never zooms back. */
  font-size: max(1rem, 0.875rem);
  padding: 0.45rem 0.6rem;
}
.signin-form input:focus { outline: none; border-color: var(--green); }

.signin-form input[type="submit"] {
  background: var(--bg-raised);
  border: 1px solid var(--green);
  border-radius: 3px;
  color: var(--green);
  font: inherit;
  padding: 0.45rem 0.6rem;
  cursor: pointer;
}
.signin-form input[type="submit"]:hover { background: var(--line); }

.whoami { flex: none; }
.whoami button {
  background: none;
  border: 0;
  color: var(--fg-faint);
  font: inherit;
  cursor: pointer;
  padding: 0;
}
.whoami button:hover { color: var(--fg); }

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

/* **The form owns the height; the buffer fills what is left.**
 *
 * It used to be the other way round -- the buffer carried a calc() height and
 * the form was whatever that added up to. In a split that is not deterministic:
 * both panes are grid items stretched to the same height, so the buffers get
 * shrunk by flex to fit, by different amounts, and the two save bars end up at
 * different heights. One of them was then sticky-pinned to the viewport and the
 * other was not, which is what made them visibly disagree.
 *
 * Sizing the form and letting the buffer take the remainder makes both panes
 * identical by construction, and the bars line up because they are the last
 * child of two boxes that end at the same place. */
.editor {
  display: flex;
  flex-direction: column;
  /* No gap: the winbar is the buffer's top edge and a gap between them makes
     it a floating label again -- 8px of it, which looks joined in a screenshot
     and is not. The save bar below gets its own margin instead, so the one
     seam that should be there still is. */
  gap: 0;
  height: calc(var(--vvh, 100dvh) - 7rem);
}
.editor > .editor-bar { margin-top: 0.5rem; }

/* **vim's winbar.** One band across the top of the window, naming what is in
 * it, with the window's controls at the other end.
 *
 * It was a bare row floating above the buffer: the frame had no top edge, the
 * path sat at heading size taking a row and a half of vertical space, and
 * nothing tied the name to the thing it named. congo's `.pane-status.is-head`
 * is the same band for the same reason and this is its shape, joined to the
 * buffer below rather than hovering over it. */
.editor-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  background: var(--line);
  border: 1px solid var(--line);
  border-bottom: 0;
  border-radius: 3px 3px 0 0;
  padding: 0.25rem 0.6rem;
  font-size: 0.8125rem;
}
.editor-head .note-path {
  flex: 1;
  min-width: 0;
  /* Winbar size, not heading size. The path was 1rem and took the height of a
     line and a half; every row above the buffer is a row of editor you do not
     get, which is the argument that moved the vim toggle up here too. */
  font-size: 0.8125rem;
}

.editor-body {
  flex: 1 1 auto;
  /* Without this a flex child refuses to shrink below its content, and a long
   * note pushes the save bar off the bottom of the pane. */
  min-height: 0;
  display: flex;
}

/* **A definite height, not `flex: 1`.**
 *
 * CodeMirror sizes itself to its content when it is not told otherwise, so a
 * sixty-line note grew past its box and sat over the save bar while a
 * two-line one came up short -- the two panes disagreeing again, one note
 * long and one short. `height: 100%` of a parent whose height is already
 * definite pins both. */
/* Square across the top and no border there, so the winbar above is the
   window's top edge rather than a second one a pixel away from it. */
.editor-body #buffer,
.editor-body .cm-editor {
  border-top: 0;
  border-radius: 0 0 3px 3px;
}

#buffer,
.cm-editor {
  /* dvh, not vh: on iOS `vh` does not shrink for the virtual keyboard, so the
   * cursor ends up behind it. --vvh is set from visualViewport when javascript
   * is there; its absence leaves 100dvh, which is close enough.
   *
   * 9rem of chrome, down from 12: the vim toggle moved up onto the path line
   * and the save bar lost a row, so the buffer gets what they gave back. */
  height: 100%;
  min-height: 0;
  width: 100%;
  background: var(--bg-sunken);
  border: 1px solid var(--line);
  border-radius: 3px;
}

#buffer {
  width: 100%;
  color: var(--fg);
  font: inherit;
  /* 16px minimum, or iOS zooms on focus and never zooms back. */
  font-size: max(1rem, var(--editor-size));
  padding: 0.6rem 0.75rem;
  resize: vertical;
  white-space: pre;
  overflow-wrap: normal;
}
#buffer:focus { outline: none; border-color: var(--green); }

/* Sticky, so it sits above the phone keyboard rather than at the end of a
 * scrolled document. Real submit buttons, so it works with javascript off.
 *
 * The actions sit at the right end and the status runs along from the left,
 * so the two things that change -- what you are about to do, and what just
 * happened -- are at opposite ends and never shuffle each other about. */
.editor-bar {
  /* Not sticky any more: the form now ends at the bottom of the visual
   * viewport, so the bar is already where sticky was putting it -- and sticky
   * was what let the two panes' bars drift apart. */
  flex: none;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 1rem;
  padding: 0.5rem 0;
  background: var(--bg);
  border-top: 1px solid var(--line);
}
.editor-bar .editor-status { margin-right: auto; }

.editor-bar input[type="submit"] {
  background: var(--bg-raised);
  border: 1px solid var(--green);
  border-radius: 3px;
  color: var(--green);
  font: inherit;
  padding: 0.4rem 1rem;
  cursor: pointer;
}
.editor-bar input[type="submit"]:hover { background: var(--line); }
.editor-bar input.is-force { border-color: var(--amber); color: var(--amber); }

.vim-toggle {
  color: var(--fg-faint);
  display: flex;
  align-items: center;
  gap: 0.3rem;
  flex: none;
  font-size: 0.8125rem;
}

.conflict { margin-top: 1.5rem; }
.conflict summary { color: var(--amber); cursor: pointer; margin-bottom: 0.5rem; }

/* CodeMirror, once it has replaced the textarea. The editor draws its own
 * chrome, so this only has to give it a box and the corpus's colours. */
/* CodeMirror's own element, which carries this class from note_editor.js.
 *
 * **No height of its own.** It had one -- a calc() left over from when the
 * editor was first wired up, two changes of layout ago -- and it quietly won
 * over everything since, which is why two panes kept coming out different
 * heights however the flex above them was arranged. The form sizes the pane
 * now and this fills it. */
.cm-host {
  height: 100%;
  min-height: 0;
  background: var(--bg-sunken);
  border: 1px solid var(--line);
  border-radius: 3px;
  overflow: hidden;
}
.cm-host.cm-focused { border-color: var(--green); }
/* The vim command line the plugin appends, kept in the same palette. */
.cm-host .cm-vim-panel {
  background: var(--bg-raised);
  color: var(--fg);
  padding: 0.15rem 0.5rem;
}
.cm-host .cm-vim-panel input { color: var(--fg); font: inherit; }

/* --- making, removing, and looking back ------------------------------- */

.new-path { display: flex; flex-direction: column; gap: 0.25rem; }
.new-path span { color: var(--fg-soft); font-size: 0.8125rem; }
.new-path input {
  background: var(--bg-sunken);
  border: 1px solid var(--line);
  border-radius: 3px;
  color: var(--green);
  font: inherit;
  font-size: max(1rem, 0.875rem);
  padding: 0.45rem 0.6rem;
}
.new-path input:focus { outline: none; border-color: var(--green); }

.confirm h1 { font-size: 1rem; font-weight: 600; color: var(--fg); }
.confirm .soft, .soft { color: var(--fg-faint); }

/* **What enter will do.** The confirm button is focused when the page opens,
   so the ring is not decoration -- it is the answer to "what happens if I
   press enter", and on the removal pages that answer deletes a file. `:focus`
   rather than `:focus-visible`: focus arrived programmatically, and the
   heuristics for whether that counts as "visible" differ by browser. */
.confirm-acts button:focus {
  outline: 2px solid var(--amber);
  outline-offset: 2px;
}
.confirm-acts button.is-danger:focus { outline-color: var(--red); }

.confirm-acts { display: flex; align-items: center; gap: 1rem; margin-top: 1rem; }
.confirm-acts button {
  background: var(--bg-raised);
  border: 1px solid var(--green);
  border-radius: 3px;
  color: var(--green);
  font: inherit;
  padding: 0.4rem 1rem;
  cursor: pointer;
}
.confirm-acts button.is-danger { border-color: var(--red); color: var(--red); }
.confirm-acts button:hover { background: var(--line); }

.history .row { grid-template-columns: minmax(8rem, 12rem) 1fr auto; }
.whoami a { color: var(--fg-faint); margin-right: 0.6rem; }
.whoami a:hover { color: var(--green); text-decoration: none; }

/* --- the jump list ---------------------------------------------------- */

.jump {
  position: fixed;
  inset: 0;
  z-index: 50;
  background: rgba(0, 0, 0, 0.6);
  display: flex;
  justify-content: center;
  padding: 10vh 1rem 1rem;
}

/* **Load bearing.** A class with `display: flex` beats the browser's own
 * `[hidden] { display: none }`, so without this the overlay is laid out at
 * inset:0 over every page while still reporting itself as hidden -- it eats
 * every click on the site and nothing says why. Two system tests failed on
 * exactly that: escape "not closing" it, and the header search box refusing
 * to be clicked. */
.jump[hidden] { display: none; }

.jump-box {
  width: min(48rem, 100%);
  max-height: 70vh;
  display: flex;
  flex-direction: column;
  background: var(--bg-raised);
  border: 1px solid var(--line);
  border-radius: 4px;
  overflow: hidden;
}

.jump-prompt {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem 0.75rem;
  border-bottom: 1px solid var(--line);
}
.jump-prompt .prompt { color: var(--green); }
.jump-prompt input {
  flex: 1;
  background: none;
  border: 0;
  color: var(--fg);
  font: inherit;
  /* 16px minimum, or iOS zooms on focus and never zooms back. */
  font-size: max(1rem, 0.875rem);
}
.jump-prompt input:focus { outline: none; }

.jump-list { overflow-y: auto; }

.jump-row {
  display: block;
  padding: 0.3rem 0.75rem;
  color: var(--fg-soft);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  direction: rtl;      /* keep the filename visible when a path is too long */
  text-align: left;
}
.jump-row:hover { text-decoration: none; color: var(--fg); }
.jump-row.is-here {
  background: var(--line);
  color: var(--fg);
  /* The cursor bar takes the family's colour too, so which corner of the
     corpus you are about to open is legible without reading the path. */
  box-shadow: inset 2px 0 0 var(--root-ink, var(--green));
}

.find-form {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  background: var(--bg-sunken);
  border: 1px solid var(--line);
  border-radius: 3px;
  padding: 0 0.6rem;
  margin-bottom: 0.75rem;
}
.find-form .prompt { color: var(--green); }
.find-form input {
  flex: 1;
  background: none;
  border: 0;
  color: var(--fg);
  font: inherit;
  font-size: max(1rem, 0.875rem);
  padding: 0.45rem 0;
}
.find-form input:focus { outline: none; }

.jump-mode {
  color: var(--amber);
  font-size: 0.8125rem;
  flex: none;
}
.jump-mode:not(:empty)::before { content: "["; }
.jump-mode:not(:empty)::after { content: "]"; }

/* A grep hit shows the line it matched under the path it is in. */
.jump-row .jump-line {
  display: block;
  color: var(--fg-faint);
  font-size: 0.8125rem;
  overflow: hidden;
  text-overflow: ellipsis;
}
.jump-row:has(.jump-line) { padding: 0.35rem 0.75rem; }

/* The window's footer: which list you are in and what the keys do. */
.jump-help {
  border-top: 1px solid var(--line);
  padding: 0.35rem 0.75rem;
  color: var(--fg-faint);
  font-size: 0.75rem;
}

/* A directory row inside the tree list. */
.jump-row.is-dir { color: var(--root-ink, var(--green)); white-space: pre; }

/* --- what each top-level directory is ----------------------------------
 *
 * Nine roots, three families. The corpus is work, tech notes and the rest of
 * a life, and in a flat list of 453 paths those were indistinguishable -- one
 * colour of grey, read by parsing the first word of every line.
 *
 * `--root-ink` is set on whatever carries `data-root` and inherits from there,
 * so this map is the only place the families are written down and the tree and
 * the search results can use it later without repeating it.
 *
 * A root with no rule here still works; it is simply uncoloured. The pairing
 * is a guess at what belongs together and is meant to be re-filed -- moving
 * `recipes` to its own colour is moving one line. `test/lib/palette_test.rb`
 * asserts every root in config/notes.yml appears here, so a new directory
 * cannot quietly end up in no family at all. */

/* put away: dimmer than any of the three families, because a row from the
   archive turning up in a list should look like what it is */
[data-root="archive"] { --root-ink: var(--fg-faint); }

/* work */
[data-root="ps"] { --root-ink: var(--amber); }

/* tech */
[data-root="arch"],
[data-root="lc"],
[data-root="rails"],
[data-root="tech"],
[data-root="vim"] { --root-ink: var(--cyan); }

/* personal */
[data-root="luther"],
[data-root="recipes"],
[data-root="soccer"],
[data-root="zen"] { --root-ink: var(--mauve); }

/* A path row reads as two things: the directory, in its family's colour, and
 * the filename, which is what you came for. */
.jump-row .jump-in { color: var(--root-ink, var(--fg-faint)); }
.jump-row .jump-leaf { color: var(--fg); }
.jump-row.is-here .jump-leaf { color: var(--ink); }

/* What the last write did, in the save bar. */
.editor-status {
  color: var(--fg-faint);
  font-size: 0.8125rem;
}
.editor-status.is-alert { color: var(--amber); }

/* --- the note's path, which is also its breadcrumb --------------------- */

.note-path {
  font-size: 1rem;
  font-weight: 600;
  margin: 0;
  color: var(--fg-soft);
  overflow-x: auto;
  white-space: nowrap;
}
.note-path a { color: var(--fg-soft); font-weight: 400; }
.note-path a:hover { color: var(--green); }
.note-path .at-root { color: var(--green); }
.note-path .sep { color: var(--line); padding: 0 0.15rem; }

/* **On the winbar the separator needs its own colour.** It is `--line` by
   default, which reads as a quiet divider on the page ground -- and vanished
   completely the moment the band behind it became `--line` too, leaving
   `~ ps meetings morning_meeting.md` with no slashes in it at all.
   test/system/winbar_test.rb asserts the two are never the same colour. */
.editor-head .note-path .sep,
.note-head .note-path .sep { color: var(--fg-faint); opacity: 0.65; }
/* The filename, which is the page you are on. */
.note-path .here { color: var(--fg); }

/* What is about to go, on a directory's confirmation page. A list rather than
   a <pre> of contents: a directory's contents are names, and a note's are
   bytes. */
.confirm-list {
  list-style: none;
  margin: 0.75rem 0;
  padding: 0.5rem 0.75rem;
  max-height: 18rem;
  overflow-y: auto;
  background: var(--bg-sunken);
  border: 1px solid var(--line);
  border-radius: 3px;
  color: var(--fg-soft);
}

/* An ordinary question rather than a warning: the same heading, without the
   colour that means "read this twice". */
.confirm-ask.is-quiet { color: var(--fg); }

.confirm-ask {
  font-size: 1rem;
  font-weight: 600;
  color: var(--red);
  margin: 0.75rem 0 0.5rem;
}

/* The buffer's own row exists so the head, the editor and the save bar are
 * three siblings in the order they appear: without it CodeMirror mounts at the
 * end of the form and the save bar ends up above the text. It is sized above. */

/* The character sitting under the block cursor.
 *
 * **Here rather than in the editor's theme, and with !important**, because it
 * has to beat the span the syntax highlighter already wrapped that character
 * in -- a colour merely inherited from the cursor loses to it, which left a
 * heading under the caret painted green on yellow. CodeMirror's style module
 * does not carry !important through reliably, so the rule lives in the
 * stylesheet where it does. */
.cm-editor .cm-fat-cursor,
.cm-editor .cm-fat-cursor * {
  color: var(--caret-ink) !important;
}

.editor-head .to-view { color: var(--fg-faint); font-size: 0.8125rem; flex: none; }
.editor-head .to-view:hover { color: var(--cyan); }

/* --- panes ------------------------------------------------------------- */

.panes.is-split {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1rem;
}

/* Each half of a split gets half the width, so the buffer inside has to stop
 * assuming it owns the page. min-width:0 is what lets a grid child actually
 * shrink rather than being sized by its widest line. */
.panes.is-split > .editor { min-width: 0; }

/* The focused half, said quietly -- with two buffers on screen and one cursor,
 * which one has the keys is otherwise a guess. */
.panes.is-split .cm-host { border-color: var(--line); }
.panes.is-split .cm-host.cm-focused { border-color: var(--green); }

@media (max-width: 60rem) {
  /* A phone has no room for two buffers side by side. */
  .panes.is-split { grid-template-columns: 1fr; }
}

/* A heading in the table of contents.
 *
 * **direction: ltr, undoing the rule above.** The path rows are rtl so a long
 * path truncates from the left and keeps its filename visible -- which, on a
 * heading, moved the leading hashes to the end and rendered "agenda #".
 *
 * Coloured the way the editor colours that heading, so the table of contents
 * and the buffer agree about what is a section and what is a subsection. */
.jump-row.is-heading {
  direction: ltr;
  white-space: pre;
  color: var(--mauve);
}
.jump-row.is-h1 { color: var(--green); }
.jump-row.is-h2,
.jump-row.is-h3 { color: var(--mauve); }

/* --- a phone ----------------------------------------------------------- */

@media (max-width: 40rem) {
  /* **The filename, and nothing about the file.**
   *
   * At 60rem the listing already drops the title and the line count and keeps
   * name, size and date. At phone width even that is too much: 390px minus two
   * number columns, the gaps and the padding leaves about 220px for the name,
   * which `mon-and-wed-meetings.md` does not fit in -- so the row wrapped and
   * every entry became two lines tall, using half the screen to say a date.
   *
   * A directory keeps its count. It is not a fact about a file, it is the one
   * number that decides whether a folder is worth opening, and it is three
   * characters wide.
   */
  .row {
    grid-template-columns: 1fr auto;
  }
  .row .size,
  .row .when { display: none; }
}

/* --- the welcome page --------------------------------------------------- *
 *
 * Scoped under `body.landing`, because this stylesheet is shared with the app
 * and the app owns names like `.wordmark`, `.sep` and `.row` that mean
 * something else inside it.
 *
 * **No new colours.** The whole point is that the page looks like the thing it
 * is describing, so it uses the tokens the app uses and nothing besides. The
 * panels are drawn rather than photographed for the same reason: everything
 * this app renders is monospace text on a dark ground, so a screenshot would
 * be a heavier, blurrier copy of something the browser can draw -- and it
 * would drift from the product the first time a colour changed.
 */
body.landing {
  /* Comfortable prose measure. The app's own pages are dense on purpose; this
     one is read once, from the top. */
  line-height: 1.7;
}
body.landing main {
  max-width: 44rem;
  margin: 0 auto;
  padding: 0 1.25rem 6rem;
}

.landing .lede { padding: 3rem 0 0.75rem; }

/* The mark, in the app's own spelling and the app's own green. Bigger than
   the `~ notes` it replaces, which was small enough to read as a path
   somebody had mistyped rather than as the name of the thing.
   
   `.brand` rather than `.wordmark`: this stylesheet is shared with the app,
   and `.wordmark` there is a 0.875rem link in a header bar. */
/* The mark and the credit on one line: what this is on the left, whose it is
   on the right. `baseline`, so the two sit on the same line of type despite
   being different sizes. It wraps on a narrow screen rather than shrinking. */
.landing .top {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
  margin-bottom: 2rem;
}
.landing .brand {
  color: var(--green);
  font-weight: 600;
  font-size: 1.15rem;
  letter-spacing: 0.02em;
  margin: 0;
}
.landing .top .credit { margin: 0; }

.landing h1 {
  font-size: 1.9rem;
  line-height: 1.25;
  font-weight: 600;
  margin: 0 0 1rem;
  color: var(--ink);
}

/* **`--fg`, not `--fg-soft`.** This is the page's body copy, and the app uses
   `--fg` for what it means you to read and the softer tokens for what it
   means you to skim. A landing page is all the former. */
.landing .sub,
.landing section > p {
  color: var(--fg);
  max-width: 34rem;
  margin: 0 0 1.2rem;
}
.landing .sub { font-size: 1rem; }
.landing strong { color: var(--ink); font-weight: 600; }
.landing em { color: var(--ink); font-style: normal; }
.landing code {
  color: var(--amber);
  background: var(--bg-raised);
  padding: 0.05em 0.3em;
  border-radius: 2px;
}

/* Secondary, but still meant to be read. `--fg-faint` is for furniture; these
   carry real caveats. */
.landing .aside { color: var(--fg-soft); font-size: 0.9rem; margin: 0.4rem 0 0; }

/* **Where a call to action would be, if there were one.** There is nothing to
   sign up for, download or try, so the page says that here instead of putting
   a button on a door that opens for one person. Amber because it is a caveat
   rather than a warning -- the same weight the app gives "this note changed
   on disk since you opened it". */
.landing .status {
  margin: 2.25rem 0 0;
  max-width: 34rem;
  border: 1px solid var(--amber);
  border-radius: 3px;
  padding: 0.75rem 0.9rem;
  color: var(--fg);
}
.landing .status strong { color: var(--amber); }

/* Chips rather than underlined words: a run of bare words reads as a
   sentence, and a border says where each one ends and doubles the tap
   target. */
.landing .jump-to { display: flex; flex-wrap: wrap; gap: 0.5rem; margin: 2rem 0 0; font-size: 0.85rem; }
.landing .jump-to a {
  display: inline-flex;
  align-items: baseline;
  gap: 0.15rem;
  /* **`--fg`, not a softer token.** These are links, which have to be read
     rather than skimmed. The chip stays quiet through its border and its
     ground instead, which is where quiet belongs. */
  color: var(--fg);
  border: 1px solid var(--line);
  border-radius: 2px;
  padding: 0.2rem 0.55rem;
  background: var(--bg-raised);
  transition: color 0.12s, border-color 0.12s;
}

/* **One custom property per chip, driving the mark and the hover together.**
   Set it once and the `#`, the border and the focus ring cannot drift into
   disagreeing about which colour this chip is.
   
   The four are the app's own accents, rotated -- tracker's landing page does
   this and this is its rule, so the two front doors look like siblings. They
   are decoration rather than information: nothing here claims that `phone` is
   mauve for a reason, which is why the label stays `--fg` and the colour
   never carries anything on its own. `--red` is left out, because red means
   one thing in this app and it is not "the sixth section". */
.landing .jump-to a { --chip: var(--green); }
.landing .jump-to a:nth-child(4n + 2) { --chip: var(--cyan); }
.landing .jump-to a:nth-child(4n + 3) { --chip: var(--mauve); }
.landing .jump-to a:nth-child(4n + 4) { --chip: var(--amber); }

.landing .jump-to .hash { color: var(--chip); }
.landing .jump-to a:hover {
  color: var(--chip);
  border-color: var(--chip);
  background: var(--line);
  text-decoration: none;
}
.landing .jump-to a:focus-visible { outline: 2px solid var(--chip); outline-offset: 2px; }

@media (prefers-reduced-motion: reduce) { .landing .jump-to a { transition: none; } }

.landing section { padding: 3rem 0 0; border-top: 1px solid var(--line); margin-top: 3rem; }
/* The first section sits closer: between the lede and the heading below it
   the full rhythm has three separate gaps doing one job. Later sections keep
   it, because there a rule separates two finished thoughts. */
.landing section:first-of-type { margin-top: 1.75rem; padding-top: 1.75rem; }
.landing section h2 { font-size: 1.15rem; font-weight: 600; color: var(--ink); margin: 0 0 1rem; }

/* --- the drawn panels --------------------------------------------------- */

.landing .shot {
  background: var(--bg-raised);
  border: 1px solid var(--line);
  border-radius: 3px;
  overflow: hidden;
  margin: 1.5rem 0;
  font-size: 0.8125rem;
  line-height: 1.55;
}
/* The app's winbar, which is where a path lives on every page of it. */
.landing .shot-bar {
  display: flex;
  align-items: center;
  gap: 0.35rem;
  margin: 0;
  padding: 0.35rem 0.7rem;
  background: var(--line);
  color: var(--fg-soft);
  overflow-x: auto;
  white-space: nowrap;
}
.landing .shot-bar .prompt { color: var(--green); }
.landing .shot-bar .sep { color: var(--fg-faint); }
.landing .shot-bar .here { color: var(--ink); font-weight: 600; }

/* A block cursor, because the point of the panel is that you typed this. */
.landing .caret {
  display: inline-block;
  width: 0.55em;
  height: 1.05em;
  background: var(--caret);
  margin-left: 0.15em;
  vertical-align: text-bottom;
  animation: landing-caret 1.1s steps(1) infinite;
}
@keyframes landing-caret { 50% { opacity: 0; } }
@media (prefers-reduced-motion: reduce) {
  .landing .caret { animation: none; }
}

.landing .shot-body { padding: 0.5rem 0; }
.landing .shot-body.is-buffer { background: var(--bg-sunken); }

/* A line of the editor, coloured the way the editor colours it. */
.landing .shot-line { padding: 0 0.7rem; white-space: pre; overflow-x: auto; }
.landing .shot-line.is-cursor { background: var(--cursorline); }
.landing .shot-line .no {
  display: inline-block;
  width: 1.75rem;
  color: var(--fg-faint);
  text-align: right;
  margin-right: 0.75rem;
}
.landing .shot-line .h1 { color: var(--green); font-weight: 600; }
.landing .shot-line .h2 { color: var(--mauve); font-weight: 600; }
.landing .shot-line .li { color: var(--list); }
/* The caret sitting on a character, which is how the editor draws it. */
.landing .shot-line .cur { background: var(--caret); color: var(--caret-ink); }

/* A row of the tree, and a row of the completion menu. */
.landing .shot-row,
.landing .shot-opt { display: flex; justify-content: space-between; gap: 1rem; padding: 0.15rem 0.7rem; color: var(--fg); }
.landing .shot-row .n { color: var(--fg-faint); font-variant-numeric: tabular-nums; }
.landing .shot-row.is-in .d { padding-left: 1.1rem; }
.landing .shot-row.is-here,
.landing .shot-opt.is-here {
  background: var(--cursorline);
  color: var(--ink);
  box-shadow: inset 2px 0 0 var(--green);
}

/* --- the one thing this site asks for -------------------------------- */

.landing .waitlist {
  margin: 1.75rem 0 0;
  padding: 1rem 1.1rem;
  background: var(--bg-raised);
  border: 1px solid var(--line);
  border-radius: 3px;
  max-width: 34rem;
}
.landing .waitlist .aside { margin: 0 0 0.85rem; }
.landing .waitlist .flash { margin: 0 0 0.85rem; }

.landing .waitlist-form { display: flex; flex-direction: column; gap: 0.6rem; }
.landing .waitlist-form input[type="email"],
.landing .waitlist-form input[type="text"] {
  background: var(--bg-sunken);
  border: 1px solid var(--line);
  border-radius: 3px;
  color: var(--ink);
  font: inherit;
  /* 16px minimum, or ios zooms on focus and never zooms back. */
  font-size: max(1rem, 0.875rem);
  padding: 0.45rem 0.6rem;
}
.landing .waitlist-form input:focus { outline: none; border-color: var(--green); }
.landing .waitlist-form input::placeholder { color: var(--fg-faint); }

.landing .waitlist-form input[type="submit"] {
  align-self: flex-start;
  background: none;
  border: 1px solid var(--green);
  border-radius: 2px;
  color: var(--green);
  font: inherit;
  padding: 0.45rem 1.1rem;
  cursor: pointer;
}
.landing .waitlist-form input[type="submit"]:hover { background: var(--green); color: var(--bg); }
.landing .waitlist-form input[type="submit"]:focus-visible { outline: 2px solid var(--green); outline-offset: 2px; }

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