/* web/styles/pane.css */
/* The pane's width is the reader's to set, within bounds that keep a name readable and keep the
   document the larger half. A pane put away takes no column at all. */
.layout {
  --pane: 285px;
  display: grid;
  grid-template-columns: var(--pane) minmax(0, 1fr);
  height: 100vh;
  overflow: hidden;
  position: relative;
}

/* A pane put away leaves no column behind it. The width the reader dragged is held in a custom
   property on this element, so the columns are restated here rather than zeroed through it. */
.layout[data-pane='away'] {
  grid-template-columns: minmax(0, 1fr);
}

aside {
  padding: 1.5rem 0.4rem;
  background: var(--sidebar);
  border-right: 1px solid var(--line);
  overflow: auto;
  min-width: 0;
}

/* The pane's header stands still and only the workspaces under it scroll: a pane taller than the
   window keeps its configuration, the application and putting it away in view. */
#workspace-sidebar {
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

/* What scrolls reaches the pane's sides and foot, so its scrollbar stands at the pane's edge and
   the last row keeps the pane's own space below it. It begins a little above its first row, inside
   the space under the header, so the first row's focus ring is not cut by its top edge. */
.pane-body {
  flex: 1 1 auto;
  min-height: 0;
  overflow: auto;
  margin: -0.5rem -0.4rem -1.5rem;
  padding: 0.5rem 0.4rem 1.5rem;
}

.layout[data-pane='away'] > #workspace-sidebar {
  display: none;
}

/* The edge the pane is dragged by. It sits over the boundary rather than in the flow, so nothing
   moves when it is grabbed, and it is wider than it looks so it can be caught. */
#pane-edge {
  position: absolute;
  top: 0;
  bottom: 0;
  left: calc(var(--pane) - 3px);
  width: 7px;
  z-index: 5;
  cursor: col-resize;
  /* A finger on the edge drags the pane: the browser takes no pan or zoom from it, which would
     cancel the drag under the finger. */
  touch-action: none;
}

.layout[data-pane='away'] > #pane-edge {
  display: none;
}

#pane-edge:hover,
.layout[data-resizing='true'] #pane-edge {
  background: color-mix(in srgb, var(--accent), transparent 70%);
}

/* The page being read is a frame, and a pointer over a frame is the frame's. While the pane is
   being dragged the pointer belongs to the drag, so the frame is passed over. */
.layout[data-resizing='true'] iframe.document {
  pointer-events: none;
}

.layout[data-resizing='true'] {
  cursor: col-resize;
  user-select: none;
}

/* The control that puts the pane away sits in the pane's own top corner, on a row of its own
   above everything it would put away. */
/* The pane's own corner holds the control that puts it away, on the line the bar's controls
   stand on: the pane begins where the bar begins, so the two lines are the same line. The corner
   is the same corner on either side — `--corner-inset` is how far into its own pane each mark
   stands, and the discussion's mark is held the same way in `discussion.css`. */
.layout {
  /* How far into its own pane each corner's mark stands. It is the same distance the line stands
     below the top of the pane, so the control in the corner is held as far from the edge above it
     as from the edge beside it. */
  --corner-inset: 0.33rem;
}

/* The pane's top line: the one control that opens everything at its left, the application in the
   middle, and putting the pane away at its right, on the line the bar's controls stand on. */
.pane-top {
  /* The line the pane's own controls stand on, which is the line the bar's controls stand on:
     exactly as tall as they are, so nothing else on it can move them. The application stands in
     the middle of that line without being part of it, so its size is free. */
  position: relative;
  display: flex;
  align-items: center;
  justify-content: space-between;
  height: 21px;
  /* The pane's top padding is 1.5rem and the bar holds its controls 0.33rem down, so the line is
     lifted the difference; the pane's side padding is 0.4rem, and the rest of the inset is taken
     here. What stands below begins under the application. */
  /* The pages begin just clear of the application, which stands below the line. */
  margin: calc(0.33rem - 1.5rem) calc(var(--corner-inset) - 0.4rem) 2.5rem;
}

/* One control in two places — a pane's own corner and the bar — so it is drawn once, and drawn
   as the bar's own controls are. */
/* One control in two places — a pane's own corner and the bar — so it is drawn once: the mark
   itself, large enough to be one, and nothing around it. */
#pane-collapse,
#pane-restore,
#discussion-collapse,
#discussion-restore {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* The discussion gives its buttons room to stand apart; this one stands in a corner. */
  margin: 0;
  height: 21px;
  padding: 0 0.15rem;
  border: 0;
  border-radius: 4px;
  background: transparent;
  color: var(--muted);
  font: inherit;
  font-size: 1.2rem;
  line-height: 1;
  cursor: pointer;
}

/* The mark rides a hair above the middle of its box. Centred by its own drawn extent it reads low,
   because what stands beside it on the line — a name, a drawn mark — is taller than it is. */
#pane-collapse,
#pane-restore,
#discussion-collapse,
#discussion-restore {
  transform: translateY(-1px);
}

#pane-collapse:hover,
#pane-restore:hover,
#discussion-collapse:hover,
#discussion-restore:hover {
  color: var(--text);
}

/* The application's name and its mark, at the top of the pane, with the one control that opens
   everything about the reader and this workspace beside them. */
/* The application, in its own hand: the mark and the name are both drawn, in the text colour around
   them, so nothing is fetched to write the name and no font decides how it is written. */
.brand {
  position: absolute;
  /* In the middle of the line, less a hair: the mark is lighter than the word beside it, so
     geometric centre reads as sitting to the right of centre. */
  left: calc(50% - 0.35rem);
  /* Below the line rather than across it: the mark is taller than the controls beside it, and it
     stands clear of the top of the pane. */
  top: calc(50% + 0.9rem);
  transform: translate(-50%, -50%);
  display: flex;
  align-items: center;
  color: var(--text);
}

/* The mark and the name are one drawing, so only its width is ever set: how they stand against
   each other is inside it, and is the same wherever it is drawn. */
.brand-lockup {
  flex: 0 0 auto;
  width: 7.25rem;
  height: auto;
}

/* One control, opening what is about the reader and the workspace rather than about the page. */
.pane-menu {
  position: relative;
  /* What stands on the line is the control, not the disclosure around it: a closed disclosure
     still reports a box of its own, and that box is not what the line is made of. */
  display: flex;
  align-items: center;
  height: 21px;
  margin: 0;
}

.pane-menu > summary {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.35rem;
  height: 21px;
  margin: 0;
  /* The same box the control in the other corner stands in, so both are held the same way in. */
  padding: 0 0.15rem;
  border-radius: 5px;
  color: var(--muted);
  cursor: pointer;
  list-style: none;
}

.pane-menu > summary::-webkit-details-marker,
.pane-menu > summary::marker {
  display: none;
  content: '';
}

.pane-menu > summary:hover,
.pane-menu[open] > summary {
  background: var(--raised);
  color: var(--text);
}

/* The control's own mark. It is drawn larger than the mark opposite it without moving: the box it
   stands in and how far that box is held inside the pane's edge are the line's, not the mark's. */
.pane-gear {
  width: 1.05rem;
  height: 1.05rem;
}

.pane-menu-panel {
  position: absolute;
  z-index: 80;
  top: calc(100% + 4px);
  left: 0;
  min-width: 14rem;
  padding: 0.5rem;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--background);
  box-shadow: 0 8px 24px var(--shadow);
  font-size: 0.85rem;
}

.pane-menu-panel .account {
  display: block;
  margin: 0;
  padding: 0.35rem 0.5rem;
  font-size: 0.85rem;
}

.pane-menu-panel .account strong,
.pane-menu-panel .account span {
  display: block;
}

/* The way out of the application stands at the foot of what the control holds, under a rule:
   everything above it is something to go to, and this is the one thing that ends the reading. */
.pane-menu-foot {
  margin-top: 0.5rem;
  padding-top: 0.5rem;
  border-top: 1px solid var(--line);
}

.pane-menu-foot form {
  margin: 0;
}

/* Drawn as the rows above it are: a line of the menu, not a box set into it. */
.pane-menu-foot button {
  display: block;
  width: 100%;
  padding: 0.35rem 0.5rem;
  border: 0;
  border-radius: 6px;
  background: transparent;
  color: var(--text);
  font-size: 0.85rem;
  text-align: left;
}

.pane-menu-foot button:hover {
  background: var(--raised);
}

/* A line of the menu: a mark and the words that say what pressing it does (the theme), or just the
   words (the settings). The two are drawn alike, so the menu reads as one list. */
.pane-menu-panel > button {
  display: flex;
  gap: 0.4rem;
  align-items: center;
  width: 100%;
  margin: 0.35rem 0;
  padding: 0.35rem 0.5rem;
  border: 0;
  border-radius: 6px;
  background: transparent;
  color: var(--text);
  font: inherit;
  font-size: 0.85rem;
  text-align: left;
}

.pane-menu-panel > button > svg {
  width: 1.05rem;
  height: 1.05rem;
  flex: 0 0 auto;
}

.pane-menu-panel > button:hover {
  background: var(--raised);
}

/* The workspaces, each a fold of its own. The room between two of them is the rule between
   them and the same distance either side of it, stated once here and nowhere else, so a name
   folded away stands as far from the rule above it as from the rule below it. */
.workspaces {
  display: flex;
  flex-direction: column;
}

/* Every row after the first, with or without a notice under the row before it. */
.workspaces > .workspace ~ .workspace {
  border-top: 1px solid var(--line);
  margin-top: 0.6rem;
  padding-top: 0.6rem;
}

nav.workspaces[data-dragging] { user-select: none; }
.workspace-order-source > summary { opacity: 0.3; }
.workspace-order-shift { transform: translateY(8px); transition: transform 150ms; }
.workspace-drag-ghost {
  position: fixed;
  z-index: 1000;
  box-sizing: border-box;
  display: flex;
  align-items: center;
  overflow: hidden;
  padding: 0.3rem;
  border-radius: 5px;
  background: var(--sidebar);
  box-shadow: 0 3px 14px color-mix(in srgb, var(--text) 18%, transparent);
  color: var(--text);
  font-size: 1.15rem;
  font-weight: 700;
  line-height: 1.5;
  pointer-events: none;
}
.workspace-drop-line {
  position: fixed;
  z-index: 1001;
  height: 2px;
  background: var(--accent);
  pointer-events: none;
}
@media (prefers-reduced-motion: reduce) {
  .workspace-order-shift { transition: none; }
}

/* The workspace's own line: the pages below it are what it holds, so it is drawn as their
   container rather than as another page — the application's own face, written larger and heavier
   than a page's entry, with the room above and below that says the pages belong to it. */
.workspace > summary {
  font-size: 1.15rem;
  font-weight: 700;
  letter-spacing: 0.005em;
  line-height: 1.5;
  min-height: 2.1rem;
  margin-bottom: 0.45rem;
  padding: 0.3rem;
  border-radius: 5px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  cursor: pointer;
  list-style: none;
}

/* Folded away, the name is all there is of the workspace: it keeps as much space above it as
   below it, and the room to what follows is the rule's own. */
.workspace:not([open]) > summary {
  margin-bottom: 0;
}

.workspace > summary::-webkit-details-marker,
.workspace > summary::marker {
  display: none;
  content: '';
}

.workspace > summary:hover {
  background: var(--raised);
}

/* The workspace's name on its fold's line, with what the workspace offers at the end of it: the
   same row a page's entry is, so the control is reached the same way. */
.workspace-title {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.workspace-more {
  position: absolute;
  top: 50%;
  right: 0.15rem;
  transform: translateY(-50%);
  display: grid;
  place-items: center;
  width: 1.4rem;
  height: 1.4rem;
  padding: 0;
  border: 0;
  border-radius: 4px;
  background: var(--row, transparent);
  color: var(--muted);
  font-size: 0.8rem;
  line-height: 1;
  cursor: pointer;
  opacity: 0;
  pointer-events: none;
}

.workspace > summary:hover .workspace-more,
.workspace-more:focus-visible {
  opacity: 1;
  pointer-events: auto;
}

/* What separates the workspaces from making another. */
.pane-rule {
  margin: 1.1rem 0 0.6rem;
  border: 0;
  border-top: 1px solid var(--line);
}

/* The page tree, as the pane draws it and as "Move into…" draws a copy of it to pick a
   destination from (`reader/move-picker.mjs`): the same rules draw both, at the specificity the
   pane's alone had (the picker is named in `:where`). */
:is(aside, :where(.move-picker)) nav {
  font-size: 0.87rem;
  line-height: 1.35;
}

/* One entry is a row: the page's name, and the controls that act on it at the row's end. The
   controls appear when the row is reached and take no room when they are not there, so the tree
   reads as a list of pages rather than as a list of buttons. */
.leaf,
:is(aside, :where(.move-picker)) summary {
  position: relative;
  display: flex;
  align-items: center;
  gap: 1px;
  min-width: 0;
  border-radius: 5px;
}

/* The name fills the row and is cut rather than wrapped: a row is a row. It is a block rather
   than a flex line, because the ellipsis a block gives its text is the point. */
:is(aside, :where(.move-picker)) nav a {
  display: block;
  flex: 1;
  min-width: 0;
  padding: 0.32rem 0.25rem;
  color: var(--text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* A folder occupies the same name column as a page but has no document address. */
:is(aside, :where(.move-picker)) nav .folder {
  display: block;
  flex: 1;
  min-width: 0;
  padding: 0.32rem 0.25rem;
  color: var(--muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

:is(aside, :where(.move-picker)) nav .folder .control-glyph {
  width: 0.95rem;
  height: 0.95rem;
  margin-right: 0.35rem;
  vertical-align: -0.15em;
}

:is(aside, :where(.move-picker)) summary:has(.folder) .page-more { right: 0.15rem; }

/* A row is highlighted by its row, not by underlining its name. */
:is(aside, :where(.move-picker)) nav a,
:is(aside, :where(.move-picker)) nav a:hover {
  text-decoration: none;
}

/* The mark stands beside the name rather than against it, at the height of the line it is on. */
:is(aside, :where(.move-picker)) nav .page-emoji {
  margin-right: 0.3em;
}

:is(aside, :where(.move-picker)) nav .page-glyph {
  flex: 0 0 auto;
  width: 0.95rem;
  height: 0.95rem;
  margin-right: 0.35rem;
  vertical-align: -0.15em;
}

.leaf,
:is(aside, :where(.move-picker)) summary {
  padding-right: 0.15rem;
}

.leaf,
:is(aside, :where(.move-picker)) summary {
  --row: transparent;
  background: var(--row);
}

.leaf:hover,
:is(aside, :where(.move-picker)) summary:hover {
  --row: var(--raised);
}

:is(aside, :where(.move-picker)) nav a[aria-current='page'] {
  font-weight: 650;
}

/* A page its parent does not link: its name is italic, and the mark that writes the link stands on
   the left of the name, in the alert colour, whether or not the row is reached, since it says
   something about the page rather than offering to act on any. */
:is(aside, :where(.move-picker)) nav a[data-linked='false'] {
  font-style: italic;
}

.page-orphan {
  flex: 0 0 auto;
  width: 1.1rem;
  height: 1.1rem;
  margin-right: 0.15rem;
  padding: 0;
  border-radius: 4px;
  background: transparent;
  color: var(--alert);
  font-weight: 700;
  line-height: 1;
}

.page-orphan:hover,
.page-orphan:focus-visible {
  background: var(--raised);
}

/* A mark whose page is being linked says so until the commit draws the pane again; it is dimmed
   as every entry a command works on is (`editing-bar.css`). */
.page-orphan[data-command] {
  cursor: progress;
}

/* A new page whose first commit is under way stays in its place, drawn fainter until the tree the
   server draws holds it. */
:is(aside, :where(.move-picker)) nav a.is-committing {
  opacity: 0.55;
}

.leaf:has(a[aria-current='page']),
:is(aside, :where(.move-picker)) summary:has(a[aria-current='page']) {
  --row: var(--held);
}

:is(aside, :where(.move-picker)) summary {
  cursor: pointer;
  list-style: none;
}

:is(aside, :where(.move-picker)) summary::-webkit-details-marker,
:is(aside, :where(.move-picker)) summary::marker {
  display: none;
  content: '';
}

/* Depth is one step of indentation, taken from the triangle's own width so a child's name lines
   up under its parent's rather than under its parent's triangle. A page's problem rows stand one
   step in, right after its entry, as its children do. */
.children,
.page-problems {
  padding-left: 0.75rem;
  border-left: 1px solid var(--line);
  margin-left: 0.45rem;
}

/* The triangle that opens a page's children, drawn rather than typed so no font decides whether
   it appears. A page with no children keeps its room and shows nothing. */
.page-twist {
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  width: 0.9rem;
  height: 1.7rem;
  color: var(--muted);
}

.page-twist::before {
  content: '';
  border-style: solid;
  border-width: 0.3rem 0 0.3rem 0.42rem;
  border-color: transparent transparent transparent currentcolor;
  transform: rotate(90deg);
}

.page-twist[data-twist='none']::before {
  border-color: transparent;
}

details:not([open]) > summary > .page-twist::before {
  transform: none;
}

/* A triangle with pages under it is a control: pressed, it is drawn in the text's colour. A
   finger's tap goes to the nearest element that answers a press, and an element styled for a
   press is one (Chrome's touch adjustment): a tap on the triangle folds its pages rather than
   following the page's name beside it (`tests/browser/pane-twist-touch.mjs`). */
summary > .page-twist:not([data-twist='none']):active {
  color: var(--text);
}

/* Making a workspace is drawn as adding a page is: they are the same kind of act. */
.workspace-new {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  width: 100%;
  padding: 0.25rem 0.35rem;
  border-radius: 5px;
  background: transparent;
  color: var(--muted);
  font-size: 0.85rem;
}

.workspace-new:hover {
  background: var(--raised);
  color: var(--text);
  text-decoration: none;
}

/* The controls on a row.
 *
 * They stand over the row's end rather than in it, so a name has the whole width until a reader
 * reaches for them; the name is then held back far enough to pass behind neither. Out of view they
 * take no pointer either, so the row's right end is the row until they are there. The keyboard
 * reaches them regardless, and reaching one shows it. */
.page-add,
.page-more {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  display: grid;
  place-items: center;
  width: 1.4rem;
  height: 1.4rem;
  padding: 0;
  border: 0;
  border-radius: 4px;
  background: transparent;
  color: var(--muted);
  font-size: 0.8rem;
  line-height: 1;
  cursor: pointer;
  opacity: 0;
  pointer-events: none;
}

.page-more {
  right: 1.55rem;
}

.page-add {
  right: 0.15rem;
}

.leaf:hover .page-add,
.leaf:hover .page-more,
:is(aside, :where(.move-picker)) summary:hover .page-add,
:is(aside, :where(.move-picker)) summary:hover .page-more,
.page-add:focus-visible,
.page-more:focus-visible {
  opacity: 1;
  pointer-events: auto;
}

/* Reached, the controls stand over the row's end and the name fades out behind them.
 *
 * They mask rather than shrink it: a name that was cut short the moment the pointer arrived would
 * be an entry that changes size under the pointer about to press it. */
.page-add,
.page-more {
  background: var(--row);
}

.leaf::after,
:is(aside, :where(.move-picker)) summary::after {
  content: '';
  position: absolute;
  top: 0;
  bottom: 0;
  right: 2.95rem;
  width: 1.1rem;
  background: linear-gradient(to right, transparent, var(--row));
  opacity: 0;
  pointer-events: none;
}

.leaf:hover::after,
:is(aside, :where(.move-picker)) summary:hover::after,
.leaf:focus-within::after,
:is(aside, :where(.move-picker)) summary:focus-within::after {
  opacity: 1;
}

.page-add:hover,
.page-more:hover {
  background: var(--pressed);
  color: var(--text);
}

/* Adding a page at the top of the tree reads as one more row, because that is what it makes. */
/* Adding a page and making a workspace are the same offer in two places, so they are written the
   same way. */
.page-new,
.workspace-trash {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  width: 100%;
  margin-top: 0.3rem;
  padding: 0.25rem 0.35rem;
  border: 0;
  border-radius: 5px;
  background: transparent;
  color: var(--muted);
  font: inherit;
  font-size: 0.85rem;
  text-align: left;
  cursor: pointer;
}

.page-new:hover,
.workspace-trash:hover {
  background: var(--raised);
  color: var(--text);
}

.workspace-trash svg {
  width: 1rem;
  height: 1rem;
}

#trash-modal {
  --dialog-width: 36rem;
  --dialog-height: 35rem;
}

#trash-modal header {
  display: flex;
  align-items: center;
  justify-content: space-between;
}

#trash-list > button {
  display: block;
  width: 100%;
  padding: 0.65rem;
  border: 0;
  border-bottom: 1px solid var(--line);
  background: transparent;
  color: var(--text);
  text-align: left;
  cursor: pointer;
}

#trash-list > button:hover,
#trash-list > button:focus-visible {
  background: var(--raised);
}

#trash-list small {
  display: block;
  color: var(--muted);
}

#trash-preview {
  border-top: 1px solid var(--line);
  padding-top: 0.6rem;
}

#trash-files {
  max-height: 6rem;
  overflow: auto;
  margin: 0.4rem 0;
  padding-left: 1.4rem;
}

/* What a page offers, opened over the entry it acts on.
 *
 * The editor's own menus are the other menus in this application, and a reader meets both in the
 * same session, so this one is built to their measurements: their surface, their shadow, their
 * item height and weight. The tokens are restated rather than shared because the editor's
 * stylesheet is loaded inside the document frame and this menu is the shell's. */
.entry-menu {
  --menu-surface: var(--background);
  --menu-on-surface: var(--text);
  --menu-hover: var(--raised);
  position: fixed;
  z-index: 80;
  display: flex;
  flex-direction: column;
  min-width: 15rem;
  padding: 0 12px 12px;
  border-radius: 12px;
  background: var(--menu-surface);
  box-shadow:
    0 1px 3px 1px rgb(0 0 0 / 15%),
    0 1px 2px 0 rgb(0 0 0 / 30%);
  color: var(--menu-on-surface);
  font-family: 'Noto Sans', Arial, Helvetica, sans-serif;
}

.entry-menu button,
.entry-menu a {
  display: flex;
  align-items: center;
  gap: 0.65em;
  padding: 14px 10px;
  border: 0;
  border-radius: 8px;
  background: transparent;
  color: inherit;
  font: inherit;
  font-size: 0.875rem;
  font-weight: 600;
  line-height: 20px;
  text-align: left;
  cursor: pointer;
}

.entry-menu .menu-icon {
  display: inline-grid;
  place-items: center;
  flex: 0 0 1em;
  width: 1em;
  height: 1em;
}

.entry-menu [aria-busy=true] {
  opacity: 0.6;
  cursor: wait;
}

.entry-menu .menu-icon svg {
  width: 1em;
  height: 1em;
  margin: 0;
}

.entry-menu button:first-child {
  margin-top: 12px;
}

.entry-menu button:hover,
.entry-menu button:focus-visible,
.entry-menu a:hover,
.entry-menu a:focus-visible {
  background: var(--menu-hover);
  outline: none;
}

/* Naming a page happens where the page is listed, in the place its name will occupy. */
.page-rename {
  flex: 1;
  min-width: 0;
  padding: 0.2rem 0.25rem;
  border: 1px solid var(--accent);
  border-radius: 5px;
  font: inherit;
  font-size: 0.87rem;
}

/* The title field uses the row's width while its page controls stay out of its way. */
.leaf:has(.page-rename) .page-add,
.leaf:has(.page-rename) .page-more,
:is(aside, :where(.move-picker)) summary:has(.page-rename) .page-add,
:is(aside, :where(.move-picker)) summary:has(.page-rename) .page-more {
  visibility: hidden;
}

.leaf:has(.page-rename)::after,
:is(aside, :where(.move-picker)) summary:has(.page-rename)::after {
  opacity: 0;
}

/* What the pane says about a commit stands under the entry it is about, indented with it. */
#workspace-sidebar .notice {
  margin: 0.2rem 0 0.2rem 1.1rem;
  font-size: 0.8rem;
}

#latest-revision {
  flex: 0 0 auto;
  font-size: 0.85rem;
}

.reader {
  display: flex;
  flex-direction: column;
  min-width: 0;
  height: 100vh;
  min-height: 0;
  overflow: hidden;
}

.toolbar {
  background: var(--reader-toolbar-background, var(--sidebar));
  display: flex;
  justify-content: flex-start;
  gap: 0.35rem;
  /* Its line is drawn from the top, in every state: what grows on it (a notice that wraps, in
     the editing controls) grows the bar downward, and nothing on the line moves. Centred, every
     control would be placed again in the taller line. */
  align-items: flex-start;
  /* Controls keep their own size and wrap to a second row rather than crushing each other. */
  flex-wrap: wrap;
  row-gap: 0.3rem;
  padding: 0.25rem 1rem;
  border-bottom: 1px solid var(--line);
  font-size: 0.78rem;
  line-height: 1.2;
}

/* A narrower window gives the pane less room. It is said as the pane's width rather than as the
   column it makes, so everything placed against that width — the edge it is dragged by, above —
   is where the pane actually ends. */
@media (max-width: 1100px) {
  .layout {
    --pane: 230px;
  }
}
/* web/styles/bar.css */
/* The reader's bar lays its controls out by the width it is given (the `@container` rules below). */
.reading-area > .toolbar {
  container-type: inline-size;
}

/* The bar says what acts on the page, and its controls stand in two blocks: the page's own, from its
   left edge, and the block that acts on how the page is read, from here. The theme is not one of
   them — it is the window's and stands in the pane's settings menu (`pane.css`). What is waiting to
   be read begins the second block, and carries the room that sets it against the first. */
#unread {
  margin-left: auto;
}

/* The top bar says what acts on the page, and there is more of that than a phone has width for.
   Each control names itself in full where there is room and by its mark where there is not; the
   name it is reached by does not change with the width. */
.bar-short {
  display: none;
}

.tools {
  display: flex;
  gap: 1rem;
  white-space: nowrap;
}

.context {
  padding: 0.65rem 1.5rem;
  color: var(--muted);
  font-size: 0.75rem;
  border-bottom: 1px solid var(--line);
}

.context details {
  display: inline-block;
  margin-left: 0.5rem;
  vertical-align: top;
}

.context details[open] {
  display: block;
  margin: 0.5rem 0;
}

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

.context input,
.context select {
  width: auto;
  margin: 0;
  padding: 0.25rem;
}

.context button {
  font-size: 0.75rem;
  padding: 0.3rem 0.6rem;
}

.document {
  border: 0;
  width: 100%;
  flex: 1;
  min-height: 0;
  background: var(--background);
}

/* The file selector, put away when the blocks stand closer than two buttons: drawn by the room the
   bar has (`view.mjs`), never by a width of its own. */
.reading-area > .toolbar > #page-trail[hidden] {
  display: none;
}
/* web/styles/phone.css */
/* The phone.
   The page is the window. What a desktop puts around it — the pages down the left, the bar across
   the top — each becomes a pane that comes in from the left edge and leaves again, and each stands
   behind a round control floating over the page. Nothing is duplicated: the pane is the same bar,
   moved. */
.phone-controls {
  display: none;
}

/* The pane a phone reads what is waiting in; a wider window has the bar's own fold for that. */
#unread-pane {
  display: none;
}

@media (max-width: 760px) {
  .layout {
    display: block;
    position: relative;
    height: 100dvh;
    overflow: hidden;
  }

  .layout > aside,
  .reading-area > .toolbar,
  #unread-pane {
    /* The band at the window's foot that the phone's own controls float over (`.phone-controls`).
       Each pane keeps it as a clear border its own background shows through: a border is outside
       what scrolls, so nothing in a pane scrolls under the controls, however much it holds. */
    --phone-controls-band: calc(2.9rem + max(0.75rem, env(safe-area-inset-bottom)) + 0.45rem);
    border-bottom: var(--phone-controls-band) solid transparent;
    position: fixed;
    z-index: 30;
    max-width: none;
    max-height: none;
    overflow: auto;
    overscroll-behavior: contain;
    background: var(--sidebar);
    transition: transform 0.16s ease;
    /* A pane on its way out is already out of the way: what it is sliding off is the page, and a
       press meant for the page must not land on a pane that is leaving. */
    pointer-events: none;
  }

  /* The pages come from the left, where the pages stand on a wider window. */
  .layout > aside {
    inset: 0 auto 0 0;
    width: min(88vw, 340px);
    border-right: 1px solid var(--line);
    transform: translateX(-105%);
  }

  /* The bar rises from the foot of the window, where a thumb is, and takes its width. Its foot is
     the controls' band, as every pane's is (above). */
  .reading-area > .toolbar {
    inset: auto 0 0 0;
    width: auto;
    max-height: 72dvh;
    border-top: 1px solid var(--line);
    border-radius: 14px 14px 0 0;
    transform: translateY(105%);
  }

  /* What is waiting to be read comes from the right, the whole height, with its way out in the
     corner it opened from. */
  #unread-pane {
    display: flex;
    flex-direction: column;
    inset: 0 0 0 auto;
    width: min(88vw, 340px);
    padding: 0 0.75rem 1rem;
    border-left: 1px solid var(--line);
    transform: translateX(105%);
  }

  .layout.pages-open > aside,
  .layout.tools-open .reading-area > .toolbar,
  .layout.unread-open #unread-pane {
    transform: none;
    pointer-events: auto;
  }

  .unread-pane-top {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 0.9rem 0 0.4rem;
  }

  .unread-pane-top h2 {
    margin: 0;
    font-size: 1rem;
  }

  #unread-pane-close {
    display: grid;
    place-items: center;
    width: 2.2rem;
    height: 2.2rem;
    padding: 0;
    border: 1px solid var(--line);
    border-radius: 50%;
    background: var(--background);
    color: var(--text);
  }

  /* The pane's own head names it, so the panel's line says only what acts on the list. */
  #unread-pane .unread-top > span {
    display: none;
  }

  #unread-pane .unread-top {
    justify-content: flex-end;
    padding-top: 0;
  }

  /* In the pane the list is the pane's content, not a panel floating over something. */
  #unread-pane .unread-panel {
    position: static;
    width: auto;
    max-height: none;
    padding: 0;
    border: 0;
    border-radius: 0;
    background: transparent;
    box-shadow: none;
  }

  /* A finger has no hover to reveal a row's controls with, so a row carries them from the start,
     drawn large enough to be pressed by one. The name behind them is masked the same way it is
     masked when a pointer arrives over the row. */
  .page-add,
  .page-more,
  .workspace-more {
    width: 1.8rem;
    height: 1.8rem;
    font-size: 1rem;
    opacity: 1;
    pointer-events: auto;
  }

  /* The mark of a page its parent does not link is pressed by a finger too. */
  .page-orphan {
    width: 1.8rem;
    height: 1.8rem;
    font-size: 1rem;
  }

  .page-more {
    right: 1.95rem;
  }

  .leaf::after,
  :is(aside, :where(.move-picker)) summary::after {
    right: 3.75rem;
    opacity: 1;
  }

  /* A finger is not a pointer. The pane's own corner control is drawn large enough to be pressed
     by one, from the corner it is drawn from on every other window. */
  .pane-top {
    height: auto;
    align-items: flex-start;
  }

  .pane-gear {
    width: 1.6rem;
    height: 1.6rem;
  }

  /* The bar holds the same controls it holds on a wider window, drawn the same way, in two lines
     that follow the wider bar's two sides: what the page in view is — its history, its changes and
     the page — and then what acts on it, from keeping it offline and the draft's controls on. A
     line wraps only where a phone is narrower than it. */
  .reading-area > .toolbar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    align-content: start;
    /* The break between the two sides is a line of its own with no height, and so stands between
       two of these gaps: the sides stand twice as far apart as a side's own wrapped lines. */
    gap: 0.45rem 0.6rem;
    min-height: 0;
    padding: 1.1rem 0.75rem 0.45rem;
  }

  .reading-area > .toolbar > * {
    max-width: 100%;
    margin: 0;
  }

  .reading-area > .toolbar::after {
    content: '';
    order: 1;
    flex: 0 0 100%;
    height: 0;
  }

  /* What acts on the page stands on the first row of its line: the draft's controls wrap what
     became of the draft under them (below), and the controls beside them keep to their row. */
  .reading-area > .toolbar > #offline-status,
  .reading-area > .toolbar > #offline-status ~ *,
  .reading-area > .toolbar > #editing-controls,
  .reading-area > .toolbar > #editing-controls ~ * {
    order: 2;
    align-self: flex-start;
  }

  /* The page's size stands at the right end of the draft's row, just left of the comments' button
     (the user, 2026-10-20); the comments' own controls are the small bar's (`comment-settings.css`).
     The theme is the pane's, in its settings menu (6.2). */
  .reading-area > .toolbar > #document-text-size {
    margin-left: auto;
  }

  /* Both stand in the row of bordered controls: their height, centred on it. */
  .reading-area > .toolbar > #document-text-size > summary,
  .reading-area > .toolbar > #comment-tools-toggle {
    display: flex;
    align-items: center;
    height: 24px;
  }

  /* The page gives up room before a control does; its whole name is on the trail it opens. */
  .reading-area > .toolbar > #page-trail {
    flex: 0 1 auto;
    min-width: 0;
  }

  /* An open history or menu opens inside the sheet, on a line of its own, as a panel does. The
     size menu and the comment settings float instead, so they take no line. The settings are
     named by attribute, which keeps this rule's weight under the controls' own. */
  .reading-area > .toolbar > details[open]:not(.text-size, [id='comment-filter-options']) {
    flex-basis: 100%;
  }

  /* The phone frame is already narrower than the reading measure. */
  .reading-area > .toolbar > #reading-width {
    display: none;
  }

  /* The size menu floats over the page, as on a wider window, so opening it leaves the sheet as
     it is. The sheet stands at the foot of the screen, so the menu opens above its control. */
  .reading-area > .toolbar .text-size > div {
    top: auto;
    bottom: calc(100% + 4px);
    /* It stands near the start of its row, so it opens from its control's start. */
    left: 0;
    right: auto;
  }

  /* The comment settings open above their control, from its start: the row begins at the sheet's
     own edge, and a panel as wide as the sheet's room opens past it from its end. */
  .reading-area > .toolbar #comment-filter-options > div {
    top: auto;
    bottom: calc(100% + 4px);
    left: 0;
    right: auto;
  }

  /* What is waiting to be read has a control and a pane of its own on a phone, so the bar's own
     fold for it has nothing to do here. Neither has the way back to a put-away discussion: a
     phone reaches a discussion from the text, and a sheet holding nothing is not one. */
  .reading-area > .toolbar > #unread,
  .reading-area > .toolbar > #discussion-restore {
    display: none;
  }

  /* A draft's controls stand on their side's line with the rest, and wrap with it. */
  .reading-area > .toolbar > #editing-controls {
    min-width: 0;
    flex-wrap: wrap;
  }

  .reading-area #edit-live {
    flex-wrap: wrap;
    min-width: 0;
  }

  /* The bar's sheet stands at the foot of the window, so what became of the draft is said on a
     line of its own inside it, in full, rather than under the control and out of the window. Its
     text does not widen the sheet's columns: it wraps in the width it is given. */
  .reading-area > .toolbar #editing-controls > .notice {
    flex: 0 0 100%;
    max-width: none;
    contain: inline-size;
    /* A line of its own: nothing beside it to stand level with. */
    margin-top: 0;
  }



  /* The page is behind the pane, and reads as behind it. */
  .layout.pages-open::after,
  .layout.tools-open::after,
  .layout.unread-open::after {
    content: '';
    position: fixed;
    inset: 0;
    z-index: 29;
    background: rgb(36 51 46 / 0.24);
  }

  /* The pane is unfolded and folded by its own control over the page, so the pane's own corner
     mark and the edge it is dragged by have nothing to do here. A phone has room for one
     presentation, so the choice between them is not offered either. */
  #pane-collapse,
  #pane-edge,
  #discussion-mode {
    display: none;
  }

  /* A mark in the unfolded bar is pressed with a finger rather than pointed at, so it is given
     what a finger takes. */
  .reading-area > .toolbar .bar-mark {
    width: 1.8rem;
    height: 1.8rem;
  }
  .reading-area > .toolbar .bar-mark.bar-word {
    width: auto;
  }

  /* So is every bordered control: no shorter than a finger. */
  .reading-area > .toolbar {
    --bar-control-height: 24px;
  }

  .brand {
    font-size: 1.3rem;
  }

  :is(aside, :where(.move-picker)) nav {
    margin-top: 0.5rem;
  }

  .workspace-new {
    margin-top: 0.6rem;
  }

  .reader {
    height: 100dvh;
    min-height: 0;
  }

  /* The two controls the page is read with, within reach of a thumb and above the panes they
     open, so the control that opened one is where the reader left it. */
  .phone-controls {
    display: flex;
    position: fixed;
    z-index: 35;
    left: max(0.75rem, env(safe-area-inset-left));
    bottom: max(0.75rem, env(safe-area-inset-bottom));
    gap: 0.5rem;
  }

  .phone-control {
    display: grid;
    place-items: center;
    width: 2.9rem;
    height: 2.9rem;
    padding: 0;
    /* It stands over a page whose own colours are the author's, so it carries its own edge. */
    border: 1px solid color-mix(in srgb, var(--text) 30%, var(--line));
    border-radius: 50%;
    background: var(--background);
    color: var(--text);
    box-shadow: 0 2px 12px rgb(36 51 46 / 0.22);
  }

  /* While a menu the page's editor draws stands over the window (`reader/editing-controls.mjs`
     `menu`), the round controls step aside: they stand at the window's foot, where the menu's last
     entries are, and an entry under one could not be pressed. */
  body:has(#editing-controls[data-menu]) .phone-controls {
    display: none;
  }

  .phone-control[aria-expanded='true'] {
    background: var(--accent);
    border-color: var(--accent);
    color: var(--background);
  }

  #phone-unread {
    position: relative;
  }

  #phone-unread-count {
    position: absolute;
    top: -2px;
    right: -2px;
    min-width: 16px;
    padding: 0 4px;
    border-radius: 8px;
    background: var(--accent);
    color: var(--background);
    font-size: 0.65rem;
    line-height: 16px;
    text-align: center;
  }

  .phone-control[aria-expanded='true'] #phone-unread-count {
    background: var(--background);
    color: var(--accent);
  }

  .context {
    padding: 0.6rem 0.75rem;
  }
}

.layout aside .account {
  display: block;
}

.layout aside .account form {
  margin: 0.35rem 0 0;
}

.layout aside .account button {
  font-size: 0.75rem;
  padding: 0.35rem 0.5rem;
}
/* web/styles/notices.css */
/* A page whose authored link could not be resolved in this revision. Present and legible,
   so a missing page reads as broken rather than as absent. */
.page-defect {
  display: inline-block;
  padding: 0.5rem 0.3rem;
  color: var(--alert);
  cursor: help;
}

.page-defect-mark {
  font-weight: 700;
  margin-right: 0.35rem;
}

.page-defect-reason {
  color: color-mix(in srgb, var(--alert) 60%, var(--muted));
  font-size: 0.85em;
}

/* The notice's slot is there with no problem too, so the browser draws into it in place
   (`reader/hierarchy-problems.mjs`): it takes no box of its own, and the notice stands in the bar. */
#hierarchy-problems-notice {
  display: contents;
}

#hierarchy-defects {
  color: var(--alert);
  font-weight: 600;
}

#hierarchy-defects summary {
  cursor: pointer;
  white-space: nowrap;
}

#hierarchy-defects ul {
  position: absolute;
  z-index: 5;
  margin: 0.3rem 0 0;
  padding: 0.6rem 0.9rem 0.6rem 1.8rem;
  background: var(--background);
  border: 1px solid color-mix(in srgb, var(--alert) 35%, var(--line));
  border-radius: 6px;
  font-weight: 400;
  max-width: 34rem;
}

/* The revision in view is not the branch's own, because the branch could not be read. It sits in
   the bar beside the hierarchy's own problems: both are facts about what is being looked at. */
#source-notice {
  color: var(--alert);
  font-weight: 600;
  white-space: nowrap;
  cursor: help;
}
/* web/styles/editing-bar.css */
/* Editing controls sit with the page's trail, so the page has one place that acts on it.
   Every control is the same shape (`.bar-button`). */
#editing-controls {
  position: relative;
  display: flex;
  /* Drawn from the top, as the bar's line is (`pane.css` `.toolbar`): a notice that wraps grows
     the controls' box downward and moves none of them. */
  align-items: flex-start;
  gap: 0.35rem;
  /* The bar is a flex row: without this the controls would compress the trail to nothing and
     wrap their own labels rather than taking the width they need. */
  flex: 0 0 auto;
}

/* The bar holds the history, the trail, the editing controls and the presentation choice, and it
   stays a thin strip above the page: its controls are as tall as their own text and no more. What
   is drawn over a control's mark (`offline.css`), and what is said beside the controls, is placed
   from this height. */
.toolbar {
  --bar-control-height: 21px;
}

/* The bar's bordered control: undo, redo, Commit, Markdown, Lock, Restore, the offline copy and the
   comparison's toggles are one shape with one edge, on the control surface. None is filled: Commit
   is told by its word, as the others are by their marks. Stated within the bar, so the shell's
   general button rule does not reach it. */
.toolbar .bar-button {
  display: inline-flex;
  align-items: center;
  height: var(--bar-control-height);
  padding: 0 0.7rem;
  border: 1px solid var(--control-line);
  border-radius: 4px;
  background: var(--control);
  color: var(--control-text);
  font: inherit;
  line-height: 1;
  /* A wrapped label in a squeezed box reads as a blank square. */
  white-space: nowrap;
  cursor: pointer;
}

/* Only a pointer that hovers marks a control under it: a touch leaves its hover behind on the
   control it pressed, which would then read as pressed. */
@media (hover: hover) {
  .bar-button:hover:not([disabled]) {
    border-color: var(--pressed);
    background: var(--raised);
  }
}

.bar-button[disabled] {
  opacity: 0.45;
  cursor: default;
}


/* Two presses a reader reads as one control of two ends: Undo with Redo, and discarding the
   page's changes with Commit (Task 6, 6.18 and 6.19). The ends meet with no gap, the line between
   them is the pair's own single line, and only the outer corners are rounded. */
.bar-pair {
  display: inline-flex;
  align-items: stretch;
}

.bar-pair > .bar-button {
  border-radius: 0;
}

.bar-pair > .bar-button:first-child {
  border-start-start-radius: 4px;
  border-end-start-radius: 4px;
}

.bar-pair > .bar-button:last-child {
  border-start-end-radius: 4px;
  border-end-end-radius: 4px;
}

/* One line between the two: the first end draws it, the second states none. */
.bar-pair > .bar-button + .bar-button {
  border-inline-start-width: 0;
}

.bar-pair > .bar-button:not(:last-child) {
  border-inline-end: 1px solid var(--control-line);
}

/* Commit's word gives the pair its width; discarding takes the room its mark needs. */
.bar-pair > #edit-restore {
  padding: 0 0.45rem;
}

#edit-live {
  display: flex;
  align-items: center;
  gap: 0.35rem;
}

/* The line a draft's controls break at where they are given one width, and nothing where they
   are given a line of their own. */
.edit-break {
  display: none;
}

/* On a bar narrower than a desktop window's the page's name is held shorter; its whole name is on
   the trail it opens. */
@media (min-width: 761px) {
  @container (max-width: 880px) {
    #page-trail .trail-page {
      max-width: 7rem;
    }
  }
}

/* A member GitHub grants no push reads the page locked; the bar says why where the controls
   would stand, in the bar's muted text colour and on one line with it. */
.edit-no-push {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  min-width: 0;
  color: var(--muted);
  font-size: 0.85rem;
  white-space: nowrap;
}

.edit-no-push > span {
  overflow: hidden;
  text-overflow: ellipsis;
}

.edit-no-push .control-glyph {
  flex: none;
  width: 0.9rem;
  height: 0.9rem;
}

/* A bordered control drawn as a mark alone (Undo, Redo, Markdown, Lock, the offline copy) is a
   mark of one size at every width. One that can be pressed in says so: the Markdown view is open,
   or the page is locked. */
.toolbar .bar-button.bar-icon {
  padding: 0 0.45rem;
}
.bar-icon > .control-glyph {
  display: block;
  width: 0.9rem;
  height: 0.9rem;
}
/* A pressed control is filled deeper than a hovered one, so a control the pointer still rests on
   after a press reads as it is. */
.bar-icon[aria-pressed='true'],
.bar-button.bar-icon[aria-pressed='true']:hover:not([disabled]) {
  border-color: var(--pressed);
  background: var(--pressed);
}

/* Every set is served; the page's (`data-set`, `reader/editing-controls.mjs`) alone stands. */
#editing-controls:not([data-set='live']) > #edit-live,
#editing-controls:not([data-set='no-push']) > .edit-no-push,
#editing-controls:not([data-set='restore']) > #restore-version {
  display: none;
}

/* Not yet drawn from the page's state: what depends on it is not yet known. */
#editing-controls:not([data-drawn]) #edit-restore {
  display: none;
}
#editing-controls:not([data-drawn]) :is(#edit-undo, #edit-redo) {
  opacity: 0.7;
  cursor: progress;
}
/* Who has the page open: a mark at the page's top left (Task 6, 6.14), two bar gaps from its
   edges at every width, drawn only while someone is watching or it pulses (6.15). It is not a
   button: no state of it draws a frame, a surface or a ring — hovering and pressing change its
   colour alone, and the platform's focus outline is the platform's (6.1). */
#edit-watchers {
  /* The page's area, not the window's: it stands in the cell the page is drawn in, two bar gaps in
     from that cell's top and left edges, so the bar's line is above it whatever the bar holds. */
  position: absolute;
  grid-row: 2;
  grid-column: 1;
  inset: 0 auto auto 0;
  margin: calc(2 * var(--bar-gap, 0.35rem));
  z-index: 4;
  display: inline-flex;
  color: var(--muted);
}
#edit-watchers[data-count='0']:not([data-pulse]),
#edit-watchers[hidden] {
  display: none;
}
#edit-watchers > button {
  display: inline-flex;
  margin: 0;
  padding: 0;
  border: 0;
  background: none;
  box-shadow: none;
  color: inherit;
  cursor: pointer;
  anchor-name: --haiyo-watchers;
}
#edit-watchers > button:hover,
#edit-watchers > button[aria-expanded='true'] {
  color: var(--foreground);
}
/* Two sizes (the user, 2026-10-20): the line the mark stands on, whose top the page's loader shares
   (`discussion-panel.css`), the bar's control height and a little more; and the mark, the line's
   whole height on a phone and 64% of it on wider windows, where it stands at the line's top. */
:root {
  --watchers-line-size: 1.35rem;
  --watchers-mark-size: var(--watchers-line-size);
}
@media (min-width: 761px) {
  :root {
    --watchers-mark-size: calc(0.64 * var(--watchers-line-size));
  }
}
.watchers-mark {
  display: block;
  width: var(--watchers-mark-size);
  height: var(--watchers-mark-size);
  overflow: visible;
}
.watchers-mark circle {
  fill: none;
  stroke: currentColor;
  stroke-width: 1.5;
}
/* The rings past the watchers there are stay undrawn: the outer circle is the first watcher, and
   each further one is drawn inside it (`views/reader.rs` `WATCHERS_MARK`). */
#edit-watchers:not([data-count='2'], [data-count='3']) .watchers-mark [data-ring='2'],
#edit-watchers:not([data-count='3']) .watchers-mark [data-ring='3'] {
  display: none;
}
/* A watcher holds a draft that differs from the head's text: the mark pulses slowly, from its
   centre, and stops when no watcher's draft differs. */
#edit-watchers[data-pulse] .watchers-mark {
  animation: haiyo-watchers-pulse 1s ease-in-out infinite alternate;
  transform-origin: center;
}
@keyframes haiyo-watchers-pulse {
  from {
    opacity: 1;
    transform: scale(1);
  }
  to {
    opacity: 0.35;
    transform: scale(0.8);
  }
}
/* With reduced motion, a filled centre stands for the pulse. */
@media (prefers-reduced-motion: reduce) {
  #edit-watchers[data-pulse] .watchers-mark {
    animation: none;
  }
  /* The rings fill from the outside in, so the centre is drawn as a dot of its own. */
  #edit-watchers[data-pulse] .watchers-mark {
    background: radial-gradient(circle, currentColor 0 15%, transparent 16%);
  }
}
/* The names drop from the icon as a floating list of the bar's kind, drawn as the index's and the
   commits' panels are (`page-index.css`, `history.css`): the same edge, corners, shadow, padding
   and rows, inside the window wherever the bar puts the icon. Its header says what the names are. */
.watchers-list {
  position: fixed;
  z-index: 30;
  position-anchor: --haiyo-watchers;
  top: calc(anchor(bottom) + 4px);
  left: anchor(left);
  position-try-fallbacks: flip-inline;
  min-width: 12rem;
  max-width: min(20rem, calc(100vw - 2rem));
  margin: 0;
  padding: 0.3rem;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--background);
  box-shadow: 0 8px 24px var(--shadow);
  color: var(--text);
  font-size: 0.85rem;
}
#edit-watchers:not([data-open]) > .watchers-list {
  display: none;
}
.watchers-title {
  margin: 0 0 0.3rem;
  padding: 0.3rem 0.5rem 0.45rem;
  border-bottom: 1px solid var(--line);
  color: var(--muted);
  font-size: 0.75rem;
  font-weight: 600;
}
/* A long list scrolls inside the panel, its header standing. */
.watchers-names {
  max-height: min(16rem, 60dvh);
  margin: 0;
  padding: 0;
  overflow-y: auto;
  list-style: none;
}
/* A row as the index's rows: its padding and corners. A dot in the text colour says the reader is
   online; while their draft differs from the head's text it is the alert colour and pulses, as the
   mark does (the user, 2026-10-20). */
.watchers-names > li {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.3rem 0.5rem;
  border-radius: 4px;
}
.watchers-names > li::before {
  content: '';
  flex: none;
  width: 0.45rem;
  height: 0.45rem;
  border-radius: 50%;
  background: var(--text);
}
.watchers-names > li[data-drafted]::before {
  background: var(--alert);
  animation: haiyo-watchers-pulse 1s ease-in-out infinite alternate;
}
@media (prefers-reduced-motion: reduce) {
  .watchers-names > li[data-drafted]::before {
    animation: none;
  }
}
.watcher-name {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* A watcher whose draft differs from the head's text is marked by a quiet tag at the row's end. */
.watcher-drafted {
  flex: none;
  padding: 0.05rem 0.45rem;
  border: 1px solid var(--line);
  border-radius: 999px;
  color: var(--muted);
  font-size: 0.75rem;
}

#edit-lock:not([aria-pressed]) > .control-glyph {
  visibility: hidden;
}

/* Lock shows the page's state: an open lock while it can be edited, a closed one while it is locked. */
#edit-lock:not([aria-pressed='true']) > .lock-closed,
#edit-lock[aria-pressed='true'] > .lock-open {
  display: none;
}

/* What became of something asked of the draft is said in the bar's line, after the controls, in
   the bar's small type. A long refusal wraps in the width it is given, and the bar grows to hold
   it rather than covering the page. Its width is taken from the controls' side before the page's
   name gives way: the controls holding it shrink first, down to the notice's longest word (its
   words break between letters only where a word is longer than its line). */
#editing-controls:has(> .notice) {
  flex-shrink: 1000;
}

#editing-controls > .notice {
  flex: 0 1 auto;
  max-width: 24rem;
  overflow-wrap: break-word;
  /* Its first line on the controls' centre line: the line is drawn from its top. */
  margin: calc((var(--bar-control-height) - 1lh) / 2) 0 0;
  font-size: 0.78rem;
  line-height: 1.25;
}


/* A command's page stands dimmed in the tree until the change lands. */
#workspace-sidebar [data-command] { opacity: 0.55; }



/* Comment export is the least urgent control here, so it yields first when width runs out. */
#download-comments {
  flex: 0 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Busy, not broken: the editor module is being fetched. */
#editing-controls .bar-button[aria-busy='true'] {
  opacity: 0.7;
  cursor: progress;
}
/* web/styles/toasts.css */
/* The reader's toasts of the page in view (`reader/toasts/view.mjs`): notices about the page,
   each a card of its own, standing over the page's top left until closed.

   The stack stands in the page's cell, as the watchers mark and the loader do
   (`editing-bar.css`, `discussion-panel.css`): its left edge is the mark's right side and one bar
   gap, counted from the mark's size whether the mark is drawn or not, so the mark appearing moves
   nothing; its top is the loader's (two bar gaps from the cell's top), where the loader stands:
   the toasts show only once the page has loaded, when the loader has gone
   (`toasts/view.mjs`), so the two never stand together. It has no surface of its own: each card is drawn as a floating
   panel is (the watchers' list, the index's), its words as a notice's. */
.toasts {
  --toasts-gap: var(--bar-gap, 0.35rem);
  --toasts-left: calc(3 * var(--toasts-gap) + var(--watchers-mark-size));
  position: absolute;
  z-index: 3;
  grid-row: 2;
  grid-column: 1;
  top: calc(2 * var(--toasts-gap));
  left: var(--toasts-left);
  width: min(22rem, calc(100% - var(--toasts-left) - 2 * var(--toasts-gap)));
  /* Half the page at most: beyond it the cards scroll inside the stack, "Close all" under them. */
  max-height: 50%;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--toasts-gap);
  pointer-events: none;
}
.toasts[hidden] {
  display: none;
}
.toasts > ol {
  align-self: stretch;
  display: flex;
  flex-direction: column;
  gap: var(--toasts-gap);
  min-height: 0;
  /* Room for the cards' shadows inside the list when it scrolls, and so clips at its edges: the
     margin takes it back on the left, the top and the right, so the cards stand on the stack's
     edges, at its width. */
  --toasts-shadow-room: 0.5rem;
  margin: calc(-1 * var(--toasts-shadow-room)) calc(-1 * var(--toasts-shadow-room)) 0;
  padding: var(--toasts-shadow-room);
  overflow: visible;
  overscroll-behavior: contain;
  list-style: none;
  /* The page stays pressable between and beside the cards; a wheel over a card scrolls the list. */
  pointer-events: none;
}
/* Only a list that overflows scrolls, and so clips: until then the cards' shadows show whole. */
.toasts > ol[data-scrolls] {
  overflow-y: auto;
}
.toasts .toast {
  /* A card's words stand in it as the reader sees them: their letters, from the capitals' top to
     the baseline, have as much room above as below, and the × stands centred on the first line,
     however many lines the words take. The first line stands in the middle of the ×'s band
     (`--toast-band`: the × and its room above and below), so a card of one line is that band
     tall; the room (`--toast-room`) above the first line's box is the same below the last's. */
  --toast-line: calc(0.85rem * 1.35);
  --toast-close: 1.35rem;
  --toast-band: calc(var(--toast-close) + 1rem);
  --toast-room: calc((var(--toast-band) - var(--toast-line)) / 2);
  display: flex;
  align-items: flex-start;
  gap: 0.35rem;
  flex: none;
  padding: 0 0.35rem 0 0.75rem;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--background);
  box-shadow: 0 2px 6px var(--shadow);
  color: var(--text);
  font-size: 0.85rem;
  line-height: var(--toast-line);
  overflow-wrap: anywhere;
  pointer-events: auto;
}
.toasts .toast > p {
  flex: 1;
  margin: 0;
  padding-block: var(--toast-room);
}
/* A line box centres the font's whole height, ascent and descent, not its letters, which a
   descent deeper than the room above the capitals leaves standing high. Where the browser trims
   the words' box to their letters (`text-box`), the trimmed room is given back as padding, the
   same above and below, so the letters' middle stands where the first line's box's did, and the
   card keeps its height. The ×, whose glyph stands centred on its capitals' middle, is trimmed
   the same way, so the button centres its glyph and not its font's line. Elsewhere the line boxes
   are centred, as before. */
@supports (text-box: trim-both cap alphabetic) {
  .toasts .toast > p {
    text-box: trim-both cap alphabetic;
    padding-block: calc(var(--toast-room) + (var(--toast-line) - 1cap) / 2);
  }
  .toasts .toast > button {
    text-box: trim-both cap alphabetic;
  }
}
/* The ×: drawn small; on a phone and under a finger a 44 px square of its own (below). Its middle
   is the first line's: the room, then half a line. */
.toasts .toast > button {
  flex: none;
  width: var(--toast-close);
  height: var(--toast-close);
  margin: calc(var(--toast-room) + (var(--toast-line) - var(--toast-close)) / 2) 0 0;
  padding: 0;
  border: 0;
  border-radius: 4px;
  background: none;
  box-shadow: none;
  color: var(--muted);
  font-size: 1rem;
  line-height: 1;
  cursor: pointer;
}
.toasts .toast > button:hover,
.toasts .toast > button:focus-visible {
  color: var(--foreground);
}
/* "Close all": smaller than a card's words, under the last card, while the stack overflows. It
   stands over whatever the page holds there, so it is drawn on a surface of its own, a small
   card's (the cards' surface, line and corners), and read against it. */
.toasts .toasts-close-all {
  margin: 0;
  padding: 0.2rem 0.6rem;
  border: 1px solid var(--line);
  border-radius: 6px;
  background: var(--background);
  box-shadow: 0 1px 4px var(--shadow);
  color: var(--muted);
  font-size: 0.75rem;
  cursor: pointer;
  pointer-events: auto;
}
.toasts .toasts-close-all[hidden] {
  display: none;
}
.toasts .toasts-close-all:hover,
.toasts .toasts-close-all:focus-visible {
  color: var(--foreground);
}
/* A finger presses 44 px: each card is at least that tall, its × a 44 px square standing at its
   right edge inside it (so no two ×s' targets meet), and "Close all" is that tall. */
@media (max-width: 760px), (pointer: coarse) {
  .toasts .toast {
    --toast-close: 44px;
    --toast-band: 44px;
    padding-right: 0;
  }
  .toasts .toasts-close-all {
    min-height: 44px;
  }
}
/* A comparison draws its head at the page's top, at a height its frame decides (its text size, a
   phone's wrap) and tells (`--comparison-head`, `reader/diff.mjs`): while it stands, the stack
   stands below it, never over it. */
.reading-area[data-diff] .toasts {
  top: calc(var(--comparison-head, 0px) + 2 * var(--toasts-gap));
}
/* On a phone a pane or a sheet opens over the page, the page behind a scrim (`phone.css`): the
   page's toasts are the page's, and stand out of view behind it rather than peek out beside it. */
@media (max-width: 760px) {
  .layout:is(.pages-open, .tools-open, .unread-open) .toasts {
    visibility: hidden;
  }
}
/* web/styles/merge-decisions.css */
/* Deciding a conflicted merge.
 *
 * The view covers the page it is about rather than displacing it, so leaving the decision returns
 * the reader exactly where they were. Each side carries a colour of its own — the reader's
 * changes in the workspace's green, the branch's in blue — because which text is which is the
 * whole question, and a label alone puts that answer at the top of a block rather than on every
 * line of it. */
#conflict-resolution {
  --mine: #2f6b8f;
  --mine-field: color-mix(in srgb, #2f6b8f 12%, var(--background));
  --theirs: #7a5ea8;
  --theirs-field: color-mix(in srgb, #7a5ea8 12%, var(--background));
  position: absolute;
  inset: 0;
  z-index: 4;
  display: flex;
  flex-direction: column;
  background: var(--background);
}

/* Every control in the shell is the accent's solid green, which in a view made of controls says
   nothing about which of them matters. Here they are outlined, and only the one that applies the
   decisions carries the accent; each side's control carries that side's colour, so the button and
   the text it takes are the same thing. */
#conflict-resolution button {
  padding: 0.35rem 0.7rem;
  border: 1px solid var(--line);
  background: var(--background);
  color: var(--text);
  font-size: 0.85rem;
}

#conflict-resolution button:hover:not(:disabled) {
  border-color: var(--text);
}

#conflict-resolution button:disabled {
  color: var(--muted);
  cursor: not-allowed;
}

#conflict-apply:not(:disabled) {
  border-color: var(--accent);
  background: var(--accent);
  color: var(--background);
}

.conflict-side[data-side='ours'] > header button {
  border-color: var(--mine);
  color: var(--mine);
}

.conflict-side[data-side='theirs'] > header button {
  border-color: var(--theirs);
  color: var(--theirs);
}

.conflict-bar {
  display: flex;
  gap: 8px;
  align-items: center;
  flex-wrap: wrap;
  padding: 10px 16px;
  border-bottom: 1px solid var(--line);
}

.conflict-bar h2 {
  margin: 0;
  font-size: 1rem;
}

#conflict-count {
  margin: 0 auto 0 8px;
  color: var(--muted);
}

/* Everything is decided, so the control that applies them is the one to reach for. */
#conflict-resolution[data-settled='true'] #conflict-apply {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.conflict-file {
  flex: 1;
  min-height: 0;
  overflow: auto;
  padding: 16px;
}

.conflict-file pre {
  margin: 0;
  padding: 6px 10px;
  overflow-x: auto;
  white-space: pre-wrap;
  font-family: ui-monospace, monospace;
  font-size: 0.85rem;
  line-height: 1.5;
}

/* Text both sides agree on: present, so a decision is read in its place in the file, and quiet,
   so it is not mistaken for something being asked about. */
.conflict-context {
  color: var(--muted);
}

.conflict-region {
  margin: 10px 0;
  border: 1px solid var(--line);
  border-radius: 6px;
  overflow: hidden;
}

.conflict-sides {
  display: grid;
  grid-template-columns: 1fr 1fr;
}

@media (max-width: 900px) {
  .conflict-sides {
    grid-template-columns: 1fr;
  }
}

.conflict-side:first-child {
  border-right: 1px solid var(--line);
}

.conflict-side > header,
.conflict-chosen > header {
  display: flex;
  gap: 8px;
  align-items: center;
  justify-content: space-between;
  padding: 6px 10px;
  border-bottom: 1px solid var(--line);
  font-size: 0.8rem;
  font-weight: 600;
}

.conflict-side[data-side='ours'] > header {
  color: var(--mine);
  background: var(--mine-field);
}

.conflict-side[data-side='theirs'] > header {
  color: var(--theirs);
  background: var(--theirs-field);
}

.conflict-side[data-side='ours'] > pre {
  border-left: 3px solid var(--mine);
  background: color-mix(in srgb, var(--mine-field), var(--background) 45%);
}

.conflict-side[data-side='theirs'] > pre {
  border-left: 3px solid var(--theirs);
  background: color-mix(in srgb, var(--theirs-field), var(--background) 45%);
}

/* A side holding nothing is a deletion, which has to look deliberate rather than broken. */
.conflict-file pre[data-empty='true'] {
  color: var(--muted);
  font-style: italic;
}

.conflict-region > footer {
  display: flex;
  gap: 8px;
  padding: 8px 10px;
  border-top: 1px solid var(--line);
}

/* A decided region keeps the colour of the side it was decided from, so the file can be read
   back at a glance; text that is neither side carries the neutral outline instead. */
.conflict-region[data-decided='true'][data-side='ours'] {
  border-color: var(--mine);
}

.conflict-region[data-decided='true'][data-side='theirs'] {
  border-color: var(--theirs);
}

.conflict-region[data-decided='true'][data-side='ours'] > .conflict-chosen > header {
  color: var(--mine);
  background: var(--mine-field);
}

.conflict-region[data-decided='true'][data-side='theirs'] > .conflict-chosen > header {
  color: var(--theirs);
  background: var(--theirs-field);
}

.conflict-edit {
  display: block;
  width: 100%;
  min-height: 4rem;
  padding: 6px 10px;
  border: 0;
  background: transparent;
  color: inherit;
  font-family: ui-monospace, monospace;
  font-size: 0.85rem;
  line-height: 1.5;
  resize: vertical;
  field-sizing: content;
}
/* web/styles/trail.css */
/* Where the page is and which revision of it is in view. */
/* The trail gives way to what stands beside it in the bar down to the page name's least width
   (`.trail-page`) and its own padding, and no further, however the bar shares its width out: a
   notice beside the controls, or the narrow bar's share (`flex: 1 1 0` below), does not draw the
   controls over the name. */
#page-trail {
  position: relative;
  flex: 0 1 auto;
  min-width: 0;
  min-width: calc-size(max-content, min(size, 6ch + 0.9rem));
  font-size: 0.85rem;
}

/* The bar is one line high, so what is added to it fits the line rather than growing it. */
#page-trail > summary {
  display: flex;
  gap: 0.4rem;
  align-items: baseline;
  padding: 0.1rem 0.45rem;
  border-radius: 5px;
  line-height: 1.4;
  cursor: pointer;
  list-style: none;
  white-space: nowrap;
}

#page-trail > summary::-webkit-details-marker,
#page-trail > summary::marker {
  display: none;
  content: '';
}

#page-trail > summary:hover {
  background: var(--sidebar);
}

/* A page's name is as long as the page's author made it, and the bar is one line: the name takes
   what the line can spare and is cut with an ellipsis where it cannot, but keeps about six
   characters, which name it, or the whole name when it is shorter. The commit's id beside it gives
   way first, down to nothing: the commit the page stands at is also on the history control. A
   browser without `calc-size` lets the name go down to nothing, as before. */
.trail-page {
  overflow: hidden;
  min-width: 0;
  min-width: calc-size(max-content, min(size, 6ch));
  text-overflow: ellipsis;
  max-width: 18rem;
}

.trail-commit {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  /* Shrinks long before the name does. */
  flex-shrink: 1000;
  color: var(--muted);
  font-family: ui-monospace, monospace;
  font-size: 0.78rem;
}

/* A page shown from memory while its newer drawing is asked for says so after its commit, on the
   trail's own line: a word beside the revision it is replacing, not a box in the bar. */
#page-trail summary > .notice {
  display: inline;
  margin: 0 0 0 0.4rem;
  font-size: 0.78rem;
}

/* The chain, in the order its links were followed: the workspace at the top, the page at the
   bottom. Each step is indented under the one that reaches it, which is what says it is beneath
   it rather than beside it. */
#page-trail-steps {
  position: absolute;
  z-index: 30;
  top: calc(100% + 4px);
  left: 0;
  display: flex;
  flex-direction: column;
  min-width: 16rem;
  max-width: 24rem;
  padding: 0.35rem;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--background);
  box-shadow: 0 8px 24px var(--shadow);
}

#page-trail-steps > * {
  padding: 0.4rem 0.5rem;
  border-radius: 6px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

#page-trail-steps a:hover {
  background: var(--sidebar);
  text-decoration: none;
}

#page-trail-steps [aria-current='page'] {
  color: var(--muted);
  font-weight: 650;
}

#page-trail-steps > * + * {
  margin-left: 0.8rem;
}
/* web/styles/bar-marks.css */
/* How the discussion is presented: the control's own mark, and the name of what is in view. The
   bar names what is being looked at; a control in it says what it does without a sentence. */
#discussion-mode > summary {
  display: flex;
  gap: 0.4rem;
  align-items: center;
  list-style: none;
}

#discussion-mode > summary::-webkit-details-marker,
#discussion-mode > summary::marker {
  display: none;
  content: '';
}

.mode-icons { display: flex; align-items: center; }
.mode-icons [data-mode-icon] { display: none; }
[data-discussion-mode='right'] .mode-icons [data-mode-icon='right'],
[data-discussion-mode='bottom'] .mode-icons [data-mode-icon='bottom'],
[data-discussion-mode='margin'] .mode-icons [data-mode-icon='margin'] { display: inline-flex; }
[data-mode-name] { display: none; }
[data-discussion-mode='right'] [data-mode-name='right'],
[data-discussion-mode='bottom'] [data-mode-name='bottom'],
[data-discussion-mode='margin'] [data-mode-name='margin'] { display: inline; }
/* The width and text sizes this browser keeps are not yet known until the shell reads them: the
   page as served stands meanwhile, narrow at 100%, drawn busy. */
:is(#reading-width, .text-size)[aria-busy='true'] { opacity: 0.7; cursor: progress; }
/* What is waiting is not known until a read answers (`unread/view.mjs`). */
:is(#unread[aria-busy='true'] > summary, #phone-unread[aria-busy='true']) { opacity: 0.7; cursor: progress; }
#discussion-mode svg { width: 1.05rem; height: 1.05rem; }

/* A control of the bar drawn as its mark alone: the theme the application is read in, what is
   waiting to be read. Each acts on what stands beside it rather than on the page, so each
   is the bar's own quiet text colour on the bar's own surface, and takes a surface only under the pointer.
   It stands on the line the bar's controls stand on: the bar is as tall as its controls, and one
   that stands taller than the rest makes the whole line taller. */
.bar-mark {
  display: grid;
  place-items: center;
  width: 21px;
  height: 21px;
  padding: 0;
  border: 0;
  border-radius: 5px;
  background: transparent;
  color: var(--muted);
  cursor: pointer;
  list-style: none;
}

.bar-mark:hover,
[open] > .bar-mark {
  background: var(--raised);
  color: var(--text);
}

/* A mark that is a word rather than a drawing, such as a menu's Reset: as wide as its word. */
.bar-mark.bar-word {
  width: auto;
  padding: 0 0.3rem;
}

.bar-mark > svg,
#reading-width > span > svg {
  width: 1.05rem;
  height: 1.05rem;
}


/* The width control holds both of its marks and shows the one for the action it offers next,
   so a press shows at once, before the reader's code that would draw a mark has loaded. */
#reading-width > span {
  display: contents;
}
#reading-width[aria-pressed='true'] > .width-expand,
#reading-width:not([aria-pressed='true']) > .width-narrow {
  display: none;
}

/* A filled funnel indicates an active range. Press it to show all placeable comments. */
#comment-scope[aria-pressed='true'] svg {
  fill: currentColor;
}

.bar-mark::-webkit-details-marker,
.bar-mark::marker {
  display: none;
  content: '';
}
/* web/styles/loader.css */
/* How much of something being loaded has arrived, filled to `--loaded`, which `drawBar` sets
   between 0 and 1. The document's loader and the page's history draw the same bar. */
.loader-bar {
  display: block;
  height: 4px;
  overflow: hidden;
  border-radius: 2px;
  background: var(--line);
}
.loader-bar::before {
  content: '';
  display: block;
  width: calc(var(--loaded, 0) * 100%);
  height: 100%;
  background: var(--accent);
}
.loader-bar[hidden] {
  display: none;
}
/* web/styles/history.css */
/* The page's history, first of the page's own controls on the bar. */
#commits,
#comment-range {
  position: relative;
  flex: 0 0 auto;
}

/* A page read at an earlier version than the branch holds says so on the control itself: the
   mark in the alert colour, with an exclamation beside it, and why in its title. The control
   keeps its box, so the mark keeps its place; the exclamation stands just past its right edge,
   in room the control keeps beside it. */
#commits > .bar-mark.behind,
#commits > .bar-mark.behind:hover,
#commits[open] > .bar-mark.behind {
  position: relative;
  margin-right: 0.45rem;
  color: var(--alert);
}

#commits > .bar-mark:not(.behind) > .commits-behind {
  display: none;
}
.commits-behind {
  position: absolute;
  top: 50%;
  left: 100%;
  margin-left: 1px;
  font-size: 0.8rem;
  font-weight: 700;
  line-height: 1;
  transform: translateY(-50%);
}

/* The history drops from the control over the page and reaches to near the window's foot: a
   history is read down its length. The list scrolls inside it; the search and the count to load
   stay where they are. The bar stands at the top of the window, so what is left under it is the
   window's height less the bar's line. */
.commits-panel {
  position: absolute;
  z-index: 30;
  top: calc(100% + 4px);
  left: 0;
  display: flex;
  flex-direction: column;
  width: min(26rem, calc(100vw - 2rem));
  height: calc(100dvh - 3.25rem);
  padding: 0.3rem;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--background);
  box-shadow: 0 8px 24px var(--shadow);
  color: var(--text);
  font-size: 0.85rem;
}

/* How much of the history has arrived, and what became of loading it, on one line under the
   search. With nothing to say the line takes no room. */
.commits-status {
  display: flex;
  flex: 0 0 auto;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.25rem 0.5rem;
  padding: 0 0.35rem;
}

.commits-status .loader-bar {
  flex: 1 1 6rem;
}

/* Its kind's colour is the shell's (`shell.css` `.notice`): a refusal here is in the alert colour. */
.commits-panel .notice {
  margin: 0;
  font-size: 0.72rem;
}

[data-commits-list] {
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
  gap: 0.1rem;
  min-height: 0;
  margin: 0.25rem 0 0;
  padding: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  list-style: none;
}
/* web/styles/page-index.css */
/* The page's index: a bordered control, as the lock beside it is, pressed while its panel is
   open. The panel drops from the control and reaches to near the window's foot; the tree scrolls
   inside it. It stands from the control's left edge, or from its right edge where the window has
   no room to the right, so it is inside the window wherever the bar puts the control. */
#document-index {
  flex: 0 0 auto;
}

#document-index > summary {
  anchor-name: --haiyo-index;
  list-style: none;
}

#document-index > summary::-webkit-details-marker,
#document-index > summary::marker {
  display: none;
  content: '';
}

#document-index[open] > summary,
#document-index[open] > summary:hover {
  border-color: var(--pressed);
  background: var(--pressed);
}

.index-panel {
  position: fixed;
  z-index: 30;
  position-anchor: --haiyo-index;
  top: calc(anchor(bottom) + 4px);
  bottom: 0.75rem;
  left: anchor(left);
  position-try-fallbacks: flip-inline;
  display: flex;
  flex-direction: column;
  width: min(24rem, calc(100vw - 2rem));
  padding: 0.3rem;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--background);
  box-shadow: 0 8px 24px var(--shadow);
  color: var(--text);
  font-size: 0.85rem;
}

.index-tree {
  flex: 1 1 auto;
  min-height: 0;
  overflow: auto;
}

.index-tree ol {
  margin: 0;
  padding: 0;
  list-style: none;
}

/* A heading under another stands in from it, by one step for each level it is under. */
.index-tree ol ol {
  padding-left: 0.9rem;
}

.index-tree button {
  display: block;
  width: 100%;
  padding: 0.3rem 0.5rem;
  border: 0;
  border-radius: 4px;
  background: transparent;
  color: var(--text);
  font: inherit;
  text-align: left;
  overflow-wrap: anywhere;
  cursor: pointer;
}

.index-tree button[data-level='1'] {
  font-weight: 600;
}

/* A comparison's heading only one revision holds, in the comparison's colours: one added, and one
   removed, struck through as a removed word is (`diff.css`). */
.index-tree button[data-change='added'] {
  background: var(--diff-added);
  color: var(--diff-added-text);
}
.index-tree button[data-change='removed'] {
  background: var(--diff-removed);
  color: var(--diff-removed-text);
  text-decoration: line-through;
}

/* The heading being read, marked as the published page's index marks it: the accent's rule at its
   left (`document-column.css`). */
.index-tree button[aria-current='location'] {
  box-shadow: inset 2px 0 0 var(--accent);
  border-radius: 0 4px 4px 0;
}

.index-tree button:focus-visible {
  background: var(--raised);
}

/* Only a pointer that hovers marks the entry under it: a touch leaves its hover behind. */
@media (hover: hover) {
  .index-tree button:hover {
    background: var(--raised);
  }
}

.index-empty {
  margin: 0.5rem;
  color: var(--muted);
}

.commits-top {
  display: flex;
  flex: 0 0 auto;
  align-items: center;
  gap: 0.5rem;
  padding: 0.2rem 0.35rem 0.35rem;
  color: var(--muted);
}
/* web/styles/comment-range.css */
/* The comment filter's All: every comment that can be placed, whatever its commit. */
.commits-all {
  flex: 0 0 auto;
  padding: 0.2rem 0.6rem;
  border: 1px solid var(--line);
  border-radius: 4px;
  background: var(--background);
  color: var(--text);
  font: inherit;
  cursor: pointer;
}
.commits-all:hover {
  background: var(--raised);
}

[data-commits-search] {
  flex: 1 1 auto;
  min-width: 0;
  margin: 0;
  padding: 0.2rem 0.4rem;
  font-size: 0.8rem;
}

.commits-depth {
  display: flex;
  flex: 0 0 auto;
  align-items: center;
  gap: 0.25rem;
  font-size: 0.75rem;
  white-space: nowrap;
}

/* Wide enough for the largest count, three digits, beside the field's own number controls. */
[data-commits-depth] {
  width: 3.2rem;
  margin: 0;
  padding: 0.2rem 0.3rem;
  font-size: 0.8rem;
  font-variant-numeric: tabular-nums;
}
/* web/styles/commits.css */
/* A commit as a history shows one: its title, up to two lines of its message under it in smaller
   text, and who made it when. The page in view is marked; the history either side of it is set
   dimmer. */
.commit-target {
  display: grid;
  gap: 0.1rem;
  width: 100%;
  padding: 0.4rem 0.6rem 0.4rem calc(0.6rem - 3px);
  border: 0;
  border-left: 3px solid transparent;
  border-radius: 6px;
  background: transparent;
  color: var(--muted);
  font: inherit;
  text-align: left;
  text-decoration: none;
}

a.commit-target,
button.commit-target:not([disabled]) {
  cursor: pointer;
}

a.commit-target:hover,
button.commit-target:not([disabled]):hover,
.commit-target:focus-visible {
  background: var(--raised);
  color: var(--text);
}

.commit[data-place='active'] > .commit-target {
  border-left-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 8%, transparent);
  color: var(--text);
}

.commit-title {
  overflow: hidden;
  font-size: 0.88rem;
  font-weight: 600;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.commit-body {
  display: -webkit-box;
  overflow: hidden;
  font-size: 0.78rem;
  white-space: pre-line;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
}

.commit-body[hidden] {
  display: none;
}

.commit-meta {
  overflow: hidden;
  font-size: 0.7rem;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.commit-meta code {
  font-family: ui-monospace, monospace;
}

.commit-meta time {
  font-variant-numeric: tabular-nums;
}

.commit-here {
  color: var(--accent);
  font-size: 0.68rem;
  font-weight: 600;
}

[data-commits-empty] {
  margin: 0.5rem 0.6rem;
  color: var(--muted);
  font-size: 0.8rem;
}
/* web/styles/theme.css */
/* Which theme the application is read in: a line of the pane's settings menu that switches it. */

#discussion-mode [role='radiogroup'],
#comment-filter-options > div,
.text-size > div {
  position: absolute;
  z-index: 30;
  top: calc(100% + 4px);
  right: 0;
  min-width: 9rem;
  padding: 0.3rem;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--background);
  box-shadow: 0 8px 24px var(--shadow);
}
/* web/styles/toolbar-menu.css */
/* One row of a toolbar menu: the discussion presentations and the comment settings are the
   same rows, each a mark or a box before its words, on one centre line. The
   comment settings move into the comments sheet on a phone, so their rows outrank the sheet's
   own button style. */
#discussion-mode [role='radio'],
.bar-menu .bar-menu-row {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  width: 100%;
  margin: 0;
  padding: 0.35rem 0.5rem;
  border: 0;
  border-radius: 6px;
  background: transparent;
  color: var(--text);
  font: inherit;
  line-height: 1.25;
  text-align: left;
  white-space: nowrap;
  cursor: pointer;
}

#discussion-mode [role='radio']:hover,
.bar-menu .bar-menu-row:hover {
  background: var(--raised);
}

#discussion-mode [role='radio'][aria-checked='true'] {
  font-weight: 700;
  background: var(--held);
}

/* The mark or the box a row begins with is one size, so every row's words start on one line. */
#discussion-mode [role='radio'] > svg,
.bar-menu .bar-menu-row > svg,
.bar-menu .bar-menu-row > input[type='checkbox'] {
  flex: none;
  width: 1.05rem;
  height: 1.05rem;
  margin: 0;
}
/* web/styles/dialogs-content.css */
/* Settings and administration over the reader, in the same theme as the application. Their box is
   every dialog's (`dialog-box.css`). */
#settings-modal header,
#workspace-settings-modal header,
#members-modal header,
#publications-modal header,
#publish-modal header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
}

#settings-modal #settings-close,
#workspace-settings-modal #workspace-settings-close,
#members-modal #members-close,
#publications-modal #publications-close,
#publish-modal #publish-close,
#create-workspace-modal #create-workspace-close {
  background: transparent;
  color: var(--muted);
  padding: 0.3rem 0.5rem;
}

#settings-modal #settings-close:hover,
#workspace-settings-modal #workspace-settings-close:hover,
#members-modal #members-close:hover,
#publications-modal #publications-close:hover,
#publish-modal #publish-close:hover,
#create-workspace-modal #create-workspace-close:hover {
  background: var(--raised);
  color: var(--text);
}

.settings-empty,
.members-note {
  margin: 1rem 0 0;
  color: var(--muted);
}
/* web/styles/members.css */
/* Who is in the workspace: one row per member, with what they may do and whether they stay. */
#members-panel .card {
  margin-top: 1rem;
  padding: 0;
  border: 0;
  background: transparent;
}

#members-panel h2 {
  margin: 0 0 0.4rem;
  font-size: 0.85rem;
  font-weight: 650;
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

#members-panel .members {
  margin: 0;
  padding: 0;
  list-style: none;
}

#members-panel .member {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.35rem 0;
  border-top: 1px solid var(--line);
}

#members-panel .member-name {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

#members-panel form {
  display: flex;
  align-items: center;
  gap: 0.35rem;
  margin: 0;
}
/* web/styles/unread.css */
/* What is still waiting to be read, at the end of the bar. The letter is one of the bar's marks;
   what it opens is a list of ways back, drawn on the same surface as the bar's other menus. */
#unread {
  position: relative;
  flex: 0 0 auto;
}

#unread > summary {
  position: relative;
  anchor-name: --haiyo-unread;
}

/* How much is waiting, on the letter itself: a reader should not have to open it to find out. */
#unread-count {
  position: absolute;
  top: -3px;
  right: -5px;
  min-width: 13px;
  padding: 0 2px;
  border-radius: 7px;
  background: var(--accent);
  color: var(--background);
  font-size: 0.6rem;
  line-height: 13px;
  text-align: center;
}

/* The list drops from the letter and stands from its right edge, or from its left edge where the
   window has no room to the left: a wrapped bar puts the letter at the start of its second line,
   as on a tablet in portrait, and the list is inside the window wherever the letter stands. */
.unread-panel {
  position: fixed;
  z-index: 30;
  position-anchor: --haiyo-unread;
  top: calc(anchor(bottom) + 4px);
  right: anchor(right);
  position-try-fallbacks: flip-inline;
  width: min(24rem, calc(100vw - 2rem));
  max-height: min(24rem, 60vh);
  overflow-y: auto;
  padding: 0.3rem;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--background);
  box-shadow: 0 8px 24px var(--shadow);
}

.unread-top {
  display: flex;
  align-items: center;
  gap: 0.25rem;
  padding: 0.2rem 0.35rem 0.35rem;
  color: var(--muted);
}

/* The tabs take the room, and the controls that act on the list stand at their end. */
.unread-top > span,
.unread-tabs {
  flex: 1 1 auto;
}

/* Two names of one state: what is waiting, and what has been read. The one being looked at is
   the one written in the reading text colour; the other stands beside it in the muted one. */
.unread-tabs {
  display: flex;
  gap: 0.15rem;
}

.unread-tabs > [role='tab'] {
  padding: 0.15rem 0.4rem;
  border: 1px solid transparent;
  border-radius: 6px;
  background: transparent;
  color: var(--muted);
  font: inherit;
  font-size: 0.75rem;
  cursor: pointer;
}

.unread-tabs > [role='tab']:hover {
  background: var(--raised);
}

.unread-tabs > [role='tab'][aria-selected='true'] {
  border-color: var(--line);
  background: var(--raised);
  color: var(--text);
}

/* What one entry of the read tab offers: putting it back among what is waiting. */
.unread-again {
  margin: 0.1rem 0.45rem 0.25rem;
  padding: 0.15rem 0.4rem;
  border: 1px solid var(--line);
  border-radius: 6px;
  background: transparent;
  color: var(--muted);
  font: inherit;
  font-size: 0.7rem;
  cursor: pointer;
}

.unread-again:hover {
  background: var(--raised);
  color: var(--text);
}

#unread-list {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

#unread-list > li {
  position: relative;
}

#unread-list a {
  display: block;
  padding: 0.45rem 2.8rem 0.45rem 0.6rem;
  border-radius: 6px;
  color: var(--text);
  text-decoration: none;
}

.unread-mark {
  display: grid;
  place-items: center;
  position: absolute;
  top: 50%;
  right: 0.45rem;
  transform: translateY(-50%);
  width: 1.8rem;
  height: 1.8rem;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: 6px;
  background: var(--background);
  color: var(--text);
  font-size: 1.1rem;
  line-height: 1;
  cursor: pointer;
  opacity: 0;
}

#unread-list > li:is(:hover, :focus-within) .unread-mark,
.unread-mark:focus-visible {
  opacity: 1;
}

@media (max-width: 760px), (hover: none) {
  .unread-mark { opacity: 1; }
}

#unread-list a:hover {
  background: var(--raised);
}

.unread-who {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.5rem;
  color: var(--muted);
  font-size: 0.7rem;
}

/* The name leads the line and the stamp closes it; the stamp says more than the name does, so it
   is set smaller to leave the name the room. */
.unread-who > span {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.unread-who > time {
  flex: 0 0 auto;
  font-size: 0.62rem;
  font-variant-numeric: tabular-nums;
}

.unread-said {
  display: -webkit-box;
  overflow: hidden;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 4;
}

.unread-where {
  color: var(--muted);
  font-size: 0.7rem;
}

#unread-empty {
  margin: 0;
  padding: 0.5rem 0.45rem;
  color: var(--muted);
}

/* The list says one of these two things, never both. */
#unread-list:not(:empty) + #unread-empty {
  display: none;
}
/* web/styles/settings.css */
/* Asking what may be kept.
 *
 * The banner stands at the foot of the page until the reader answers, over the page rather than
 * pushing it: the page is what they came for. Refusing and accepting are drawn as one and the
 * same control, because a choice that is easier one way is not a choice. The categories under
 * Customize are the same list the privacy settings show. */
#consent-banner {
  position: fixed;
  z-index: 40;
  left: 50%;
  bottom: 1rem;
  transform: translateX(-50%);
  width: min(40rem, calc(100vw - 2rem));
  padding: 1rem 1.1rem;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--background);
  color: var(--text);
  box-shadow: 0 12px 36px var(--shadow);
}

#consent-banner p {
  margin: 0;
  font-size: 0.9rem;
  line-height: 1.5;
}

#consent-customize {
  margin-top: 0.6rem;
}

#consent-customize > summary {
  display: inline-block;
  padding: 0.25rem 0.4rem;
  border-radius: 6px;
  color: var(--muted);
  font-size: 0.85rem;
  cursor: pointer;
}

#consent-customize > summary:hover {
  background: var(--raised);
  color: var(--text);
}

/* What is under Customize is said by the mark beside it, as it is on every fold here. */
#consent-customize > summary::after {
  content: ' \25BE';
}

#consent-customize[open] > summary::after {
  content: ' \25B4';
}

#consent-chosen {
  display: block;
  margin: 0.6rem 0 0 auto;
}

.consent-actions {
  display: flex;
  justify-content: flex-end;
  gap: 0.5rem;
  margin-top: 0.8rem;
}

.consent-categories {
  display: grid;
  gap: 0.15rem;
  margin: 0.5rem 0 0;
  padding: 0;
  list-style: none;
}

.consent-category {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.55rem 0.65rem;
  border-radius: 8px;
}

.consent-category[data-category='necessary'] {
  background: var(--sidebar);
}

.consent-category strong {
  display: block;
  font-size: 0.95rem;
}

.consent-category p {
  margin: 0.15rem 0 0;
  color: var(--muted);
  font-size: 0.82rem;
  line-height: 1.45;
}

.consent-none {
  flex: none;
  color: var(--muted);
  font-size: 0.8rem;
  white-space: nowrap;
}

/* A switch: on is the accent, off is a held surface, and the knob is the background colour on
   either. One that is locked on is drawn on but takes nothing. */
input[role='switch'] {
  appearance: none;
  position: relative;
  display: inline-block;
  flex: none;
  width: 40px;
  height: 22px;
  margin: 0;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: 11px;
  background: var(--held);
  cursor: pointer;
  transition:
    background 150ms ease,
    border-color 150ms ease;
}

input[role='switch']::before {
  content: '';
  position: absolute;
  top: 2px;
  left: 2px;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: var(--background);
  box-shadow: 0 1px 2px var(--shadow);
  transition: left 150ms ease;
}

input[role='switch']:checked {
  border-color: var(--accent);
  background: var(--accent);
}

input[role='switch']:checked::before {
  left: 20px;
}

input[role='switch']:disabled {
  opacity: 0.55;
  cursor: default;
}

input[role='switch']:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
}

/* The settings, one tab per concern, standing down the side of what they show: a reader sees
   what the dialog holds without opening each one.

   Only an open dialog is laid out. A closed one keeps the user agent's display: none; giving
   every settings dialog a display of its own laid the closed ones out in the page's flow, at the
   foot of the body, where they scrolled with the page.

   One box on every tab: the shell sizes it from the window, with a margin, and never from what a
   tab holds. The header and the tab list stay put; the panel scrolls when it holds more. */
.settings-dialog {
  --dialog-width: 60rem;
  --dialog-height: 44rem;
}

#settings-modal[open],
#workspace-settings-modal[open],
#members-modal[open] {
  grid-template-columns: max-content minmax(0, 1fr);
  grid-template-rows: auto minmax(0, 1fr);
  column-gap: 1.2rem;
}

/* A dialog of one subject has no tab column: its panel takes the width, and what is said about
   loading it stands under it. */
.settings-dialog[open]:has(> .settings-single) {
  grid-template-columns: minmax(0, 1fr);
  grid-template-rows: auto minmax(0, 1fr) auto;
}

.settings-dialog [role='tablist'],
.settings-panels {
  min-height: 0;
  overflow-y: auto;
}

.settings-dialog > header {
  grid-column: 1 / -1;
}

#create-workspace-modal > header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
}

#create-workspace-modal > header h2 {
  margin: 0;
}

.settings-dialog [role='tablist'] {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  margin-top: 0.9rem;
  padding-right: 1rem;
  border-right: 1px solid var(--line);
}

.settings-dialog [role='tab'] {
  padding: 0.45rem 0.7rem;
  border-radius: 6px;
  background: transparent;
  color: var(--muted);
  text-align: left;
  white-space: nowrap;
}

.settings-dialog [role='tab']:hover {
  background: var(--raised);
  color: var(--text);
}

.settings-dialog [role='tab'][aria-selected='true'] {
  background: var(--raised);
  color: var(--text);
  font-weight: 600;
}

.settings-panels,
.search-body {
  min-width: 0;
  /* Focus paint fits inside the scroller while the negative margin holds field positions. */
  padding-inline: var(--focus-outline-reach);
  margin-inline: calc(-1 * var(--focus-outline-reach));
}

.settings-panels { padding-top: 0.9rem; }

/* The same choice where a window cannot hold a column beside the panel. Only one of the two is
   ever shown, so a reader is offered the tabs once. */
.settings-chooser {
  display: none;
  grid-column: 1 / -1;
  gap: 0.4rem;
  align-items: center;
  margin-top: 0.9rem;
  color: var(--muted);
  font-size: 0.85rem;
}

.settings-chooser select {
  flex: 1 1 auto;
  min-width: 0;
}

/* At phone width the dialog is the window, as every dialog is, and the choice of tab stands above
   the panel. */
@media (max-width: 560px) {
  #settings-modal[open],
  #workspace-settings-modal[open],
  #members-modal[open] {
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: auto auto minmax(0, 1fr);
  }

  .settings-dialog [role='tablist'] {
    display: none;
  }

  .settings-chooser {
    display: flex;
  }
}

.settings-dialog .settings-empty {
  margin: 0;
}

.settings-dialog .settings-list {
  margin: 0;
  padding: 0;
  list-style: none;
}

.settings-lead {
  margin: 0 0 0.6rem;
  color: var(--muted);
  font-size: 0.9rem;
}

.settings-actions {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  margin-top: 0.9rem;
}

#consent-saved {
  color: var(--muted);
  font-size: 0.85rem;
}

@media (max-width: 760px) {
  /* On a phone the banner is a sheet from the foot of the window, over the controls there: it
     is answered before anything else is done, and gone once it is. */
  #consent-banner {
    inset: auto 0 0 0;
    width: auto;
    max-height: 80dvh;
    overflow-y: auto;
    transform: none;
    border-radius: 14px 14px 0 0;
    padding-bottom: max(1rem, env(safe-area-inset-bottom));
  }

  .consent-actions {
    flex-wrap: wrap;
  }

  .consent-actions button {
    flex: 1 1 10rem;
  }
}
/* web/styles/workspace-settings.css */
/* What a workspace is shown by, wherever it is named: a mark, an image, or the first letter of
   what it is called. It stands where a page's mark stands on its own entry, at the height of the
   line, so a workspace is found the same way a page is. */
.workspace-icon {
  display: inline-grid;
  flex: 0 0 auto;
  place-items: center;
  width: 1.25rem;
  height: 1.25rem;
  /* The letter is drawn from the box's own size, so one rule covers the pane, the list and the
     tile in the settings. */
  font-size: 1.25rem;
  margin-right: 0.35rem;
  overflow: hidden;
  border-radius: 4px;
}

.workspace-icon .page-glyph {
  width: 1rem;
  height: 1rem;
  margin: 0;
}

.workspace-icon .page-emoji {
  margin: 0;
  font-size: 0.95rem;
  line-height: 1;
}

.workspace-icon img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* A workspace with nothing of its own carries its letter on a surface, so the row still has a
   mark where every other row has one. The surface is the one a held row uses rather than the one
   a hovered row uses: reached by the pointer, the row takes that colour, and a tile drawn in it
   would disappear into the row it sits on. The letter is sized from the tile it sits in, so it
   fills it the same way at every size it is drawn. */
.workspace-icon[data-icon='letter'] {
  background: var(--held);
  color: var(--text);
  font-weight: 600;
  line-height: 1;
}

/* Named against the tile it sits in: the list this also stands in styles its own spans, and a
   letter is not one of them. */
.workspace-icon .workspace-letter {
  font-size: 0.62em;
  color: inherit;
  /* The cap sits above the baseline, which leaves a letter drawn on its own looking high in its
     box. The line box is centred on the glyph instead. */
  display: block;
  line-height: 1;
  text-align: center;
}

/* What this workspace is, under the General tab: its name, and what it is shown by. */
.setting {
  margin-bottom: 1.4rem;
}

.setting h3 {
  margin: 0 0 0.2rem;
  font-size: 0.95rem;
}

.setting-about {
  margin: 0 0 0.6rem;
  color: var(--muted);
  font-size: 0.85rem;
}

#workspace-name-field {
  width: 100%;
  max-width: 26rem;
  padding: 0.45rem 0.6rem;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--background);
  color: var(--text);
  font: inherit;
}

#workspace-name-field:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 1px;
}

.icon-setting {
  display: flex;
  align-items: center;
  gap: 0.9rem;
}

/* The tile is the mark at the size it is chosen at: large enough to see an image in, and drawn
   on a surface so an image with a transparent ground still has a shape. */
#workspace-icon-tile {
  display: grid;
  place-items: center;
  /* A mark is drawn in the text colour of what it sits in, and what it sits in here is the page. */
  color: var(--text);
  width: 4.5rem;
  height: 4.5rem;
  padding: 0.55rem;
  border: 1px solid var(--line);
  border-radius: 12px;
  background: var(--background);
  cursor: pointer;
}

#workspace-icon-tile:hover {
  background: var(--raised);
}

#workspace-icon-tile .workspace-icon {
  width: 100%;
  height: 100%;
  margin: 0;
  border-radius: 8px;
}

#workspace-icon-tile .workspace-icon {
  font-size: 3.4rem;
}

#workspace-icon-tile .page-glyph {
  width: 2.2rem;
  height: 2.2rem;
}

#workspace-icon-tile .page-emoji {
  font-size: 2.2rem;
}

.icon-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
}

/* The tile is what this setting is about, so what acts on it is drawn quietly beside it rather
   than as three things to press. */
.icon-actions button {
  padding: 0.4rem 0.7rem;
  border: 1px solid var(--line);
  background: var(--background);
  color: var(--text);
  font-size: 0.85rem;
}

.icon-actions button:hover {
  background: var(--raised);
}

/* Organization management stays usable within the narrow settings dialog. */
[data-access-tokens] .settings-form,
[data-organizations] form,
.organization-members li {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.6rem;
  margin-block: 1rem;
}
[data-access-tokens] .settings-form label,
[data-organizations] form label,
[data-organizations] form p {
  flex-basis: 100%;
}
[data-organizations] form p {
  margin: 0;
}
[data-access-tokens] input,
[data-organizations] input {
  min-width: 0;
  flex: 1;
}
.organization-members ul {
  list-style: none;
  padding: 0;
}
.organization-members li strong {
  flex-basis: 100%;
  overflow-wrap: anywhere;
}
.organization-entry h3,
.organization-members h3,
.organization-confirmation h3 {
  overflow-wrap: anywhere;
}
.organization-confirmation button {
  margin-inline-end: 0.5rem;
}
/* The palette draws a link in the text's own colour, so the organizations' links are underlined to
   read as links, and stand apart from the control or link before them. */
[data-organizations] a {
  text-decoration: underline;
}
.organization-entry a {
  margin-inline-start: 0.8rem;
}
/* The seat table: one row per seat, its person and state beside what can be done with it. The
   columns share the settings dialog's width, so a long name or login wraps rather than widening
   the table, and a free row's login field and Invite share the row's last three cells. */
.organization-seats {
  width: 100%;
  border-collapse: collapse;
  table-layout: fixed;
  margin-block: 0.6rem 1rem;
}
.organization-seats caption {
  text-align: start;
  font-weight: 600;
  padding-block-end: 0.4rem;
  overflow-wrap: anywhere;
}
.organization-seats th,
.organization-seats td {
  text-align: start;
  vertical-align: top;
  padding: 0.45rem 0.4rem;
  border-block-start: 1px solid var(--line);
  overflow-wrap: anywhere;
}
.organization-seats th {
  color: var(--muted);
  font-weight: 500;
  font-size: 0.85rem;
}
.organization-seats th:first-child,
.organization-seats td:first-child {
  width: 3rem;
  color: var(--muted);
}
.organization-seats th:last-child {
  width: 34%;
}
.organization-seats td div + div {
  color: var(--muted);
  font-size: 0.85rem;
}
[data-organizations] .organization-seats form {
  margin-block: 0;
}
/* A free row's login keeps room to read what is typed: what its invite came to (a retry, a
   dismissal) wraps to a line of its own rather than narrowing the field. */
.organization-seats form input {
  flex: 1 1 8rem;
}
.organization-seats td button {
  max-width: 100%;
}
/* web/styles/discussion-modes.css */
/* A page kept in memory stands in the document, hidden, until the reader returns to it: its frame
   stays alive there, which a frame taken out of the document does not. */
#reader-bar > [data-kept],
.reading-area > [data-kept] {
  display: none !important;
}

/* web/styles/comment-settings.css */
/* The comment settings: resolution, which is independent of the selected commit range, and the
   folds of every discussion's replies. */
#comment-filter-options {
  position: relative;
  flex: 0 0 auto;
}
.margin-card-state {
  color: var(--muted);
  font-size: 0.75rem;
}

/* One control serves the document and comment text scales. */
.text-size {
  position: relative;
  flex: 0 0 auto;
  font-size: 12px;
}
.text-size > summary {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.25rem;
  min-height: 20px;
  cursor: pointer;
  list-style: none;
  white-space: nowrap;
}
.text-size > summary::-webkit-details-marker {
  display: none;
}
.text-size > summary > svg {
  width: 15px;
  height: 15px;
}
.text-size > div {
  display: flex;
  align-items: center;
  gap: 0.3rem;
  width: max-content;
}
.text-size output {
  min-width: 3em;
  text-align: center;
}

#comment-range > .commits-panel {
  left: auto;
  right: 0;
}
.commit[data-selected] > .commit-target {
  background: color-mix(in srgb, var(--accent) 16%, var(--background));
  color: var(--text);
  border-left-color: var(--accent);
}
@media (max-width: 760px) {
  .reading-area > .toolbar > #comment-range[open] {
    justify-self: stretch;
  }
}

.access-token-list { list-style: none; padding: 0; }
.access-token-list .setting { border-top: 1px solid var(--line); padding-top: 0.8rem; }
[data-access-tokens] input { max-width: 100%; }
[data-access-tokens] .setting > input { display: block; width: 100%; margin-bottom: 0.5rem; }

/* The filter's list and the settings menu are the comments panel's, and the panel keeps what is
   in it: both open inside it, from the header's right edge, and scroll when longer than the room
   the panel has. */
#discussion > header {
  position: relative;
}

#discussion > header > :is(#comment-range, #comment-filter-options) {
  position: static;
}

#discussion > header :is(#comment-range, #comment-filter-options) > :is(.commits-panel, .bar-menu) {
  z-index: 30;
  right: 0;
  left: auto;
  width: min(26rem, 100%);
  min-width: 0;
  height: auto;
  max-height: max(8rem, calc(var(--discussion-size) - 3.5rem));
  overflow-y: auto;
}

/* Cards view has no panel, and no room in the bar: the button stands to the right of the
   presentation control, in that view alone, and the bar it opens is the bar's height and only as
   wide as its buttons need, floating under the bar's right end. */
#comment-tools-toggle {
  display: none;
}

.reading-area[data-layout-mode='margin'] #comment-tools-toggle {
  display: grid;
}

/* A phone holds the comments' controls in the small bar in every view: its button stands at the
   right end of the page controls' row of Undo and Redo, after the page's size (`phone.css`), and
   the bar opens above it (`view.mjs`). */
@media (max-width: 760px) {
  .reading-area #comment-tools-toggle {
    display: grid;
  }
}

.comment-tools {
  /* It is a bar: the bar's own type, so a menu opened from it is drawn as the bar's menus are. */
  font-size: 0.75rem;
  position: absolute;
  top: calc(var(--reader-toolbar-height, 30px) + 4px);
  right: var(--corner-inset, 0.75rem);
  z-index: 30;
  display: flex;
  align-items: center;
  gap: 0.35rem;
  width: max-content;
  height: var(--reader-toolbar-height, 30px);
  padding: 0 0.4rem;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--background);
  box-shadow: 0 8px 24px var(--shadow);
  color: var(--text);
}

.comment-tools[hidden] {
  display: none;
}

/* The small bar stands under the bar, so its lists have that much less window to open into. */
.comment-tools :is(#comment-range, #comment-filter-options) > :is(.commits-panel, .bar-menu) {
  height: auto;
  max-height: calc(100dvh - var(--reader-toolbar-height, 30px) - 2rem);
  overflow-y: auto;
}
/* web/styles/comparison.css */
/* The comparison is drawn in the page's document frame, over its editor (`document/comparison.mjs`). */
#diff-controls { display: flex; align-items: center; gap: 0.3rem; }
#diff-controls[hidden] { display: none; }
#diff-commits { position: relative; }
#diff-commits > summary { cursor: pointer; white-space: nowrap; }
#diff-controls[aria-busy='true'] { opacity: 0.6; }
.reading-area[data-diff] #editing-controls,
.reading-area[data-diff] .discussion-margin-cards,
.reading-area[data-diff] .discussion-connectors,
.reading-area[data-diff] .document-beyond { display: none; }
#markdown-diff[aria-pressed='true'] { background: var(--held); }
@media (max-width: 760px) {
  #diff-controls { flex-wrap: wrap; }
  #diff-controls #diff-layout { display: none; }
}
#diff-commits > summary {
  box-sizing: border-box;
  height: 21px;
  padding: 0 0.4rem;
  font: inherit;
  line-height: 19px;
  white-space: nowrap;
}
@media (max-width: 760px) {
  .reading-area > .toolbar { grid-template-columns: max-content max-content minmax(0, 1fr); }
  .reading-area > .toolbar > #markdown-diff { grid-area: 1 / 2; align-self: center; }
  .reading-area > .toolbar > #page-trail { grid-area: 1 / 3; }
  .reading-area > .toolbar:has(> #commits[open]) > #markdown-diff { grid-area: 2 / 1; }
  .reading-area > .toolbar:has(> #commits[open]) > #page-trail { grid-area: 2 / 2 / 3 / -1; }
  .reading-area > .toolbar > #diff-controls { justify-self: stretch; }
  #diff-commits[open] { width: 100%; }
}
@media (min-width: 761px) {
  @container (max-width: 880px) {
    /* The title yields space to the comparison control on a narrow toolbar. */
    #page-trail { flex: 1 1 0; }
  }
  @container (max-width: 700px) {
    .reading-area[data-diff] #page-trail { display: none; }
  }
}
@media (min-width: 761px) {
  @container (max-width: 550px) {
    /* A wrapped selector opens toward the document, clear of the pages pane. */
    #reader-bar #discussion-mode [role='radiogroup'] { left: 0; right: auto; }
  }
}

.diff-status {
  grid-row: 2 / 3;
  grid-column: 1 / 2;
  position: absolute;
  inset: 0;
  display: grid;
  place-content: center;
  padding: 1rem;
  background: var(--background);
  color: var(--text);
}
.diff-status[hidden] { display: none; }
/* web/styles/editor-toolbar.css */
/* Pending lock requests retain the toolbar icon box. */
#edit-lock[aria-busy=true] { position: relative; }
#edit-lock[aria-busy=true] svg { visibility: hidden; }
#edit-lock[aria-busy=true]::after {
  content: "";
  position: absolute;
  width: 14px;
  height: 14px;
  left: calc(50% - 7px);
  top: calc(50% - 7px);
  box-sizing: border-box;
  border: 2px solid currentColor;
  border-right-color: transparent;
  border-radius: 50%;
  animation: lock-pending 0.8s linear infinite;
}
@keyframes lock-pending { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
  #edit-lock[aria-busy=true]::after { animation: none; }
}
/* web/styles/publications.css */
/* A workspace's publications: one row each, saying what it is and where it is read, with its
   actions at the end of the row and its settings opened under it. */
.publication-list > .publication {
  padding: 0.7rem 0;
  border-bottom: 1px solid var(--line);
}

.publication-row {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  justify-content: space-between;
  gap: 0.5rem 1rem;
}

.publication-summary {
  flex: 1 1 18rem;
  min-width: 0;
}

.publication-title {
  font-weight: 600;
}

/* What a publication is, as name and value pairs: a column of names, and what each holds. */
.publication-facts {
  display: grid;
  grid-template-columns: max-content minmax(0, 1fr);
  gap: 0.15rem 0.8rem;
  margin: 0.35rem 0 0;
  font-size: 0.85rem;
}

.publication-facts > div {
  display: contents;
}

.publication-facts dt {
  color: var(--muted);
}

.publication-facts dd {
  margin: 0;
  min-width: 0;
  overflow-wrap: anywhere;
}

.publication-actions {
  display: flex;
  flex: none;
  gap: 0.5rem;
}

.publication-actions > form {
  margin: 0;
}

.publication-settings-toggle[aria-expanded='true'] {
  background: var(--held);
}

.publication-edit {
  margin-top: 0.4rem;
}

/* Published, the dialog says so where its form stood, with the address it is read at. */
.publish-done {
  margin: 0.5rem 0;
}

/* Publishing one page is a small dialog of one subject: the shell's width, as tall as its form,
   which scrolls only where the window is shorter. */
.settings-dialog.publish-dialog {
  --dialog-width: 32rem;
}

/* At phone width every dialog is the window (`.dialog-shell`). */
@media (min-width: 561px) {
  .settings-dialog.publish-dialog[open] {
    height: fit-content;
    max-height: calc(100dvh - 2rem);
  }
}


/* Readers besides the workspace's members are asked for while Private is chosen. */
.publication-form:has(input[name='visibility'][value='public']:checked) [data-publication-readers] {
  display: none;
}

/* Who besides the members reads a private publication: one component in the publish form and in
   a publication's settings, the readers listed with Remove, then a username to add one. */
.publication-readers {
  margin: 0.5rem 0 0;
  padding: 0;
  border: 0;
}

.publication-readers > legend {
  margin-bottom: 0.3rem;
  padding: 0;
  font-size: 0.8rem;
  font-weight: 600;
}

.publication-readers > p {
  margin: 0.4rem 0;
  font-size: 0.8rem;
  font-weight: 400;
}

.publication-readers:has(.publication-reader-list > li) [data-readers-none] {
  display: none;
}

.publication-reader-list {
  margin: 0 0 0.5rem;
  padding: 0;
  list-style: none;
}

.publication-reader-list > li {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  padding: 0.25rem 0;
  font-size: 0.9rem;
  font-weight: 400;
  border-bottom: 1px solid var(--line);
}

.publication-reader-add {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  grid-template-areas: 'label label' 'field add';
  align-items: end;
  gap: 0.25rem 0.5rem;
}

.publication-reader-add > label {
  grid-area: label;
  margin: 0;
  font-size: 0.8rem;
  font-weight: 600;
}

.publication-reader-add > input {
  grid-area: field;
  margin: 0;
  font-size: 0.9rem;
}

.publication-reader-add > button {
  grid-area: add;
}

/* A publication's settings say which publication they edit. */
.publication-edit-title {
  margin: 0.4rem 0 0;
  font-size: 0.95rem;
}

/* One form in both dialogs, publishing a page and a publication's settings: its fields span the
   dialog's content, its choosers included. */
.publication-form {
  margin-top: 0.7rem;
}

.publication-form .chooser-field {
  max-width: none;
}

/* Every setting is named as a chooser names its own, so the fields read as one form. */
.publication-form > .setting-field,
.publication-choice > legend {
  font-size: 0.8rem;
  font-weight: 600;
}

.publication-form > .setting-field > :is(input, select) {
  margin: 0.25rem 0 1rem;
  font-size: 0.9rem;
  font-weight: 400;
}

/* A choice among a few values, Visibility and Revision, as a group of radios; the index of
   headings, drawn or not, as one checkbox. */
.publication-choice {
  margin: 0 0 1rem;
  padding: 0;
  border: 0;
}

.publication-choice > legend {
  margin-bottom: 0.3rem;
  padding: 0;
}

.publication-choice > label {
  display: flex;
  align-items: center;
  gap: 0.45rem;
  font-size: 0.9rem;
  font-weight: 400;
}

.publication-choice input:is([type='radio'], [type='checkbox']) {
  width: auto;
  margin: 0.25rem 0;
}

.publication-commits > summary {
  margin: 0.3rem 0 0;
  padding: 0.45rem 0.7rem;
  border: 1px solid var(--line);
  border-radius: 8px;
  cursor: pointer;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The history stands in the form, under its control, rather than over the dialog. */
.publication-commits > .commits-panel {
  position: static;
  width: 100%;
  height: 18rem;
  margin-top: 0.3rem;
  box-shadow: none;
}

/* A publication's own domain: where it stands, the records to add at the owner's DNS provider,
   each copied as it is, and checking or removing it. A token or an address may be longer than
   the dialog is wide, so a record breaks anywhere rather than widening it. */
.publication-domain {
  margin: 0.8rem 0 0;
  padding: 0.6rem 0 0;
  border-top: 1px solid var(--line);
}

.publication-domain > h4 {
  margin: 0 0 0.4rem;
  font-size: 0.85rem;
}

.publication-domain > p {
  margin: 0.4rem 0;
  font-size: 0.8rem;
}

/* Named as the settings above it are: the same width, label and field type. */
.domain-add {
  display: flex;
  flex-wrap: wrap;
  align-items: end;
  gap: 0.5rem;
}

.domain-add > label {
  flex: 1 1 14rem;
  margin: 0;
  font-size: 0.8rem;
  font-weight: 600;
}

.domain-add input {
  margin: 0.25rem 0 0;
  font-size: 0.9rem;
  font-weight: 400;
}

.domain-status[data-state='live'] {
  color: var(--text);
  font-weight: 600;
}

/* Still served, but its records fail: the owner is warned until when. */
.domain-status[data-state='failing'] {
  color: var(--alert);
}

.domain-records {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.78rem;
  table-layout: fixed;
}

.domain-records th,
.domain-records td {
  padding: 0.25rem 0.3rem;
  border-bottom: 1px solid var(--line);
  text-align: left;
  vertical-align: top;
}

.domain-records th:first-child,
.domain-records td:first-child {
  width: 6.5rem;
}

.domain-records code {
  overflow-wrap: anywhere;
}

.domain-records button.copy {
  margin-left: 0.25rem;
  padding: 0.1rem 0.4rem;
  font-size: 0.72rem;
}

.domain-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin: 0.5rem 0 0;
}

.domain-actions > form {
  margin: 0;
}

.publication-delete > button {
  background: transparent;
  color: var(--alert);
  border: 1px solid currentColor;
}
/* web/styles/move-picker.css */
/* "Move into…": the filter, and under it the tree or the list of the pages it matches, in a
   dialog as wide as the window allows. The tree is the pane's (above); an entry is chosen by its
   name, so the fade the pane gives a row's end for its controls has no place here. */
.move-picker {
  width: min(28rem, calc(100vw - 2rem));
}

/* Its rows: the question, the filter, and the places, which take the room left and scroll. */
.move-picker.dialog-shell[open] {
  grid-template-rows: auto auto minmax(0, 1fr);
}

.move-picker .move-filter {
  box-sizing: border-box;
  width: 100%;
  margin: 0.25rem 0 0.6rem;
}

.move-picker .move-places {
  max-height: min(24rem, 60vh);
  overflow: auto;
}

.move-picker :is(.leaf, summary)::after {
  display: none;
}

.move-picker [role=treeitem],
.move-picker [role=option] {
  cursor: pointer;
}

.move-picker [role=treeitem]:focus-visible,
.move-picker [role=option]:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
  border-radius: 4px;
}

.move-picker [role=listbox] {
  margin: 0;
  padding: 0;
  list-style: none;
}

.move-picker [role=option] {
  display: grid;
  padding: 0.35rem 0.4rem;
  border-radius: 5px;
}

.move-picker [role=option]:hover {
  background: var(--raised);
}

.move-picker .move-title {
  font-size: 0.9rem;
}

.move-picker .move-parent,
.move-picker .move-none {
  color: var(--muted);
  font-size: 0.8rem;
}
