/*
 * The whole of dumbwaiter's styling, served at /assets/dumbwaiter.css.
 *
 * It has to sit under app/assets/. /assets/{*path} is the only asset route, so a
 * top-level app/x.css embeds as x.css, and a request for /x.css falls through to
 * the catch-all and is answered with the app shell. A stylesheet that arrives as
 * text/html is one the browser refuses, and all the page does about it is look
 * unstyled.
 *
 * Every color, and every measurement two rules have to agree on, is a custom
 * property on :root, and no rule below spells a color of its own. That is what
 * makes the dark theme a second :root block rather than a second copy of every
 * rule, and it is what lets the commits that draw the diff and the lanes add
 * rules without picking a palette. Add a property when a surface needs a color
 * nothing here has.
 *
 * Class names are short and unprefixed: there is one page and nothing to collide
 * with. Where a name exists in the client already it is spelled the same way, so
 * .line.insert, .line.delete and .line.context are LineKind out of
 * diff/textdiff.ts verbatim and a renderer sets the class from the kind it holds.
 */

:root {
  /* form controls, scrollbars and the canvas follow the theme without a rule */
  color-scheme: light dark;

  /* Monospace throughout. Every column below is a column of git output, and in a
     proportional font an object id is a different width per id. */
  --mono: ui-monospace, 'DejaVu Sans Mono', 'Liberation Mono', monospace;
  --size: 13px;

  /* And the one exception to that, taken on purpose. A rendered markdown document
     is prose and not output (DESIGN §8.5): nothing in it is a column, and setting
     a README in a fixed width is the one thing that makes it read worse than its
     own source. So .prose is the only block below that names another face, and
     the code inside it goes back to --mono, which is where the invariant still
     holds. Larger than --size, because 13px was picked for a grid of fixed-width
     glyphs and reads small in a face that has none. */
  --prose: ui-sans-serif, system-ui, 'DejaVu Sans', 'Liberation Sans', sans-serif;
  --prose-size: 14px;
  /* How wide a line of prose gets before it stops being one anybody reads. The
     pane can be dragged wider than that; the paragraphs need not follow. */
  --prose-width: 46em;

  /* What the commit rows and the graph rail have to agree on: one SVG is drawn
     per row, the middle of its lane n half a lane past n * --lane-width and its
     dot of radius --lane-dot at half of --row-height. A row that took its height
     from its content instead would slide every rail off the commit it belongs
     to. The drawer reads all three off :root rather than spelling them again. */
  --row-height: 20px;
  --lane-width: 14px;
  --lane-dot: 3.5px;
  /* How wide the rail column is, in the list as a whole and not per row: a row
     sized to its own lanes would step the id column in and out down the page.
     Six lanes is what most histories use, and the count is a property of its own
     because that is the width the drawer starts from. Raising it is not how the
     drawer widens the column, though: a var() is substituted where it is
     declared, so a page that needs more sets --rail itself on the list. */
  --rail-lanes: 6;
  --rail: calc(var(--rail-lanes) * var(--lane-width));
  /* What is left of the row once the rail and the fixed columns have taken
     theirs is the subject, so the list is wide: below about 34em a subject
     ellipses after a handful of words, and above 56em the diff starts losing the
     80 columns a patch is written to. */
  --log-width: minmax(34em, 56em);

  /* Surfaces */
  --bg: #ffffff;
  --fg: #1b1b1b;
  --fg-dim: #666666;
  --bg-chrome: #f2f2f2;
  --bg-hover: #f0f0f0;
  --bg-select: #d8e4f0;
  --border: #cccccc;
  --accent: #2b5f9e;
  --danger: #b3202c;
  /* What lifts a panel off the page it covers. A border alone reads as a box drawn
     in the layout rather than as something in front of it, and the ref picker is
     the first thing here that is in front of anything. */
  --shadow: rgb(0 0 0 / 18%);

  /* The diff. The tints stay pale on purpose: the text keeps its own color on
     top of them, because a highlighted token has to win over the line it is on. */
  --diff-insert-bg: #e6ffec;
  --diff-insert-fg: #1a7f37;
  --diff-delete-bg: #ffebe9;
  --diff-delete-fg: #b3202c;
  --diff-hunk-bg: #eef2f7;
  --diff-hunk-fg: #4a5a70;

  /* The eight token classes of DESIGN §8.7, one hue each, in the order
     syntax/scan.ts lists them. Plain code is --fg and has no property here,
     because the absence of a class is what plain means. Each of these is dark
     enough to hold up on the two diff tints above as well as on --bg, which is
     the one thing the palette has to do that a page of prose does not ask of it. */
  --tok-comment: #5f6a75;
  --tok-string: #0a3069;
  --tok-number: #116329;
  --tok-keyword: #b31d28;
  --tok-type: #17696f;
  --tok-function: #6f42c1;
  --tok-property: #0550ae;
  --tok-meta: #953800;

  /* Lane colors, indexed rather than named: the rail picks var(--lane-N) for
     N = lane % --lanes. The count lives here with the colors so the drawer cannot
     carry a copy of it that drifts when a color is added. */
  --lanes: 8;
  --lane-0: #3572b0;
  --lane-1: #2f9e44;
  --lane-2: #d9480f;
  --lane-3: #9c36b5;
  --lane-4: #c92a2a;
  --lane-5: #0c8599;
  --lane-6: #b08000;
  --lane-7: #d6336c;
}

/* The dark theme is the same document with other values. Only what a dark
   background changes is listed; the geometry above is shared. */
@media (prefers-color-scheme: dark) {
  :root {
    --bg: #16181a;
    --fg: #dcdcdc;
    --fg-dim: #8b9199;
    --bg-chrome: #202325;
    --bg-hover: #23262a;
    --bg-select: #2b3f57;
    --border: #33383d;
    --accent: #79b8ff;
    --danger: #f2777a;
    /* darker and not lighter: a shadow on a dark background is the absence of the
       little light there is, and a pale one would read as a glow */
    --shadow: rgb(0 0 0 / 55%);

    --diff-insert-bg: #12261a;
    --diff-insert-fg: #56d364;
    --diff-delete-bg: #2c1618;
    --diff-delete-fg: #f2777a;
    --diff-hunk-bg: #1e2530;
    --diff-hunk-fg: #8ba3c7;

    /* the eight token hues again, at the lightness a dark background wants: one
       picked to be legible on white is one that disappears on this */
    --tok-comment: #8b949e;
    --tok-string: #a5d6ff;
    --tok-number: #7ee787;
    --tok-keyword: #ff7b72;
    --tok-type: #56d4dd;
    --tok-function: #d2a8ff;
    --tok-property: #79c0ff;
    --tok-meta: #ffa657;

    /* the same eight hues, lifted until they read against the background */
    --lane-0: #74b0f0;
    --lane-1: #63d68a;
    --lane-2: #ff9f5a;
    --lane-3: #cf9bec;
    --lane-4: #ff8080;
    --lane-5: #4fd1e0;
    --lane-6: #e8c35a;
    --lane-7: #ff85b0;
  }
}

/* The page is the viewport and never scrolls: the panes do, so that the bar at
   the top stays put while a commit list a thousand rows long moves under it. */
html,
body {
  height: 100%;
  overflow: hidden;
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--fg);
  font-family: var(--mono);
  font-size: var(--size);
  line-height: 1.45;
}

a {
  color: var(--accent);
  text-decoration: none;
}

a:hover {
  text-decoration: underline;
}

/* The mount, and the only layout in the file: a bar across the top, the commit
   list left with the rail in it, the divider, the commit itself right. */
#app {
  height: 100%;
  display: grid;
  grid-template-columns: var(--log-width) auto 1fr;
  grid-template-rows: auto 1fr;
  grid-template-areas:
    'chrome chrome chrome'
    'log split detail';
}

/* Not every view is two panes. The picker and a group listing are one column,
   and the modifier belongs here so the file still holds one grid. */
#app.one-pane {
  grid-template-columns: 1fr;
  /* named as well as the areas, because the narrow layout below adds a third row
     and this rule outranks it whatever the width */
  grid-template-rows: auto 1fr;
  grid-template-areas:
    'chrome'
    'main';
}

/* The frame before the first render, drawn by the mount rather than by markup in
   the shell, which would be a second source of truth for a DOM the view layer
   owns. :has(*) and not :empty, so a newline inside the mount does not decide
   whether the frame appears. Both go when the first view lands. */
#app:not(:has(*))::before {
  grid-area: log;
  content: '';
  border-right: 1px solid var(--border);
}

#app:not(:has(*))::after {
  grid-area: detail;
  content: '';
}

/* Which repository, which revision, and what the view has to say about the read. */
.chrome {
  grid-area: chrome;
  display: flex;
  align-items: baseline;
  gap: 1.5em;
  padding: 4px 10px;
  background: var(--bg-chrome);
  border-bottom: 1px solid var(--border);
  white-space: nowrap;
  overflow: hidden;
}

.chrome .repo {
  font-size: inherit;
  font-weight: bold;
  margin: 0;
}

/* The rev in the bar, which is a control and no longer a label: the button that
   says where you are, and the panel of every branch and tag behind it. */
.revs {
  min-width: 0;
}

.rev {
  font: inherit;
  color: var(--fg-dim);
  background: none;
  border: 1px solid transparent;
  border-radius: 3px;
  padding: 0 4px;
  max-width: 24em;
  cursor: pointer;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* not in the label, because the rev is the button's text and a marker in it would
   be a character somebody copies out along with the branch name */
.rev::after {
  content: ' \25be';
}

.rev:hover,
.revs.open .rev {
  color: var(--fg);
  background: var(--bg-hover);
  border-color: var(--border);
}

.rev:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

/* Fixed and not absolute: the bar clips what runs past it and this is meant to run
   past it, and a fixed box is not clipped by an ancestor's overflow. Where it goes
   is measured on open, since only the button knows where it is. Above .path, which
   is the one other thing on the page that leaves the flow. */
.rev-panel {
  position: fixed;
  z-index: 2;
  display: flex;
  flex-direction: column;
  min-width: 22em;
  max-width: min(48em, calc(100vw - 2em));
  max-height: min(60vh, 32em);
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 3px;
  box-shadow: 0 4px 12px var(--shadow);
}

.revs:not(.open) .rev-panel {
  display: none;
}

/* No focus ring: the filter has the focus for as long as the panel is open, so a
   ring around it says nothing, and what the eye wants is the row the arrows are on. */
.rev-filter {
  font: inherit;
  color: inherit;
  background: none;
  border: none;
  border-bottom: 1px solid var(--border);
  padding: 4px 8px;
  outline: none;
}

/* min-height: 0 for the same reason the panes have it: the flex item's automatic
   minimum is its content, so without it the list grows past the panel's max height
   instead of scrolling inside it. */
.rev-list {
  margin: 0;
  padding: 0 0 4px;
  list-style: none;
  overflow: auto;
  min-height: 0;
}

/* Stuck to the top of the list while its own rows scroll past, the way a path
   header is in a diff: 239 branches is more than a panel shows at once and a list
   you have scrolled into should still say what kind you are looking at. */
.rev-kind {
  position: sticky;
  top: 0;
  z-index: 1;
  padding: 4px 8px 2px;
  background: var(--bg);
  color: var(--fg-dim);
  font-weight: bold;
}

/* A marker column and the name, so the names line up whether or not a row is the
   one being shown. The dot is drawn here rather than put in the text for the reason
   the diff's +/- is: what a copy of the row yields should be the branch name. */
.rev-row {
  display: grid;
  grid-template-columns: 1.5ch 1fr;
  gap: 0 1ch;
  padding: 0 8px;
  cursor: pointer;
  white-space: nowrap;
}

.rev-row::before {
  content: '';
}

.rev-row.current::before {
  content: '\2022';
  color: var(--fg-dim);
}

/* the anchor takes the row's color: a panel of 239 links in the link color is a
   wall of blue, and the row is what you are clicking anyway */
.rev-row > * {
  min-width: 0;
  color: inherit;
  overflow: hidden;
  text-overflow: ellipsis;
}

.rev-row > a:hover {
  text-decoration: none;
}

.rev-row.current {
  background: var(--bg-select);
}

.rev-row:hover {
  background: var(--bg-hover);
}

/* Where the arrow keys are, which is not where the focus is: the focus stays in the
   filter so typing keeps working, and this is what aria-activedescendant names. */
.rev-row.active {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

/* A name no URL can spell, which takes a backslash in a ref or bytes that are not
   UTF-8 in a tree entry, and a submodule, whose commits are another repository's.
   The row is the name and nothing to click, so it reads as one that is there and
   cannot be gone to. */
.rev-row[aria-disabled],
.entry[aria-disabled] {
  color: var(--fg-dim);
  cursor: default;
}

/* Both panes scroll on their own. min-height: 0 is what allows it: a grid item's
   automatic minimum is its content, so without it a long list makes the row
   taller than the viewport instead of overflowing inside it. */
.pane {
  overflow: auto;
  min-height: 0;
}

.log {
  grid-area: log;
  margin: 0;
  padding: 0;
  list-style: none;
}

/* The divider, which draws the line the commit list used to carry as a border.
   It is padding with the background clipped to the content box, so the column
   is a hit area several pixels wide around one pixel of ink: a one-pixel target
   is a pixel hunt, and every other splitter on the desktop is forgiving here.

   The whole page stops selecting text while it is dragged. That belongs on the
   root and not on the divider, because the pointer is captured and therefore
   almost never over the divider during a drag. */
.split {
  grid-area: split;
  box-sizing: border-box;
  width: 7px;
  padding: 0 3px;
  cursor: col-resize;
  background: var(--border);
  background-clip: content-box;
}

.split:hover,
.split:focus-visible,
.split.dragging {
  background-color: var(--accent);
  outline: none;
}

:root:has(.split.dragging) {
  user-select: none;
}

.detail {
  grid-area: detail;
}

.main {
  grid-area: main;
  padding: 8px 10px;
}

/* The repository listing: a link per repository and per group, then what the
   daemon knows about it and the day it last moved. Three columns and not three
   fields run together, because a listing is read down the names and across only
   once the eye has found its row.

   The list owns the columns and a row is a subgrid of them. That is what makes
   them line up across rows at all: a row with a grid of its own would size its
   columns to its own content and every row would land somewhere else.

   Every column is sized to its own content and none of them stretches, so the
   three sit together and the leftover width is left over. The description is
   the one that may shrink below that, because it is the one field a reader can
   lose the tail of and still know which repository the row is.

   It scrolls on its own terms, because it is not always inside a pane: a
   collection that wrote itself a document draws the rows in the left column and
   the document beside them (DESIGN §8.5), and there the list *is* the pane. Same
   two properties `.pane` carries, and min-height: 0 for the same reason.
   align-content with them, because a grid stretches its rows into whatever height
   it was given and a pane is as tall as the viewport: two repositories would
   otherwise be one row at the top and one halfway down the page. */
.repos {
  display: grid;
  grid-template-columns: max-content minmax(0, max-content) max-content;
  align-content: start;
  gap: 0 1.5em;
  margin: 0;
  padding: 0;
  list-style: none;
  overflow: auto;
  min-height: 0;
}

/* And where it is the pane, the inset the pane around it used to give it. Ahead of
   `.log`'s padding on specificity, so the column it lands in is that rule's to
   decide and what the rows sit in is this one's. */
.repos.log {
  padding: 8px 10px;
}

.repos li {
  display: grid;
  grid-column: 1 / -1;
  grid-template-columns: subgrid;
}

/* on the cells and not the row: a grid item is what has a width to overflow */
.repos li > * {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* A row of either pane's list, and the columns of a commit: the rail, the
   abbreviated id, the subject, who and when. A tree entry is a row too and has
   two columns of its own, which .entry below overrides these with.

   The columns are the same width in every row so the eye can read down them, which
   is also why who and when are not sized to their content. The rail keeps its
   column while it is still empty, because widening a laid-out row afterwards is
   how a whole list shifts sideways.

   The id column is git's own abbreviation length and the date column is what
   YYYY-MM-DD HH:MM needs, so a list that spells either differently has to say so
   here too. */
.row {
  display: grid;
  grid-template-columns: var(--rail) 8ch 1fr 12ch 16ch;
  gap: 0 1ch;
  align-items: center;
  height: var(--row-height);
  padding: 0 8px;
  cursor: pointer;
  white-space: nowrap;
}

.row:hover {
  background: var(--bg-hover);
}

.row.selected {
  background: var(--bg-select);
}

.row:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

/* The rail is sized here and filled by the lane layout, an SVG the height of the
   row. Block, so it sits on no text baseline and leaves no gap under itself. */
.row .rail {
  display: block;
  width: var(--rail);
  height: var(--row-height);
}

/* One line per rail crossing the row, one circle for the commit. The drawer says
   which lane color each of them takes and says it once, as the color of the
   element; whether that paints a stroke or a fill is this file's business. The
   width is what reads as a rail at this row height without a history full of
   merges coming out as a wall of ink. */
.rail line {
  stroke: currentColor;
  stroke-width: 1.5px;
}

.rail circle {
  fill: currentColor;
}

.row .oid,
.row .who,
.row .when {
  color: var(--fg-dim);
}

.row .subject,
.row .who {
  overflow: hidden;
  text-overflow: ellipsis;
}

/* One entry of a directory: a marker column and the name, and nothing else. No
   mode and no size, both of which the file pane has room for and neither of which
   is worth a column that is 100644 all the way down. */
.row.entry {
  grid-template-columns: 2ch 1fr;
}

/* The kind as ls -F spells it, drawn here rather than put in the text for the
   reason the diff's +/- is: what a copy of the row yields should be the name. An
   ordinary file gets no marker and still gets the column, because content: ''
   generates a box — how .rev-row::before already works. */
.entry::before {
  content: '';
  color: var(--fg-dim);
}

.entry.tree::before {
  content: '/';
}

.entry.exec::before {
  content: '*';
}

.entry.symlink::before {
  content: '@';
}

.entry.gitlink::before {
  content: '&';
}

/* the anchor takes the row's color, the same as a ref row: every entry is a link,
   so the link color would paint a whole directory blue and the row is what is
   being clicked anyway */
.entry > * {
  min-width: 0;
  color: inherit;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* The path header of a listing wraps where a diff's does not: a diff names one
   file at a time and the pane is as wide as it is, while a crumb trail is as deep
   as the tree and the end of it is the part that says where you are. */
.crumbs {
  white-space: normal;
}

/* The commit: who wrote it and when, its message as written, then the paths. A
   row like the bar above it, but wrapping: an object id and a name are as long
   as they are, and a pane narrow enough to break them is one to break them in.

   A file's header is the same row with other fields in it — a mode, a size and the
   whole object id — so it joins the rule rather than restating it. */
.commit-head,
.file-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0 1.5em;
  padding: 8px 10px;
  border-bottom: 1px solid var(--border);
}

.commit-head .oid,
.commit-head .who {
  color: var(--fg-dim);
}

/* Dim all the way across, where a commit's header dims only two of its fields:
   nothing in here is the file, it is what the pane knows about the bytes below it. */
.file-head {
  color: var(--fg-dim);
}

.message {
  margin: 0;
  padding: 8px 10px;
  /* pre-wrap and not pre: a message is written to a width the pane need not have,
     and a wrapped line beats a horizontal scrollbar over the whole commit */
  white-space: pre-wrap;
}

/* One header per changed path, stuck to the top of the pane while its own hunks
   scroll past, so a long diff never leaves you reading a file you cannot name. */
.path {
  position: sticky;
  top: 0;
  z-index: 1;
  padding: 2px 10px;
  background: var(--bg-chrome);
  border-top: 1px solid var(--border);
  border-bottom: 1px solid var(--border);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Wide enough for R100, so that the paths start in the same column whatever
   happened to them and the list reads down. Inline, because .path stays a block
   and its ellipsis is over the whole line rather than over one flex item. */
.path .status {
  display: inline-block;
  min-width: 4ch;
  color: var(--fg-dim);
}

.hunk {
  padding: 0 10px;
  background: var(--diff-hunk-bg);
  color: var(--diff-hunk-fg);
}

/* One element per line, which is the point of the whole pane: a highlighter later
   replaces what is inside .text with token spans and touches nothing else. Two
   gutters, before and after, six characters because a file with a million lines
   is not one anybody reads in a browser. */
.line {
  display: grid;
  grid-template-columns: 6ch 6ch 1fr;
  gap: 0 1ch;
  padding: 0 10px;
}

.line .num {
  color: var(--fg-dim);
  text-align: right;
  /* a copied hunk should paste as the file, not as the file with its line
     numbers wedged into every line */
  user-select: none;
}

.line .text {
  white-space: pre;
}

/* The +, - and space belong to the presentation and not to the line, so they are
   drawn here rather than put in the text node: the text stays the file's bytes,
   which is what a highlighter tokenizes and what a copy of the pane yields. */
.line .text::before {
  content: ' ';
}

/* A file is the same line with one gutter, because it has one side, and with no
   marker in front of the text, because nothing in it is a change: a column of spaces
   all the way down would be a character in every copied line and nothing else. Both
   are here and not in the drawer, so the element a highlighter walks is one element
   in both panes. */
.file .line {
  grid-template-columns: 6ch 1fr;
}

.file .line .text::before {
  content: none;
}

.line.insert {
  background: var(--diff-insert-bg);
}

.line.insert .text::before {
  content: '+';
  color: var(--diff-insert-fg);
}

.line.delete {
  background: var(--diff-delete-bg);
}

.line.delete .text::before {
  content: '-';
  color: var(--diff-delete-fg);
}

/* And the coloring inside the text, which is a span per run and nothing at all
   between two of them (client/ui/code.ts): plain code is the text node the line
   always held and inherits the pane's own color. The tints above set a background
   and never a text color, so a token on an inserted line keeps its own and reads as
   the token it is rather than as part of the change.

   A rendered document's fenced block is the second selector on every one of these
   rules and not a block of its own: ui/markdown.ts appends the same spans in the
   same classes, it is only the element around them that differs — one <code>
   holding every line, where a pane holds a row per line. So a token gets its color
   named once and the two places cannot drift apart. */
.line .text .tok-comment,
.prose pre .tok-comment {
  color: var(--tok-comment);
}

.line .text .tok-string,
.prose pre .tok-string {
  color: var(--tok-string);
}

.line .text .tok-number,
.prose pre .tok-number {
  color: var(--tok-number);
}

.line .text .tok-keyword,
.prose pre .tok-keyword {
  color: var(--tok-keyword);
}

.line .text .tok-type,
.prose pre .tok-type {
  color: var(--tok-type);
}

.line .text .tok-function,
.prose pre .tok-function {
  color: var(--tok-function);
}

.line .text .tok-property,
.prose pre .tok-property {
  color: var(--tok-property);
}

.line .text .tok-meta,
.prose pre .tok-meta {
  color: var(--tok-meta);
}

/* A rendered markdown document, which is the file pane's other body: block-level
   elements instead of one per line, and the one block in here not set in --mono.
   The vocabulary is what ui/markdown.ts builds and no more than that. Every color
   it wants :root already has, so the dark theme needed nothing added for it, and
   a table's alignment is written on the cell by the drawer rather than matched
   here, the direction being the document's own data. */
.prose {
  max-width: var(--prose-width);
  padding: 8px 10px;
  font-family: var(--prose);
  font-size: var(--prose-size);
  line-height: 1.55;
  /* a URL in a paragraph is one word and as long as somebody made it: break it
     rather than give the pane a horizontal scrollbar over the one */
  overflow-wrap: break-word;
}

/* The first block carries the padding above and no margin of its own, or a
   document opening on a heading starts an inch down the pane. */
.prose > :first-child {
  margin-top: 0;
}

.prose h1,
.prose h2,
.prose h3,
.prose h4,
.prose h5,
.prose h6 {
  margin: 1.4em 0 0.5em;
  line-height: 1.25;
}

/* A rule under the two a document is scanned by, the way a printed page has one. */
.prose h1,
.prose h2 {
  padding-bottom: 0.2em;
  border-bottom: 1px solid var(--border);
}

.prose h1 {
  font-size: 1.7em;
}

.prose h2 {
  font-size: 1.4em;
}

.prose h3 {
  font-size: 1.2em;
}

.prose h4,
.prose h5,
.prose h6 {
  font-size: 1em;
}

.prose p,
.prose ul,
.prose ol,
.prose pre,
.prose table,
.prose blockquote {
  margin: 0 0 1em;
}

/* Enough that three levels of nesting read as three, and not so much that a list
   starts halfway across the pane. */
.prose ul,
.prose ol {
  padding-left: 1.6em;
}

/* Inside an item the last block ends where the item does: a nested list that kept
   its own bottom margin would space a tight outer list as though it were loose. */
.prose li > :last-child {
  margin-bottom: 0;
}

/* Code goes back to --mono, which is the invariant above holding rather than
   bending: what is inside a fence or a span is still a column of characters. A
   fence whose info string names a language this client reads is colored as well,
   by the .tok-* rules above that name it beside a pane's line. */
.prose code {
  font-family: var(--mono);
  font-size: 0.92em;
}

.prose pre {
  padding: 8px 10px;
  background: var(--bg-chrome);
  border-radius: 3px;
  /* a fenced block is written to a width the pane need not have, and wrapping
     somebody's code changes what it says */
  overflow-x: auto;
}

/* A span and not a block: the tint is what tells one from the prose around it,
   where a fenced block has a box of its own to do the telling. */
.prose :not(pre) > code {
  padding: 0.1em 0.3em;
  background: var(--bg-chrome);
  border-radius: 3px;
}

.prose blockquote {
  padding-left: 1em;
  border-left: 3px solid var(--border);
  color: var(--fg-dim);
}

.prose hr {
  height: 1px;
  margin: 1.5em 0;
  background: var(--border);
  border: none;
}

/* A block box, so the table scrolls inside the pane instead of widening it: a
   table is columns, and wrapping them to the width available is what makes one
   stop being readable. */
.prose table {
  display: block;
  width: max-content;
  max-width: 100%;
  border-collapse: collapse;
  overflow-x: auto;
}

.prose th,
.prose td {
  padding: 4px 10px;
  border: 1px solid var(--border);
}

.prose thead {
  background: var(--bg-chrome);
}

.prose img {
  max-width: 100%;
}

/* The box in front of a task-list item, which is a control nobody can use: it says
   whether the item is done, and ticking it would say the file changed when it did
   not. So it sits in the line rather than on it and is out of the way. */
.prose .task {
  margin: 0 0.4em 0 0;
  vertical-align: middle;
}

/* An image file drawn as itself, which is the third body the file pane has: a box
   and the one image in it, which is all ui/file.ts builds. The box shrinks to what
   it holds rather than spanning the pane, so the tint reads as a mat around the
   picture — a PNG that is transparent where it is not drawn has no edge of its own,
   and a band the width of the pane would draw none either. */
.detail .picture {
  display: inline-block;
  padding: 8px 10px;
  background: var(--bg-chrome);
}

/* A block, or the strip an inline box leaves under its baseline lands inside the
   tint and reads as padding nobody asked for. The width is the pane's to decide
   and the height follows it, since what a picture must not do is widen the pane. */
.detail .picture img {
  display: block;
  max-width: 100%;
  height: auto;
}

/* Anything the view has to say in its own voice: a binary file, a diff over the
   size cap, git's no-newline marker, a walk that ran out of budget. */
.note {
  padding: 0 10px;
  color: var(--fg-dim);
}

.error {
  padding: 8px 10px;
  color: var(--danger);
  white-space: pre-wrap;
}

/* Narrow enough that two columns would leave neither readable: stack them, the
   list over the commit, each still scrolling on its own. */
@media (max-width: 60em) {
  #app {
    grid-template-columns: 1fr;
    grid-template-rows: auto 1fr 1fr;
    grid-template-areas:
      'chrome'
      'log'
      'detail';
  }

  .log {
    border-right: none;
    border-bottom: 1px solid var(--border);
  }
}
