/* The dashboard's tokens: colours in light and dark, spacing, radii and
   type sizes. One file, so that everything drawn in the dashboard's look —
   the dashboard, a page the agent makes (src/artifact), the browser
   extension's options (extension/src) — reads the same values. Light
   first; the dark set applies with the system unless the reader chose,
   and the reader's choice is stamped on the root as data-theme. */
:root {
  --background: #ffffff;
  /* The rail, one step off the page so the two read as separate surfaces
     without a rule between them. */
  --rail: #f7f7f6;
  /* What sits on the page: cards, menus, the selected pill. Level with the
     page in light, so a card is told apart by its outline rather than by a
     wash — which is why everything using it carries a border. */
  --surface: #ffffff;
  --border: #e7e7e4;
  /* What you type into inside the rail: filled rather than outlined, because
     it is a place to type and not a value being edited. */
  --field: #eeeeea;
  /* A row in the rail under the pointer. One step further from the page than
     the rail itself, so hovering reads as pressing in rather than lifting. */
  --sidebar-hover: #efefec;
  /* The border under a pointer. One step darker, so a field says it can be
     interacted with before it is focused. */
  --border-strong: #d8d8d4;
  --text: #18181b;
  --muted: #6f6f76;
  /* What you press. Near-black rather than true black, which reads as heavy.
     This is the accent, and it is deliberately not a color. */
  --accent: #1f1f22;
  --accent-text: #ffffff;
  --accent-hover: #000000;
  /* The leaf from the mark, and from the front page. Not an accent: the
     accent here is deliberately not a color. This is for the one drawing that
     is decoration rather than interface. */
  --leaf: #729d39;
  --good: #16a34a;
  --bad: #dc2626;
  --warn: #d97706;

  /* How a day felt, from awful to great, in the Journal: a blue for the low
     end, a gray for okay and the leaf's greens for the high end. Never a
     verdict (red and green mean good and bad elsewhere); a mood is a
     reading, so none of them is the color of a warning. */
  --mood-1: #2f4b8f;
  --mood-2: #6f8cc9;
  --mood-3: #a3a39b;
  --mood-4: #94bb5c;
  --mood-5: #4f7a1f;

  /* Spacing, on an 8px unit. Quarter steps below one unit, half steps above;
     anything between these is drift, not a finer scale.
       1  icon and label pairs, chip to chip
       2  button clusters, card content
       3  toolbar internals, rows in a card list
       4  panel to panel, page section gaps
       6  page level separation */
  --space-1: 6px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  /* Was referenced in three places and never defined, so every one of them
     had no margin at all: a section's intro ran straight into its first
     row. */
  --space-5: 20px;
  --space-6: 24px;

  /* Corner radius derives from one unit rather than a literal per rule.
       1  a chip, a tag, something the size of a word
       2  a control: button, input, the selected pill
       3  a panel: card, menu, dialog */
  --radius-1: 6px;
  --radius-2: 8px;
  --radius-3: 12px;
  --radius-round: 999px;

  /* Type roles, not sizes. A new role is a token here, never a number at a
     call site.
       title      the page title and the current breadcrumb
       body       running text and values, the default
       label      field labels, timestamps, secondary metadata
       overline   the label over a group, in the rail and over a section */
  --text-title: 15px;
  --text-body: 14px;
  --text-label: 12px;
  /* Not small caps. At this size the letter-spacing does the separating, and
     uppercase only makes it harder to read. */
  --text-overline: 12.5px;

  /* How wide prose is allowed to get. One number, because two paragraphs on
     one page wrapping in different places is the kind of thing that looks
     like a bug and is one. Controls, tables and panels are not prose and are
     not capped. */
  --measure: 74ch;

  /* Shadow means "this responds to you". Menus float above the page and cast
     one; a static panel is an outline and a background, never a shadow.

     The one exception is the selected pill in the rail, which lifts by a
     single pixel — enough to read as raised off the gray without becoming a
     second floating thing. */
  --shadow-floating: 0 8px 28px rgb(0 0 0 / 22%);
  --shadow-raised: 0 1px 2px rgb(0 0 0 / 6%);
}

/* Light is the palette above, and is what a reader gets unless something says
   otherwise. Dark is defined once and applied twice: for somebody whose system
   asks for it and who has not overridden it, and for somebody who chose it
   outright. Written this way so that choosing light wins over a dark system,
   and choosing dark wins over a light one — a toggle that only worked in one
   direction would be worse than no toggle. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme='light']) {
    --background: #141416;
    --rail: #0c0c0e;
    --surface: #1a1a1d;
    --border: #2a2a2e;
    --field: #18181b;
    --sidebar-hover: #17171a;
    --border-strong: #3a3a40;
    --text: #f4f4f5;
    --muted: #a0a0a8;
    /* Inverted, not dimmed: the near-black button becomes a near-white one,
       and the text on it becomes near-black. */
    --accent: #f4f4f5;
    --accent-text: #18181b;
    --accent-hover: #ffffff;
    /* Lifted off the leaf, which is too dark to read on this ground. */
    --leaf: #9fca63;
    --good: #4ade80;
    --bad: #f87171;
    --warn: #fbbf24;
    --mood-1: #7c9be6;
    --mood-2: #a9bde8;
    --mood-3: #8a8a92;
    --mood-4: #b5d68a;
    --mood-5: #9fca63;
  }
}

:root[data-theme='dark'] {
  --background: #141416;
  --rail: #0c0c0e;
  --surface: #1a1a1d;
  --border: #2a2a2e;
  --field: #18181b;
  --sidebar-hover: #17171a;
  --border-strong: #3a3a40;
  --text: #f4f4f5;
  --muted: #a0a0a8;
  /* Inverted, not dimmed: the near-black button becomes a near-white one,
     and the text on it becomes near-black. */
  --accent: #f4f4f5;
  --accent-text: #18181b;
  --accent-hover: #ffffff;
  --leaf: #9fca63;
  --good: #4ade80;
  --bad: #f87171;
  --warn: #fbbf24;
  --mood-1: #7c9be6;
  --mood-2: #a9bde8;
  --mood-3: #8a8a92;
  --mood-4: #b5d68a;
  --mood-5: #9fca63;
}

* {
  box-sizing: border-box;
}

/* A scrollbar that does not draw a gray slab down the side of a quiet page. */
*::-webkit-scrollbar {
  width: 10px;
  height: 10px;
}
*::-webkit-scrollbar-track {
  background: transparent;
}
*::-webkit-scrollbar-thumb {
  background-color: var(--border);
  border-radius: var(--radius-2);
  border: 3px solid var(--background);
}
*::-webkit-scrollbar-thumb:hover {
  background-color: var(--border-strong);
}

html {
  /* Landscape on iOS inflates text in a way that breaks the table layouts. */
  -webkit-text-size-adjust: 100%;
  /* Tells the browser which form controls and scrollbars to draw. */
  color-scheme: light dark;
}

:root[data-theme='light'] {
  color-scheme: light;
}
:root[data-theme='dark'] {
  color-scheme: dark;
}

/* One stylesheet, no framework. The dashboard is a handful of tables and
   forms; a component library would be more code than the application.

   THE LOOK: quiet chrome, color only where it means something.

   Almost nothing here is colored. The rail is a warm gray against a white
   page, the selected row is a raised pill rather than a highlight, and what
   you press is near-black. Color is kept for the few things that carry state
   — whether a DNS record is published, whether a delivery failed, whether a
   session is the one you are reading this in — so that when something is
   colored it is worth looking at.

   It inverts rather than dims for dark. A near-black button on white becomes
   a near-white button on black; the rail stays one step darker than the page
   in both, so the same shape reads the same way round.

   The TeaNode mark keeps its green. It is the one piece of brand here, and
   chrome that competed with it would make it just another colored thing —
   which is what an accent taken from the logo did: every button, link and
   active row was the brand color, so none of them meant anything. */

body {
  margin: 0;
  background: var(--background);
  color: var(--text);
  font:
    15px/1.5 -apple-system,
    BlinkMacSystemFont,
    'Segoe UI',
    Roboto,
    Helvetica,
    Arial,
    sans-serif;
}

/* A link in running text is the text color, underlined. The accent is what
   you press, and painting every link with it turns a page of prose into a
   page of buttons. */
a {
  color: var(--text);
  text-decoration-color: var(--border-strong);
  text-underline-offset: 2px;
}
a:hover {
  text-decoration-color: var(--text);
}

/* A link in a table, a card header or the rail is a destination, not prose.
   Underlining every one of them turns a list of subjects into a wall of
   rules; the row already says it can be clicked. */
td a,
th a,
.sidebar a,
.tile,
.breadcrumb a {
  text-decoration: none;
}

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

/* The window, once. A row of two: the rail down the left at full height, and
   everything else in a column beside it. The page itself never scrolls — the
   content area does — which is what keeps the rail and the bar in place while
   a long list moves under them.

   min-height: 0 on the flex children is what makes that scroller work: a flex
   item defaults to min-height: auto and refuses to shrink below its content,
   so without it the whole page grows instead. */
.layout {
  display: flex;
  height: 100vh;
  height: 100dvh;
  overflow: hidden;
}

.main-column {
  display: flex;
  flex-direction: column;
  flex: 1;
  min-width: 0;
  min-height: 0;
}

/* --- the bar across the top ----------------------------------------------
   Left to right: the control that collapses the rail, the product, where you
   are, and then the things that are about you rather than about the page. */
.topbar {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex: 0 0 auto;
  height: 56px;
  padding: 0 var(--space-3);
  padding-top: env(safe-area-inset-top);
  background: var(--background);
  border-bottom: 1px solid var(--border);
  z-index: 20;
}

/* On the bar the trail stands in for the page heading, so the page's own name
   is on the end of it and carries the weight; the way back up stays quiet
   behind it. */
.topbar .breadcrumb {
  margin: 0;
  /* The one thing on the bar that gives way, and it shrinks rather than
     pushing: without the basis, a long subject makes the row overflow and
     everything in it is squeezed to make room. */
  flex: 0 1 auto;
  min-width: 0;
  overflow: hidden;
  font-size: var(--text-body);
}

.topbar .breadcrumb .crumb.current {
  color: var(--text);
  font-weight: 600;
  min-width: 0;
  overflow: hidden;
}

/* What follows the name on the bar ("You") keeps its place when the name is
   long enough to be cut. */
.crumb-note {
  flex: 0 0 auto;
  font-weight: 400;
}

/* The label inside it, not the crumb: a crumb is a flex row — the chevron and
   the word — and text-overflow says nothing about a flex item, so a subject
   too long for the bar was cut off mid-word with no sign that there was more
   of it. The word itself is what has to give way, and an ellipsis is what
   says so. */
.topbar .breadcrumb .crumb.current > a,
.topbar .breadcrumb .crumb.current > span:not(.separator):not(.crumb-note) {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.topbar-controls {
  display: flex;
  align-items: center;
  gap: 2px;
  margin-left: auto;
  flex: 0 0 auto;
}

/* A square, quiet until it is under the pointer. Used by the hamburger and by
   each of the menus on the right, so they line up and behave alike.
   
   It never gives way. A square with a width and nothing said about shrinking
   is still a flex item that may be shrunk, so a breadcrumb too long for the
   bar -- a subject, on a phone -- squeezed the hamburger beside it out of
   square and moved what was in it. The row has one thing in it that is meant
   to give way, and this is not it. */
.icon-button {
  display: flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: 34px;
  height: 34px;
  padding: 0;
  border: none;
  border-radius: var(--radius-1);
  background: none;
  color: var(--muted);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}

.icon-button:hover {
  background: var(--border);
  color: var(--text);
}

/* --- where you are --------------------------------------------------------
   Directly above the heading, in the content: the trail says how you got to
   this page and the heading names it, so they belong together. Ancestors
   only — see Breadcrumb() for why the page's own name is not repeated. */
.breadcrumb {
  display: flex;
  align-items: center;
  min-width: 0;
  /* The same air between a crumb and the chevron after it as the crumb below
     puts between that chevron and its own label. Without it the gap fell on
     one side only, and the trail read as "Domains> example.com> Templates" —
     each chevron stuck to the word behind it. */
  gap: var(--space-2);
  margin-bottom: var(--space-2);
  overflow: hidden;
  font-size: var(--text-label);
  color: var(--muted);
}

/* Each crumb carries the chevron that precedes it, so one rule sets the air
   on both sides of every separator: this gap after it, the container's gap
   before it. */
.crumb {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-width: 0;
}

/* The crumbs behind you keep their full width; the one you are on gives way,
   because it is the only one that can be arbitrarily long — a message subject
   is a sentence. */
.crumb:not(:last-child) {
  flex: 0 0 auto;
}

.crumb:last-child {
  flex: 0 1 auto;
  min-width: 0;
}

.breadcrumb a {
  color: var(--muted);
  text-decoration: none;
  white-space: nowrap;
}

.breadcrumb a:hover {
  color: var(--text);
  text-decoration: underline;
}

.breadcrumb .separator {
  display: flex;
  align-items: center;
  flex: 0 0 auto;
  color: var(--muted);
  opacity: 0.5;
}

/* --- menus hanging off the bar -------------------------------------------- */
.menu-button {
  position: relative;
}

/* The twelve colors a person without a picture gets behind their initial,
   chosen by the server from a hash of their id so the same person is the same
   color on every screen and in the iPhone app (the app carries the same
   twelve). Each holds white text at 4.5 to 1 or better; the dark set is the
   same hues one step down, so a circle of color does not glare on the dark
   ground. */
:root {
  --avatar-0: #b23a2e;
  --avatar-1: #b0521a;
  --avatar-2: #8c6600;
  --avatar-3: #5c7a1c;
  --avatar-4: #1d7a45;
  --avatar-5: #0f7a73;
  --avatar-6: #0e6e94;
  --avatar-7: #2f5fb3;
  --avatar-8: #5b4fc2;
  --avatar-9: #8a44b3;
  --avatar-10: #a63a82;
  --avatar-11: #5f6873;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme='light']) {
    --avatar-0: #a8392e;
    --avatar-1: #a4501c;
    --avatar-2: #7d5c00;
    --avatar-3: #55711c;
    --avatar-4: #1b6d3f;
    --avatar-5: #0e6d67;
    --avatar-6: #0e6485;
    --avatar-7: #2c56a3;
    --avatar-8: #5348b0;
    --avatar-9: #7c3fa3;
    --avatar-10: #953676;
    --avatar-11: #565e69;
  }
}

:root[data-theme='dark'] {
  --avatar-0: #a8392e;
  --avatar-1: #a4501c;
  --avatar-2: #7d5c00;
  --avatar-3: #55711c;
  --avatar-4: #1b6d3f;
  --avatar-5: #0e6d67;
  --avatar-6: #0e6485;
  --avatar-7: #2c56a3;
  --avatar-8: #5348b0;
  --avatar-9: #7c3fa3;
  --avatar-10: #953676;
  --avatar-11: #565e69;
}

/* A person: their picture, or their initial on a color of their own. Sized by
   the component, which sets width, height and font size inline because the
   rail, a list row and the settings page each want a different one. */
.avatar {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  border-radius: 50%;
  color: #ffffff;
  font-weight: 600;
  line-height: 1;
  user-select: none;
  -webkit-tap-highlight-color: transparent;
}

.avatar-picture {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border-radius: 50%;
  object-fit: cover;
}

.avatar-color-0 {
  background: var(--avatar-0);
}

.avatar-color-1 {
  background: var(--avatar-1);
}

.avatar-color-2 {
  background: var(--avatar-2);
}

.avatar-color-3 {
  background: var(--avatar-3);
}

.avatar-color-4 {
  background: var(--avatar-4);
}

.avatar-color-5 {
  background: var(--avatar-5);
}

.avatar-color-6 {
  background: var(--avatar-6);
}

.avatar-color-7 {
  background: var(--avatar-7);
}

.avatar-color-8 {
  background: var(--avatar-8);
}

.avatar-color-9 {
  background: var(--avatar-9);
}

.avatar-color-10 {
  background: var(--avatar-10);
}

.avatar-color-11 {
  background: var(--avatar-11);
}

/* Signed in as somebody else: the avatar wears a mark in the warning color,
   so it shows on every page, the narrowed rail included, without a line of
   its own. */
.avatar-impersonated {
  position: relative;
  box-shadow: 0 0 0 2px var(--warn);
}

.avatar-badge {
  position: absolute;
  right: -5px;
  bottom: -4px;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 15px;
  height: 15px;
  border-radius: 50%;
  background: var(--warn);
  color: var(--surface);
}

.menu-impersonated,
.menu-marked {
  position: relative;
}

/* Invitations to a circle that wait for an answer, on the corner of the
   account button and of the phone's menu button, in the accent as the number
   on the drawer's launcher. */
.circle-invitation-mark {
  position: absolute;
  top: -6px;
  right: -8px;
  min-width: 16px;
  padding: 0 4px;
  border-radius: 8px;
  background: var(--accent);
  color: var(--accent-text);
  font-size: 11px;
  font-weight: 600;
  line-height: 16px;
  text-align: center;
  font-variant-numeric: tabular-nums;
  box-shadow: 0 0 0 2px var(--background);
}

.menu-marked > .circle-invitation-mark {
  top: 2px;
  right: 0;
}

.menu {
  /* Fixed, and placed from the button's rectangle: it is rendered into the
     body so that a table's scroll container cannot clip it. */
  position: fixed;
  max-height: min(80vh, 640px);
  overflow-y: auto;
  /* Wide enough for a domain name. The trigger is as narrow as its column,
     and the menu is not: it is positioned, so it can be wider than the cell
     it hangs from, and truncating "example.com" to "exam…" in a menu whose
     whole job is telling them apart is no help at all. */
  min-width: 240px;
  max-width: 340px;
  padding: 6px;
  background: var(--background);
  border: 1px solid var(--border);
  border-radius: var(--radius-2);
  box-shadow: var(--shadow-floating);
  /* Above the rail, which on a phone is a fixed overlay at 40 with a scrim at
     35 — and the menu is rendered into the body, so it is the rail's sibling
     rather than its child and does not inherit its layer. At 30 the account
     menu opened behind the rail that triggered it: invisible, and nothing on
     it could be tapped. Below the dialog scrim at 50, because a dialog is
     modal and a menu is not. */
  z-index: 45;
}

/* A rule between groups of menu items. Ten items in one column read as a
   list to search; three short groups read as three choices. */
.menu-separator {
  height: 1px;
  margin: 4px -6px;
  background: var(--border);
}

.menu-header {
  padding: 6px 8px 8px;
  margin-bottom: 4px;
  border-bottom: 1px solid var(--border);
  font-size: 12px;
  color: var(--muted);
  overflow-wrap: anywhere;
}

/* The slot before a choice in a menu of things that can be ticked, so that
   the ticked and the unticked line up. */
.menu-check {
  display: inline-flex;
  flex: 0 0 14px;
  width: 14px;
  height: 14px;
}

.menu button,
.menu a {
  display: flex;
  align-items: center;
  gap: 9px;
  width: 100%;
  padding: 8px;
  border: none;
  border-radius: 6px;
  background: none;
  color: var(--text);
  font: inherit;
  font-size: 14px;
  text-align: left;
  text-decoration: none;
  cursor: pointer;
}

.menu button:hover,
.menu a:hover {
  background: var(--sidebar-hover);
}

/* The option in effect. A tick would need a column of its own for the sake of
   one row; the accent says the same thing in the space already there. */
.menu button.selected {
  color: var(--accent);
}

/* --- a grid of destination cards ------------------------------------------
   Used by the settings hub and the domain list: a handful of things, each
   with a name, a line about it, and somewhere it goes. A table would be
   denser and would say less. */
.surface-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: var(--space-3);
  margin-bottom: var(--space-6);
}

.surface-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: 34px;
  height: 34px;
  border-radius: var(--radius-1);
  background: var(--background);
  color: var(--accent);
}

/* A group label above a grid or a list. */
/* The small gray label over a group, in the rail and over a section of
   settings. Not uppercase: at this size the weight and the color do the
   separating, and capitals only make it harder to read. */
/* A line above a list: what it is on the left, what you can do to it on the
   right. */
.page-actions {
  display: flex;
  /* Top, not center: the heading is above and the action belongs level with
     the first line of the intro, not halfway down a paragraph. */
  align-items: flex-start;
  gap: var(--space-4);
  margin-bottom: var(--space-4);
  /* Two buttons in a card on a phone come to 348px of the 350 there are, and
     that is with the shortest of the three catalogues. A row that cannot fit
     wraps rather than running off the side. */
  flex-wrap: wrap;
}

/* The text beside the action takes the room; the action does not. Excluding
   the controls by name, because a page whose first child IS the button had it
   stretched across the whole width — :first-child outranks the rule below
   that says a button keeps its size. */
.page-actions > :first-child:not(button):not(a):not(.tag) {
  flex: 1;
  min-width: 0;
}

/* One action and nothing beside it sits at the end of the row. Three pages
   did this by putting an empty <span /> first so that the rule above had
   something to stretch; a modifier says what is meant. */
.page-actions-end {
  justify-content: flex-end;
}

/* The last row of a page, at the end of the scrolling area, is where the
   drawer's round button (44 pixels, 20 from the corner) floats. A narrow
   window keeps room under the content for it; a wide one does not, and an
   action at the right end of the last row came to rest half under it. */
.page-actions-foot {
  margin-bottom: 48px;
}

/* Room above it when it follows something. At the top of a page this row is
   the heading's own line and needs none; under the release notes it had none
   either, and Check now sat against the last line of the changelog. */
.page-actions:not(:first-child) {
  margin-top: var(--space-4);
}

.page-actions button {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  flex: 0 0 auto;
}

.icon-button.active {
  color: var(--accent);
  background: var(--border);
}

button.destructive {
  border-color: var(--bad);
  color: var(--bad);
}

/* --- overview bands -------------------------------------------------------
   A band is a small heading and a row of tiles under it, after the reference:
   the heading names what the row is about so the tiles do not have to repeat
   it, and the rows read as groups rather than as one long grid. */
.band {
  margin-bottom: var(--space-6);
}

.band-heading {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin: 0 0 var(--space-3);
  font-size: var(--text-overline);
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
}

.band-icon {
  display: flex;
  color: var(--muted);
}

/* Anything that belongs to the heading rather than to a tile: a period the
   numbers cover, a link to the full list. */
.band-aside {
  margin-left: auto;
  text-transform: none;
  letter-spacing: normal;
  font-weight: 400;
}

/* Equal columns and equal heights, so tiles with different amounts to say
   still end level. */
/* auto-fit rather than auto-fill: a band of five tiles should fill the row it
   is given rather than leave a fifth stranded under four. The track is narrow
   enough that a normal desktop takes all five, and tiles stretch to the tallest
   in the row so a wrapped detail line does not stagger them. */
.tile-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: var(--space-3);
  align-items: stretch;
}

/* The whole tile is the target when it has somewhere to go — one thing to aim
   at, the size of the thing you are aiming at. */
.tile {
  position: relative;
  display: flex;
  flex-direction: column;
  height: 100%;
  padding: var(--space-4);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-3);
  color: var(--text);
  text-decoration: none;
}

a.tile:hover {
  border-color: var(--muted);
}

/* The border, and nothing else. Hover used to recolour the number as well,
   which meant a green "20 accepted" turned near-black under the pointer and a
   red one turned near-black too — the color that was carrying the meaning
   disappeared exactly when somebody reached for it. */
a.tile:hover strong {
  color: var(--accent);
}

/* --- a tile that is a number --------------------------------------------- */
.tile-label {
  font-size: var(--text-overline);
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
}

/* The answer, in the size the eye lands on first. Tabular figures so a column
   of them does not jitter as they change. */
.tile-value {
  display: flex;
  align-items: baseline;
  gap: var(--space-1);
  margin-top: var(--space-2);
  font-size: 30px;
  font-weight: 600;
  line-height: 1.1;
  font-variant-numeric: tabular-nums;
}

/* No color on the number. It said "this many were accepted" in green and
   "this many were rejected" in red, which reads as a verdict on the number —
   but a rejection count is not bad news, it is the count of a thing that
   happened, and zero of them is not good news either. The word beside it says
   which it is. What is left colored is the warning mark on a tile that
   actually wants attention, which is the one place the color means
   something. */

/* The unit belongs to the number but is not part of it, so it stays the
   weight of ordinary text. */
.tile-unit {
  font-size: var(--text-body);
  font-weight: 400;
  color: var(--muted);
}

/* The line under the number, and the last thing in a tile: pushed to the
   bottom so a row of tiles lines up along it. */
.tile-detail {
  margin-top: auto;
  padding-top: var(--space-3);
  font-size: var(--text-label);
  color: var(--muted);
}

/* An icon in the tile's own corner, for a state worth seeing before the
   number is read. */
.tile-corner {
  position: absolute;
  top: var(--space-4);
  right: var(--space-4);
  display: flex;
  color: var(--warn);
}

/* A number that is not known yet, rather than one that is zero. */
.tile-unknown {
  color: var(--muted);
  font-weight: 400;
}

/* --- a tile that is a destination ---------------------------------------- */
.tile.resource strong {
  margin-top: var(--space-3);
  font-size: var(--text-body);
  font-weight: 600;
}

.tile-corner-icon {
  display: flex;
  color: var(--accent);
}

/* --- the agent's conversations, a tile each ------------------------------ */
/* One row above the tiles: which kind, the search box taking the rest of
   it, and starting a new conversation at its end. */
.conversation-toolbar {
  display: flex;
  /* The tabs, the search box and the button the same height: the row's. */
  align-items: stretch;
  gap: var(--space-2);
  margin-bottom: var(--space-4);
}

/* As tall as the box beside it, as the mailbox's list toolbar does. */
.conversation-toolbar .segmented {
  flex: 0 0 auto;
  align-self: stretch;
}

.conversation-toolbar .segmented button {
  height: 100%;
}

.conversation-toolbar input[type='search'] {
  flex: 1 1 0;
  min-width: 0;
  max-width: 420px;
  /* The size of the search box on every other page's list. */
  padding-block: 5px;
  font-size: var(--text-label);
}

/* At the row's end even when there is no search box to push it there. */
.conversation-toolbar .conversation-new {
  flex: 0 0 auto;
  margin-left: auto;
}

/* auto-fill rather than the band's auto-fit: one conversation is one tile's
   width, not a banner across the page. min() so a phone's single column is
   never wider than the screen. */
.conversation-tiles {
  grid-template-columns: repeat(auto-fill, minmax(min(260px, 100%), 1fr));
  margin: 0;
  padding: 0;
  list-style: none;
}

/* The tile is a button: the whole card opens the conversation. What a
   button is given everywhere else (its padding, its centred text, the
   background it takes under the pointer) is put back to the tile's own. */
button.conversation-tile {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  width: 100%;
  min-width: 0;
  padding: var(--space-4);
  border-radius: var(--radius-3);
  background: var(--surface);
  color: var(--text);
  font-weight: inherit;
  text-align: left;
  cursor: pointer;
  transition:
    transform 150ms ease,
    box-shadow 150ms ease,
    border-color 150ms ease;
}

/* It lifts under the pointer, a little higher with a shadow under it, so
   it reads as a thing to press rather than a panel. Nothing inside it
   changes color: the card is what is pressed, not its title. Only where
   there is a pointer to hover: a phone's tap sets hover as it presses,
   and a tile that moves under the finger takes the press's ripple away
   with it (ripple.ts lets a mark go when its control moves). */
button.conversation-tile:hover:not(:disabled),
button.conversation-tile:focus-visible {
  background: var(--surface);
}

@media (hover: hover) {
  button.conversation-tile:hover:not(:disabled) {
    border-color: var(--muted);
    box-shadow: 0 6px 18px rgba(0, 0, 0, 0.12);
    transform: translateY(-2px);
  }
}

button.conversation-tile:focus-visible {
  border-color: var(--muted);
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.12);
  transform: translateY(-2px);
}

@media (prefers-reduced-motion: reduce) {
  button.conversation-tile {
    transition: none;
  }

  button.conversation-tile:hover:not(:disabled),
  button.conversation-tile:focus-visible {
    transform: none;
  }
}

.conversation-tile-title {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  min-width: 0;
  font-weight: 600;
  overflow-wrap: anywhere;
}

/* What the agent wrote of the conversation, cut at three lines so a long
   one does not make its whole row of tiles tall. */
.conversation-tile-summary {
  display: -webkit-box;
  margin: 0;
  overflow: hidden;
  color: var(--muted);
  font-size: var(--text-label);
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 3;
  overflow-wrap: anywhere;
}

/* Where the search matched: a wash of the warning color under the words,
   and the text keeps its own color so it still reads in either theme. */
.conversation-tile mark {
  padding: 0 1px;
  border-radius: 2px;
  background: color-mix(in srgb, var(--warn) 35%, transparent);
  color: inherit;
}

.conversation-tile .tile-detail {
  display: flex;
  align-items: center;
  gap: var(--space-1);
}

/* --- a modal asking for one thing ---------------------------------------- */
.dialog-scrim {
  position: fixed;
  inset: 0;
  z-index: 50;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-4);
  background: rgb(0 0 0 / 45%);
  /* A dialog taller than the screen scrolls with the scrim rather than
     hanging off both ends of it: a group with three lists in it was a
     dialog whose title was above the window and whose buttons were below,
     with nothing to scroll. */
  overflow-y: auto;
  overscroll-behavior: contain;
}

.dialog {
  width: 100%;
  max-width: 420px;
  padding: var(--space-4);
  background: var(--background);
  border: 1px solid var(--border);
  border-radius: var(--radius-2);
  box-shadow: var(--shadow-floating);
}

.dialog h3 {
  margin-top: 0;
}

.dialog label {
  display: block;
  margin-bottom: var(--space-3);
}

.dialog-actions {
  display: flex;
  justify-content: flex-end;
  /* Centred on the row: the icons at the other end are shorter than the
     buttons, and sat on the row's top edge. */
  align-items: center;
  gap: var(--space-2);
  /* More actions than a phone's width puts the far-side ones on a line of
     their own, rather than off the screen's left edge. */
  flex-wrap: wrap;
  margin-top: var(--space-4);
  /* Where the dialog is taller than the screen, Cancel and Save stay on it:
     they are the way out, and hunting for them by scrolling is not a way
     out. */
  position: sticky;
  bottom: calc(var(--space-4) * -1);
  /* The dialog's own bottom padding, kept: the negative margin below
     cancels it, and without this the gutter under the buttons would be
     half what every other dialog has. */
  padding: var(--space-2) 0 var(--space-4);
  margin-bottom: calc(var(--space-4) * -1);
  background: var(--background);
}

/* An action that is neither the way forward nor the way out -- deleting what
   is being edited -- sits at the other end of the footer, away from Save.
   Together with it, because it also ends the dialog; apart from it, because
   pressing it by accident is not the same size of mistake. */
.dialog-actions-other {
  margin-right: auto;
  /* More than one button on the far side keeps the row's own spacing. */
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}

/* A dialog's far-side actions, the explorer's and a transaction's Ask the
   agent, are icons as tall as the buttons beside them, with a border so
   they read as buttons. */
.graph-explore-dialog-action,
.dialog-icon-action {
  width: 40px;
  height: 40px;
  border: 1px solid var(--border);
  color: var(--text);
}

/* A button keeps its word whole; the icons beside it are narrow enough
   that the row fits a phone. */
.dialog-actions button {
  white-space: nowrap;
}

/* --- a picture, over the page ---------------------------------------------
   Clicking a picture used to leave for a tab of raw bytes. It opens here
   instead: the dialogs' own scrim, with a stage in place of a form, and the
   picture on it to be zoomed and moved around. The address is still on the
   anchor underneath, so a middle click goes to the file the way it did. */
.picture-opener {
  /* Block, and no wider than the picture in it, so that the anchor is the
     picture rather than the whole line the picture sits on -- and so that
     an inline anchor's baseline no longer leaves a gap under it. */
  display: block;
  width: fit-content;
  max-width: 100%;
  cursor: zoom-in;
}

.lightbox-scrim {
  /* The stage answers what does not fit by moving the picture, so the scrim
     itself never scrolls: a pinch that zooms the picture and scrolls what is
     under it at the same time is neither gesture. */
  overflow: hidden;
  /* Darker than a dialog's. Here the page behind is not context, it is the
     thing competing with what is being looked at. */
  background: rgb(0 0 0 / 78%);
}

.lightbox {
  display: flex;
  flex-direction: column;
  width: 100%;
  height: 100%;
  max-width: 1100px;
  overflow: hidden;
  background: var(--background);
  border: 1px solid var(--border);
  border-radius: var(--radius-3);
  box-shadow: var(--shadow-floating);
}

.lightbox-head,
.lightbox-foot {
  display: flex;
  flex: 0 0 auto;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
}

/* The one word among the icons.
   It was drawn as an underlined link, which made the only control in this
   row that says what it does in words read as a footnote, while the two
   that say it with a picture read as controls. Same treatment as they
   have, at a word's shape rather than a square: as tall as they are, quiet
   until the pointer is on it, and no rule under it. */
.lightbox-fit {
  height: 34px;
  padding: 0 var(--space-2);
  border: none;
  border-radius: var(--radius-1);
  background: none;
  color: var(--muted);
  font-size: var(--text-label);
  cursor: pointer;
}

.lightbox-fit:hover:not(:disabled) {
  background: var(--border);
  color: var(--text);
}

.lightbox-head {
  border-bottom: 1px solid var(--border);
}

.lightbox-foot {
  border-top: 1px solid var(--border);
}

/* What the picture is, and under it where it was posted. A file name can be
   longer than the row, so it is cut rather than allowed to push the way out
   off the end. */
.lightbox-name {
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
  min-width: 0;
}

.lightbox-name strong,
.lightbox-name .muted {
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.lightbox-name strong {
  font-size: var(--text-body);
}

.lightbox-name .muted {
  font-size: var(--text-label);
}

/* The ground the picture moves over. touch-action is none because every
   touch landing here is the lightbox's own: left alone, the browser reads
   the pinch as a page zoom and the drag as a scroll, and the picture does
   neither. */
.lightbox-stage {
  display: flex;
  flex: 1 1 auto;
  align-items: center;
  justify-content: center;
  min-height: 0;
  overflow: hidden;
  background: var(--field);
  touch-action: none;
  cursor: grab;
}

.lightbox-stage.dragging {
  cursor: grabbing;
}

.lightbox-picture {
  width: auto;
  height: auto;
  max-width: 100%;
  max-height: 100%;
  /* The middle, which is what the arithmetic behind the gesture measures
     from and where a zoom with no pointer in it should stay. */
  transform-origin: center;
  user-select: none;
  -webkit-user-drag: none;
}

/* Wide enough for three digits and the sign, so the buttons on either side
   of the number do not step sideways as it grows. */
.lightbox-scale {
  min-width: 5ch;
  font-size: var(--text-label);
  font-variant-numeric: tabular-nums;
  text-align: center;
}

.lightbox-hint {
  margin-left: auto;
  font-size: var(--text-label);
}

@media (max-width: 600px) {
  /* No room for a sentence beside the controls, and a finger finds the
     pinch without being told about it. */
  .lightbox-hint {
    display: none;
  }
}

/* --- the rail ------------------------------------------------------------- */
/* The product, at the top of its own rail. */
.sidebar-brand {
  display: flex;
  align-items: center;
  flex: 0 0 auto;
  gap: var(--space-2);
  height: 56px;
  /* 18px, which is where the icons below start: 8px of row margin and 10px of
     row padding. The mark is the first thing in that column, not a thing
     beside it. */
  padding: 0 var(--space-4) 0 18px;
  font-size: var(--text-title);
  font-weight: 600;
  letter-spacing: 0.02em;
  white-space: nowrap;
  margin-bottom: var(--space-2);
  color: var(--text);
  text-decoration: none;
}

/* No rule under the mark. The rail is already a step off the page, and a
   border on top of that is a second line saying the same thing. */
.sidebar-brand:hover {
  opacity: 0.75;
}

/* The mark stays exactly where it is when the rail narrows. Centring it while
   collapsed looked right in a screenshot and wrong in motion: the rule applies
   the instant the class changes, so the mark jumped to the middle of a rail
   that was still full width and then slid left as the rail caught up. It sits
   at the same offset as the icons below it in both states, and nothing about
   it moves. */
.sidebar.collapsed .sidebar-brand {
  padding-right: 0;
}

/* The rail's width, said once for whatever sits beside it (the toasts begin
   where the content does). The rail is collapsed to icons when it carries the
   collapsed class. */
:root {
  --rail-width: 232px;
}

:root:has(.sidebar.collapsed) {
  --rail-width: 60px;
}

.sidebar {
  display: flex;
  flex-direction: column;
  width: var(--rail-width);
  flex: 0 0 var(--rail-width);
  min-height: 0;
  height: 100vh;
  height: 100dvh;
  overflow: hidden;
  background: var(--rail);
  border-right: 1px solid var(--border);
  /* No padding under the account row: that row has its own, and the two
     together left the name sitting twice as far from the bottom edge as from
     the line above it. */
  overflow-y: auto;
}

/* Collapsed, it keeps the icons and drops the words. Wide enough for a
   comfortable target around a centered icon and nothing more. */
.sidebar.collapsed {
  width: var(--rail-width);
  flex: 0 0 var(--rail-width);
}

.sidebar.collapsed .sidebar-label {
  display: none;
}

.sidebar.collapsed nav a {
  justify-content: center;
  padding-left: 0;
  padding-right: 0;
}

/* A rule between the group that answers "what is happening" and the group
   that answers "what is configured". */
.sidebar-group + .sidebar-group {
  margin-top: 8px;
  padding-top: 8px;
  border-top: 1px solid var(--border);
}

/* A rule inside a group, where two groups would be too much: the rail's one
   group of mailbox rows has three parts to it — what is always at the top,
   the folders, and what is about the mailbox rather than in it. Inset from
   the edges so it reads as a seam in a list rather than the end of the rail. */
.sidebar-divider {
  height: 0;
  margin: 8px 0;
  border-top: 1px solid var(--border);
}

.sidebar-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  color: var(--muted);
}

/* A row in the rail is quiet until you are on it: muted text, no fill, and a
   1px transparent border so that becoming selected does not move it. */
.sidebar nav a {
  display: flex;
  align-items: center;
  gap: 11px;
  margin: 1px 8px;
  padding: 8px 10px;
  border: 1px solid transparent;
  border-radius: var(--radius-2);
  color: var(--muted);
  font-size: 14px;
  font-weight: 500;
  text-decoration: none;
  white-space: nowrap;
}

/* Tighter rows with a pointer: the mailbox's rail is two dozen rows and the
   last three, Recordings, Coding and Finance, were below the fold of a
   900px window behind a thin scrollbar. The drawer on a phone keeps its
   full-height rows for a thumb. */
@media (min-width: 901px) {
  .sidebar nav a {
    padding-top: 5px;
    padding-bottom: 5px;
  }
}

.sidebar nav a:hover {
  background: var(--sidebar-hover);
  color: var(--text);
}

.sidebar nav a:hover .sidebar-icon {
  color: var(--text);
}
/* The row you are on: a raised pill rather than a wash of color. It says
   "you are here" without spending the one accent the page has on navigation,
   and it still reads as selected when the rail is collapsed to icons. */
.sidebar nav a.active {
  background: var(--surface);
  border-color: var(--border);
  box-shadow: var(--shadow-raised);
  color: var(--text);
  /* The same weight as every other row, deliberately: bolder text is wider
     text, so the row you are on would be a different size from the row above
     it, and the whole list would shift by a hair as you moved between them.
     The pill says which row it is. */
}

.sidebar nav a.active .sidebar-icon {
  color: var(--accent);
}

/* The one thing on the page that scrolls.
   
   And the one thing that decides how wide anything is. Without a limit here a
   card on a 2560px monitor is 2264px of box holding 617px of writing, with
   most of a thousand pixels of nothing to the right of every sentence — which
   is what capping the prose alone leaves behind. The measure was doing its
   job; the box around it was not.
   
   The cap is padding rather than a max-width on the element, so the column
   this scrolls stays the full width and the scrollbar stays at the window's
   edge, where a scrollbar belongs. On a narrow window the max() falls back to
   the 32px gutter and nothing changes.
   
   1400px because that is comfortably more than anything here needs: the mail
   list is eight columns and 1019px at its natural width, and the DNS table is
   a fixed layout in percentages that fits whatever it is given. */
.content {
  flex: 1;
  min-height: 0;
  min-width: 0;
  overflow-y: auto;
  /* The scrollbar's room is kept whether or not the page needs one. A page
     long enough to scroll was ten pixels narrower than a short one, so
     switching between two tabs moved everything on them sideways. */
  scrollbar-gutter: stable;
  padding: var(--space-6) max(32px, (100% - 1400px) / 2);
}

/* The mail and knowledge pages never scroll the content (their frames hold the
   scrolling), so a strip kept for a scrollbar that is never drawn is only an
   empty band at the window's edge on a classic scrollbar. */
.content:has(> .mailbox-frame),
.content:has(> .knowledge-frame) {
  scrollbar-gutter: auto;
}

h2 {
  margin-top: 0;
  font-size: 20px;
}
h3 {
  font-size: 16px;
  margin-bottom: 8px;
}

table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--text-body);
}
th,
td {
  text-align: left;
  padding: 8px 10px;
  border-bottom: 1px solid var(--border);
  vertical-align: top;
}

/* Top is right for a cell that stacks several lines — the values found for a
   DNS record, the evidence behind an authentication check — and wrong for a
   row whose height is set by a control at the end of it. A 34px button in a
   row of one-line text makes the row taller than the text, and the text then
   sits at the top of that space rather than level with the button it is
   beside. Wherever a row holds one, its cells line up through the middle. */
tr:has(.icon-button) > td {
  vertical-align: middle;
}

/* A value and the button that copies it, on one line. The value takes the
   room and gives up its end to an ellipsis rather than wrapping to four
   lines; the button keeps its size. */
.value-row {
  display: flex;
  align-items: center;
  gap: 6px;
}

.value-clamp {
  flex: 1;
  min-width: 0;
  /* Clamped by height, not by refusing to break. A DKIM key is one
     unbreakable token, so "white-space: nowrap" gave the cell an intrinsic
     width of four hundred characters and an auto-layout table did as it was
     told: the column grew to fit and pushed the two columns after it off the
     page. Letting it break anywhere — which .mono already asks for — and
     showing one line of it keeps the column whatever width the table had in
     mind. */
  /* break-all, not overflow-wrap: anywhere. "anywhere" permits a break
     inside a word only where one is needed to stop it overflowing, and the
     clamp then ends the line at the last ordinary break opportunity before
     the giant token — which for a DKIM record is the space after "k=rsa;",
     fifteen characters into a column nine hundred pixels wide. break-all
     makes every character a break opportunity, so the line fills and the
     ellipsis lands at the edge. Verified in the browser rather than
     reasoned about: the first two attempts at this were both wrong. */
  word-break: break-all;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 1;
  line-clamp: 1;
  overflow: hidden;
}

/* Quieter than a trash icon, and smaller: it sits inside a line of text
   rather than at the end of a row. */
.icon-button.copy-button {
  width: 24px;
  height: 24px;
  flex: 0 0 auto;
}

/* --- the shared table -----------------------------------------------------
   An outlined surface holding the table, so the header, the rows and the
   empty state share one edge. Following the reference: small uppercase
   headers, alternating rows, a hairline between them, and the count and
   pagination on a bar underneath rather than inside the frame. */
.table-surface {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-3);
  overflow: auto;
}

.table-surface table {
  font-variant-numeric: tabular-nums;
}

/* A column given a width means it: a relative timestamp and a file size are
   short, and wrapping them onto two lines to save eight pixels makes every
   row taller for nothing. Only the columns that hold sentences may wrap, and
   those say so with .wrap. */
.table-surface th,
.table-surface td.shrink,
.table-surface time {
  white-space: nowrap;
}

.table-surface td:not(.wrap) {
  white-space: nowrap;
}

.table-surface th {
  padding: 12px 10px;
  font-size: 13px;
  font-weight: 550;
  letter-spacing: 0;
  color: var(--muted);
  white-space: nowrap;
}

.table-surface td {
  padding: 14px 10px;
  vertical-align: middle;
}

/* Alternating rows, lighter than the page rather than darker, so a row can be
   followed across a wide table. */
/* No zebra stripe. A stripe is a second way of saying what the rules under
   each row already say, and on a page this quiet it reads as two grays
   arguing. Rows are told apart by their height and their rule. */

.table-surface tbody tr:hover > td {
  background: var(--sidebar-hover);
}

/* A row that has somewhere to go is the target, not the one link inside it.
   The cursor is the only thing that says so: a hover tint the whole table
   already has cannot distinguish a row you can open from one you cannot. */
.table-surface tbody tr.linked {
  cursor: pointer;
}

/* The last row's border would stack against the surface's own edge and read
   as a two-pixel rule. */
.table-surface tbody tr:last-of-type > td {
  border-bottom: none;
}

.table-empty {
  margin: 0;
  padding: var(--space-6);
  text-align: center;
}

/* Count on the left, pagination on the right. */
.table-bar {
  display: flex;
  align-items: center;
  /* Wrapping at any width. The bar is as wide as the column it sits in, and
     that column is narrow on a phone but also on a desktop window with the
     navigation open beside it — a breakpoint on the window cannot see the
     second case. Wrap only happens when it does not fit, so allowing it
     everywhere costs a wide bar nothing. */
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-3);
  margin-top: var(--space-3);
  font-size: var(--text-label);
}

.table-pagination {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: var(--space-2);
  margin-left: auto;
}

/* None of these are sentences. "383 messages", "1-50 of 383" — each is one
   fact, and breaking one across lines to save a few pixels makes it harder to
   read than the crowding it was meant to relieve. They keep their line and
   the bar wraps instead. */
.table-bar > .muted,
.table-pagination > .muted {
  white-space: nowrap;
}

.table-pagination label,
.table-rows {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  margin: 0;
  color: var(--muted);
}

.table-rows .select {
  padding: 3px 6px;
  min-height: 28px;
  font-size: var(--text-label);
}

.table-pagination .icon-button {
  width: 28px;
  height: 28px;
  /* Kept square. As flex items in a crowded row they were squeezed to
     seventeen pixels wide on a phone, which is a target nobody can press. */
  flex: 0 0 auto;
}

.table-pagination .icon-button:disabled {
  opacity: 0.35;
  cursor: default;
}

.table-pagination .icon-button:disabled:hover {
  background: none;
  color: var(--muted);
}

/* The same chevron, pointing the other way. */
.flip {
  display: flex;
  transform: rotate(180deg);
}

/* A timestamp is exact on hover, which is worth signposting. */
time[title] {
  cursor: help;
}

/* A list is scanned down, not read across: one line per row, and anything
   too long for its column ends in an ellipsis rather than growing the row to
   five lines. max-width: 0 is the table-layout trick that makes
   text-overflow work in a cell — the percentage width on the column decides
   the real size. */
td.truncate {
  max-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  /* ...but not below the point where the column says nothing. The trick
     above gives the column whatever is left after the fixed ones, and a
     window between a phone and a full screen leaves almost nothing: the
     settings rail takes its share, four stated widths take theirs, and the
     column holding what each row is about came out 56 pixels wide, which
     fits two letters and an ellipsis. A floor costs nothing where there is
     room, since the column measures the same at full width with it as
     without, and the surface scrolls sideways when there is not, which is
     what a table does. */
  min-width: 14rem;
}
/* A table of rules, not a grid of boxes. The head is a quiet label in the
   same case as everything else — uppercase at this size is harder to read and
   buys nothing that weight and color do not already give. */
th {
  color: var(--muted);
  font-weight: 550;
  font-size: 13px;
  letter-spacing: 0;
}
/* A head's words in the middle of the head, not at its top: a sortable
   head is a button, forty pixels tall on a phone to be hit with a thumb,
   and its word sat half a line below the plain heads beside it. */
thead th {
  vertical-align: middle;
}
tbody tr:hover {
  background: var(--sidebar-hover);
}
td.shrink {
  width: 1%;
  white-space: nowrap;
}

/* A static panel: an outline and a background. No shadow — that is reserved
   for things that float, so a shadow always means the same thing. */
.card {
  /* The side padding, named so that a rule inside the card that should run
     from border to border can reach out by exactly this much. */
  --card-padding-inline: var(--space-4);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-3);
  padding: var(--space-4) var(--card-padding-inline);
  margin-bottom: var(--space-4);
}

/* The first heading in a panel sits at the top of it. A browser's default
   margin above an h3 pushes the whole panel's content down by a line and
   makes the padding look uneven. */
.card > h3:first-child {
  margin-top: 0;
}

/* The sentence under a panel's heading belongs to the heading, so it sits
   against it rather than a line below. Written once here because it was
   written thirty times as style={{ marginTop: 0 }}, which no media query can
   reach and no other page can share. */
.card > h3 + p,
.settings-section > h3 + p {
  margin-top: 0;
}

/* The last paragraph in a panel stops at the padding. Same reason. */
.card > p:last-child {
  margin-bottom: 0;
}

/* A field in a row of fields is laid out by the row's gap, not by its own
   margin: a label that keeps its block margin pushes its neighbours out of
   line with it. */
.row > label {
  margin: 0;
}

/* Prose stops at a readable measure. These pages carry real explanation, and
   on a wide screen a paragraph that fills the panel runs past 200 characters
   a line — far enough that the eye loses which line comes next. Controls and
   tables are unaffected; only the text is capped. */
/* Any explanatory paragraph, however deeply it sits. This was three child
   combinators, which meant the cap held only for a paragraph that happened to
   be a direct child: the steps under "Getting mail flowing" are a paragraph
   inside a div inside a row inside the card, so they escaped it and ran the
   full 2161px of a wide window while every passage around them stopped at 617.
   A rule that depends on how deeply somebody nested a div is a rule that will
   keep being wrong.
   
   :where() so the whole selector counts as nothing: the exceptions below are
   the same weight and win by being written after, rather than by an escalation
   nobody can follow later. */
:where(.card, .content, .settings-section) p.muted {
  max-width: var(--measure);
}

/* Except the ones that are paragraphs by tag and panels by drawing: an empty
   state standing in for a row, a notice with a background behind it. The
   measure that keeps a sentence readable makes those a narrow box beside
   full-width rows, which reads as a mistake rather than as a margin. A
   centered one, the coding page's empty states, would be centered in the
   narrow box and stand left of the pane's middle. Same specificity as the cap
   above and written after it, so it wins. */
p.settings-empty,
p.notice,
p.table-empty,
p.coding-empty {
  max-width: none;
}

/* A paragraph that is the whole of a table cell is bounded by its column
   already, and a measure inside one only makes the column look mis-set. */
td p.muted {
  max-width: none;
}

/* Five columns of very different appetite: a two-letter type, a name of about
   thirty characters, a value that may be four hundred, and two short
   verdicts. Left to size themselves the value asks for everything it can get
   — a signing key is one unbreakable token — and the name is squeezed until
   <selector>._domainkey.<domain> wraps mid-word, which is the one thing in
   the row a reader scans down the column for.
   
   So the proportions are stated and the layout fixed, which also gives the
   value a width to be clamped against rather than one worked out from its own
   content. Percentages, so it still fits a narrow window; the value column is
   whatever is left, which is about half. A min-width on the name used to do
   half of this job and cannot do it here: a fixed layout takes the stated
   width and ignores the rest. */
.dns-table {
  table-layout: fixed;
}

.dns-table th:nth-child(1),
.dns-table td:nth-child(1) {
  width: 7%;
}

.dns-table th:nth-child(2),
.dns-table td:nth-child(2) {
  width: 26%;
}

.dns-table th:nth-child(4),
.dns-table td:nth-child(4) {
  width: 13%;
}

.dns-table th:nth-child(5),
.dns-table td:nth-child(5) {
  width: 8%;
}

/* A sentence under a value in a table is prose, and the rule above misses it:
   it caps the direct children of a card, and this sits inside a cell. The
   column is as wide as the table, so an explanation left uncapped runs to two
   hundred characters a line — the case that made this necessary is the advice
   under a failing check, which is the most important text on the page and was
   the hardest to read. The table itself is unaffected. */
/* What is stopping a record from doing anything, as distinct from what the
   record is for: the colour of a caution rather than of an aside, because it
   is the sentence that saves the week. */
td .cell-note.warn {
  color: var(--warn);
}

td .cell-note {
  display: block;
  /* A sentence breaks at its spaces. It sits inside a cell that may allow
     breaking anywhere for the value above it, and inheriting that turns
     "no mail arrives" into "no mai / l arrives". */
  overflow-wrap: normal;
  word-break: normal;
  /* Asked for rather than capped. A ceiling alone makes it worse: automatic
     table layout sizes a column from the widest thing it must fit, so a
     capped block reports a narrow preference and the column collapses to
     something narrower still. Naming the width makes it the preference, and
     the ceiling keeps it inside a narrow window. */
  width: var(--measure);
  max-width: 100%;
}

/* A chip is the size of the word in it. Neutral by default — most of them
   say what something is, not that anything is wrong — and tinted only for the
   three that carry state. The tint is the color at low opacity rather than a
   second palette, so it follows the theme without a dark variant of its own. */
.tag {
  display: inline-block;
  padding: 2px 8px;
  border-radius: var(--radius-1);
  font-size: 12px;
  font-weight: 550;
  border: 1px solid var(--border);
  color: var(--muted);
  white-space: nowrap;
}
.tag.good {
  color: var(--good);
  border-color: color-mix(in srgb, var(--good) 35%, transparent);
  background: color-mix(in srgb, var(--good) 10%, transparent);
}
.tag.bad {
  color: var(--bad);
  border-color: color-mix(in srgb, var(--bad) 35%, transparent);
  background: color-mix(in srgb, var(--bad) 10%, transparent);
}
.tag.warn {
  color: var(--warn);
  border-color: color-mix(in srgb, var(--warn) 35%, transparent);
  background: color-mix(in srgb, var(--warn) 10%, transparent);
}

/* --- what happened to a message -------------------------------------------
   The first line of a mail: its verdict, its kind, and one sentence saying
   what became of it. The page used to open with the subject and the sender,
   which are the two things the reader already knew — they clicked the row to
   get here. Whether it was accepted was nowhere on the page. */
.verdict {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-bottom: var(--space-4);
}

/* A label-and-value table inside a panel: the label column takes what it
   needs and the value takes the rest. */
.detail td:first-child {
  vertical-align: top;
  white-space: nowrap;
}

/* Each authentication check on its own line: mechanism, verdict, and what was
   examined to reach it. The third column is the one that was missing — a row
   of bare tags says nothing went wrong and never says why something did. */
.checks td {
  vertical-align: top;
  padding-top: 6px;
  padding-bottom: 6px;
}

.checks td:first-child {
  font-weight: 550;
}

/* One block per recipient rather than one row. A failed delivery carries a
   remote server, an enhanced status code and a diagnostic line from the far
   end, and none of the three fit in a column. */
.delivery + .delivery {
  margin-top: var(--space-4);
  padding-top: var(--space-4);
  border-top: 1px solid var(--border);
}

.delivery-head {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-bottom: var(--space-2);
}

.delivery-recipient {
  font-weight: 600;
}

/* Three weights of button, and the difference between them is how much they
   ask for. The default is outlined and quiet; .primary is the near-black one
   and there is at most one per view; .link is text that happens to act. */
button,
.button {
  font: inherit;
  font-weight: 550;
  padding: 7px 14px;
  border-radius: var(--radius-2);
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text);
  cursor: pointer;
}

button:hover:not(:disabled) {
  background: var(--sidebar-hover);
}

button.primary {
  background: var(--accent);
  color: var(--accent-text);
  border-color: var(--accent);
}

button.primary:hover:not(:disabled) {
  background: var(--accent-hover);
  border-color: var(--accent-hover);
}

button:disabled {
  opacity: 0.5;
  cursor: default;
}

/* The one confirming button that takes something away. Red is reserved for
   that, so that a red button always means the same thing — signing out takes
   nothing away and is not one of these. */
button.primary.danger {
  background: var(--bad);
  border-color: var(--bad);
  color: #fff;
}

button.primary.danger:hover:not(:disabled) {
  background: color-mix(in srgb, var(--bad) 85%, black);
  border-color: color-mix(in srgb, var(--bad) 85%, black);
}

/* A word in a sentence that does something: "Remote content blocked. Show
   images." It is drawn as an anchor because in a line of prose that is what
   it reads as, and a box around it would break the line.
   
   A control standing on its own is not this. It is `.button-quiet` below,
   because underlined text with no padding gave a control that acts on the
   page the same paint as one that leaves it, and no target worth tapping. */
button.link {
  border: none;
  background: none;
  color: var(--muted);
  padding: 0;
  font-weight: inherit;
  text-decoration: underline;
  /* A button centers its text, which only shows once it wraps: a long
     name in a list then sat centered under the short ones beside it. */
  text-align: start;
}

button.link:hover:not(:disabled) {
  background: none;
  color: var(--text);
}

/* A control that stands on its own, quiet: not the thing to do first, so
   muted and unbordered until it is pointed at, but padded and shaped like
   the button it is. */
.button-quiet {
  /* A border at rest, not only under the pointer. Quiet is muted colour and
     a light edge; invisible is not quiet, it is a control nobody can find,
     and on a touch screen there is no hover to discover it with. */
  border: 1px solid var(--border);
  background: none;
  color: var(--muted);
  padding: 2px 8px;
  border-radius: var(--radius-2);
  font: inherit;
  font-weight: inherit;
  text-decoration: none;
  cursor: pointer;
}

/* Beside a label's words the quiet anchor is a box in the line, not text. */
label > span > .button-quiet {
  display: inline-block;
  vertical-align: baseline;
}

.button-quiet:hover:not(:disabled) {
  border-color: var(--border);
  background: var(--sidebar-hover);
  color: var(--text);
}

input,
select,
textarea {
  font: inherit;
  padding: 7px 10px;
  border: 1px solid var(--border);
  border-radius: var(--radius-2);
  background: var(--surface);
  color: var(--text);
  width: 100%;
}

/* A text box is as wide as its row, and dragged wider it spills out of the
   form it sits in; only its height is the reader's to change. */
textarea {
  resize: vertical;
}

input:hover:not(:disabled),
select:hover:not(:disabled),
textarea:hover:not(:disabled) {
  border-color: var(--border-strong);
}

input:focus-visible,
select:focus-visible,
textarea:focus-visible {
  outline: none;
  border-color: var(--text);
}

/* A checkbox is not a text field. The full-width rule above is meant for the
   things you type into; applied to a tick box it produces a control the width
   of its row that shoves its own label off the end.

   The accent is here too, because a tick box is the one control that ignores
   the palette and draws itself in the browser's blue. */
input[type='checkbox'],
input[type='radio'] {
  width: auto;
  padding: 0;
  accent-color: var(--accent);
  vertical-align: -0.15em;
  margin-right: var(--space-1);
}

/* A clock or a calendar is a text field too.

   Left to itself iOS draws these natively, and a native date or time control
   sizes itself: it takes the width its own clock face wants and declines to
   shrink to the column it is in, so on a phone the field sat wider than the
   one under it and pushed the whole page sideways. Nothing on a desktop shows
   this — Chrome shrinks them without complaint — and the page-level
   overflow-x: hidden hides the evidence rather than the cause, which is why
   it survived so long.

   Turning the native appearance off puts them back under the same rules as
   every other field: the width they are given, the padding and border above,
   and their text against the left edge rather than centred in the middle of
   the box. Tapping one still opens the system picker.

   A month field is the same native control and sizes itself the same way,
   so it is on the list too. */
input[type='time'],
input[type='date'],
input[type='month'],
input[type='datetime-local'] {
  appearance: none;
  -webkit-appearance: none;
  box-sizing: border-box;
  min-width: 0;
  max-width: 100%;
  text-align: start;
  /* As tall as a text field beside it: one line, the padding and the
     border. Chrome pads the date's own editor a pixel above and below, so
     a date sat two pixels taller than the search box next to it in a row
     of filters. */
  height: calc(1lh + 16px);
}

label {
  display: block;
  margin-bottom: 10px;
  font-size: 14px;
}

/* The name of a control that is not a single field — a switch and the editor
   under it — said the way a field's label is said, since it is doing the same
   job for the reader. */
.field-label {
  margin-bottom: 4px;
  font-size: 14px;
}

/* A label whose control is a tick box is a row, not a block: the box and its
   text line up on one baseline and stay lined up when the text wraps onto a
   second line, which on a phone it does. */
label:has(> input[type='checkbox']),
label:has(> input[type='radio']) {
  display: flex;
  align-items: center;
  gap: var(--space-1);
}

label:has(> input[type='checkbox']) > input,
label:has(> input[type='radio']) > input {
  flex: 0 0 auto;
  margin-right: 0;
  vertical-align: baseline;
}
label span {
  display: block;
  color: var(--muted);
  font-size: 13px;
  margin-bottom: 3px;
}

.row {
  display: flex;
  gap: 12px;
  align-items: flex-end;
  flex-wrap: wrap;
}
.row > * {
  flex: 1;
  min-width: 140px;
}
/* Two rows of fields stacked are a form, and want the form's gap between
   them. Inside .form-narrow its own gap does this; a card that lays rows out
   directly -- the agent's Limits, among others -- had them flush, so four
   rows of numbers read as one dense block. */
.row + .row {
  margin-top: var(--space-4);
}

.row > .shrink {
  flex: 0 0 auto;
  min-width: 0;
}

.muted {
  color: var(--muted);
}
.error {
  color: var(--bad);
}
.success {
  color: var(--good);
}
/* Monospace, and allowed to break inside a word — but only when the word
   cannot fit on a line of its own. That is the difference between
   overflow-wrap:anywhere and word-break:break-all, and it matters here: a
   four-hundred character signing key has to break somewhere, while a record
   name of twenty-seven characters was being shredded into five lines beside
   it because break-all breaks at the fill point whether it needs to or not. */
.mono {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 13px;
  overflow-wrap: anywhere;
}

/* --- signing in ----------------------------------------------------------
   A card in the middle of an empty page. There is no shell yet — no rail, no
   breadcrumb — because there is nothing to navigate to until you are in. */
.auth-page {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 100vh;
  min-height: 100dvh;
  /* Room at the top for the language and appearance controls, which are
     fixed in the corner with no shell to hold them: in a window as narrow
     as a browser extension's they sat on the card. */
  padding: max(56px, var(--space-6)) var(--space-6) var(--space-6);
  /* The rail's gray rather than the page's white, so the card is a card. On
     white it was a white rectangle held together by a hairline, and the eye
     had nothing to land on. */
  background: var(--rail);
}

.auth-card {
  width: 100%;
  max-width: 400px;
  padding: 40px 36px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-3);
}

/* What you have reached, before it asks you for anything. This page has no
   navigation, no heading and nothing else on it, so the mark and the name are
   the only things that say where you are — centered and given room, rather
   than tucked into the corner above the first field. */
.auth-header {
  text-align: center;
  margin-bottom: var(--space-6);
}

.auth-header h1 {
  margin: var(--space-3) 0 0;
  font-size: 22px;
  font-weight: 650;
  letter-spacing: -0.01em;
  line-height: 1.2;
}

/* One line saying what to do here. Close under the name, because the two are
   one block: the name is what this is and this is what it wants. */
.auth-header p {
  margin: 6px 0 0;
  font-size: var(--text-body);
  line-height: 1.5;
  text-wrap: balance;
}

.auth-field {
  display: block;
  margin-bottom: var(--space-4);
}

.auth-field > span {
  display: block;
  margin-bottom: 6px;
  font-size: var(--text-label);
  font-weight: 550;
  color: var(--text);
}

.auth-field input,
.auth-field select {
  width: 100%;
  min-height: 40px;
}

/* A row of secondary actions under the card's content, for the page that
   hands over a command to copy rather than asking for anything. */
.auth-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: var(--space-2);
  margin-top: var(--space-3);
}

/* Under the field it belongs to, in the gap the field already leaves. */
.auth-hint {
  display: block;
  margin-top: 6px;
  font-size: var(--text-label);
}

/* A hint is a span inside the field's label, so the label's own rule for its
   span reaches it too; a hint is read after the field, not as its name. */
.auth-field > span.auth-hint {
  margin-bottom: 0;
  font-weight: 400;
}

.auth-field > span.auth-hint.muted {
  color: var(--muted);
}

.auth-button {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  width: 100%;
  min-height: 40px;
}

/* The primary sits directly under the last field: they are one column, and a
   gap here would read as the form having ended. */
.auth-card > button.primary {
  margin-top: var(--space-2);
}

/* The welcome page's choice of model service: four buttons in two
   columns, the chosen one drawn in the text color like a pressed segment. */
.welcome-services {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-2);
}

.welcome-services button {
  min-height: 40px;
  justify-content: center;
}

.welcome-services button[aria-pressed='true'] {
  border-color: var(--text);
  box-shadow: inset 0 0 0 1px var(--text);
  color: var(--text);
}

/* What the welcome page is doing, one line per step, ticked as each ends. */
.welcome-progress {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.welcome-progress li {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  color: var(--muted);
}

.welcome-progress li.done,
.welcome-progress li.current {
  color: var(--text);
}

.welcome-progress-mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 22px;
  height: 22px;
  margin-top: 1px;
  border-radius: 50%;
  border: 1px solid var(--border-strong);
}

.welcome-progress li.done .welcome-progress-mark {
  border-color: transparent;
  background: var(--text);
  color: var(--surface);
}

.welcome-progress-note {
  display: block;
  font-size: var(--text-label);
}

.welcome-spinner {
  width: 12px;
  height: 12px;
  border-radius: 50%;
  border: 2px solid var(--border-strong);
  border-top-color: var(--text);
  animation: welcome-spin 0.8s linear infinite;
}

@keyframes welcome-spin {
  to {
    transform: rotate(360deg);
  }
}

@media (prefers-reduced-motion: reduce) {
  .welcome-spinner {
    animation: none;
  }
}

/* A rule with a word in it. Two ways to sign in need something between them
   saying they are alternatives rather than steps. */
.auth-or {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin: var(--space-4) 0;
  color: var(--muted);
  font-size: var(--text-label);
}

.auth-or::before,
.auth-or::after {
  content: '';
  flex: 1;
  height: 1px;
  background: var(--border);
}

/* The footnote: how to get out of trouble. Above a rule, because it is not
   part of the form — it is what to read when the form has not worked. */
.auth-footnote {
  margin: var(--space-5) 0 0;
  padding-top: var(--space-4);
  border-top: 1px solid var(--border);
  font-size: var(--text-label);
  line-height: 1.55;
  text-align: center;
  text-wrap: balance;
}

/* On a line of its own, and selectable as one thing. Set through the middle
   of a centered sentence it wrapped in the middle of the command. */
.auth-command {
  display: block;
  margin-top: var(--space-2);
  padding: 8px 10px;
  background: var(--field);
  border-radius: var(--radius-2);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 12px;
  color: var(--text);
  overflow-wrap: anywhere;
}

.banner {
  padding: 10px 14px;
  border-radius: 5px;
  border: 1px solid var(--warn);
  color: var(--warn);
  margin-bottom: 16px;
  font-size: 14px;
}

/* --- a suggestion above the page ------------------------------------------
   One row: an icon, a sentence, and the two answers to it. A card rather than
   a colored band, and the icon in the accent rather than a warning color,
   because nothing is wrong: it is an offer, and it should look like one. On
   a phone the answers drop under the sentence rather than squeezing it. */
.nudge {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-3);
  margin-bottom: var(--space-4);
  padding: var(--space-3) var(--space-4);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-3);
}

.nudge-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: 34px;
  height: 34px;
  border-radius: var(--radius-1);
  background: var(--rail);
  color: var(--accent);
}

/* The sentence takes the room; the answers do not. */
.nudge-text {
  display: flex;
  flex-direction: column;
  flex: 1 1 280px;
  min-width: 0;
  gap: 2px;
  font-size: var(--text-body);
}

.nudge-text .muted {
  font-size: var(--text-label);
  line-height: 1.45;
}

.nudge-actions {
  display: flex;
  align-items: center;
  flex: 0 0 auto;
  gap: var(--space-3);
}

/* Scrolls sideways rather than wrapping, because tabs are a row: wrapped onto
   a second line they stop reading as one set of alternatives, and the line
   they wrap onto moves as the labels change with the language. Five tabs are
   wider than a phone, so the row that does not fit is scrolled and the tab
   half in view says so. */
.tabs {
  display: flex;
  align-items: center;
  gap: 4px;
  border-bottom: 1px solid var(--border);
  margin-bottom: 16px;
  overflow-x: auto;
  /* No scrollbar under the tabs: on a phone it is an overlay that appears
     while scrolling, and on a desktop the row fits and a permanent gutter
     under it would be a line that means nothing. */
  scrollbar-width: none;
}

.tabs::-webkit-scrollbar {
  display: none;
}

/* A row with more tabs past an edge fades out at that edge, so a row cut
   between two tabs still says it goes on.

   The fade covers the labels only. The strip's last three pixels (the rule
   under the row and the active tab's underline) are a second mask layer
   that is always opaque, so the underline of the tab you are on stays a
   line instead of dissolving into a smudge at the edge. */
.tabs.tabs-more-after,
.tabs.tabs-more-before {
  mask-image: var(--tabs-fade), linear-gradient(#000, #000);
  mask-size:
    100% 100%,
    100% 3px;
  mask-position:
    0 0,
    0 100%;
  mask-repeat: no-repeat;
}

.tabs.tabs-more-after {
  --tabs-fade: linear-gradient(to right, #000 calc(100% - 56px), transparent);
}

.tabs.tabs-more-before {
  --tabs-fade: linear-gradient(to left, #000 calc(100% - 56px), transparent);
}

.tabs.tabs-more-before.tabs-more-after {
  --tabs-fade: linear-gradient(to right, transparent, #000 56px, #000 calc(100% - 56px), transparent);
}

/* A tab keeps its width in a row that scrolls; the default would squeeze them
   all until every label was unreadable. */
.tabs button,
.tabs .tab-action,
.tabs .tab-actions {
  flex: 0 0 auto;
}

.tabs button {
  border: none;
  border-bottom: 2px solid transparent;
  border-radius: 0;
  background: none;
  color: var(--muted);
  font-weight: 550;
  padding: 8px 2px;
  margin-right: 22px;
}
.tabs button:hover:not(:disabled) {
  background: none;
  color: var(--text);
}

/* An action sitting with the tabs rather than being one: it does not change
   what is below, it takes a copy away with you — or, on a domain, it leaves
   for a different page with a filter applied. Direct children only: two of
   them inside .tab-actions would each take half the free space and sit at
   opposite ends of the row. */
.tabs > .tab-action {
  margin-left: auto;
}

.tabs .tab-actions {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: 4px;
}

.tabs .tab-action {
  align-self: center;
}

/* The tabs' own button rules take the box off a button; an action beside the
   tabs is an ordinary button and keeps it. */
.tabs button.tab-action {
  border: 1px solid var(--border);
  border-radius: var(--radius-2);
  background: var(--surface);
  color: var(--text);
  padding: 7px 14px;
  margin-right: 0;
}

.tabs button.tab-action:hover:not(:disabled) {
  background: var(--sidebar-hover);
  color: var(--text);
}

@media (max-width: 600px) {
  /* Closer together, so more of the row is in view before it has to be
     scrolled. */
  .tabs button {
    margin-right: 12px;
  }

  /* The download is not offered here. It saves an .eml, which on a phone is a
     file most things refuse to open, and in a scrolling row it sat past the
     end where nobody would find it anyway. The message itself is on the page,
     and the same link is a tap away on anything larger. */
  .tabs .tab-action {
    display: none;
  }
}
/* The indicator is the text color rather than the accent. Switching a
   section of the page is not an action to be pressed, and coloring it spends
   the one emphasis the page has on something you are already looking at. */
/* Color and the rule under it, and not a heavier weight: bolder text is
   wider text, so switching tabs moved every tab to the right of the one
   chosen. */
.tabs button.active {
  border-bottom-color: var(--text);
  color: var(--text);
}

/* The message body is rendered inside a sandboxed frame. It is mail written
   by a stranger, so it gets no scripts, no same-origin access, and a white
   background regardless of the dashboard theme, because messages assume one. */
/* The height is set from the measured content, so this never scrolls on its
   own — the page does. The white is deliberate and not themed: mail is
   written assuming a white page, and forcing dark on a stranger's markup
   produces invisible text. */
.message-frame {
  display: block;
  width: 100%;
  border: 1px solid var(--border);
  border-radius: var(--radius-1);
  /* The surface color, not white: the document inside paints its own
     ground, but this shows at the rounded corners, before the document has
     loaded, and wherever a message leaves its body transparent — and in the
     dark theme a white element behind a dark document was a thin bright
     halo around every message. */
  background: var(--surface);
}

/* --- column filters -------------------------------------------------------
   A second header row: the name of the column above, the question you can ask
   of it below. Filters belong on the column they filter, not in a bar above
   the table where you have to work out which one they apply to. */
.filter-row td {
  padding: 0 6px 10px;
  border-bottom: 1px solid var(--border);
  vertical-align: middle;
  background: none;
}

.column-filter {
  width: 100%;
  min-width: 0;
  padding: 4px 8px;
  font-size: var(--text-label);
  min-height: 28px;
}

/* The trigger for a multiselect: reads as the current answer, not as a
   control shouting for attention. */
.column-filter-button {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-1);
  width: 100%;
  padding: 5px 8px;
  border: 1px solid var(--border);
  border-radius: var(--radius-1);
  background: var(--background);
  color: var(--muted);
  font: inherit;
  font-size: var(--text-label);
  line-height: 1.2;
  min-height: 28px;
  cursor: pointer;
  text-align: left;
}

.column-filter-button:hover {
  color: var(--text);
  border-color: var(--muted);
}

/* A filter that is doing something says so, so a list that looks short is
   never a mystery. */
.column-filter-button.active {
  color: var(--accent);
  border-color: var(--accent);
}

.column-filter-summary {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The chevron points down when it opens a menu below it. */
.column-filter-button svg {
  flex: 0 0 auto;
  transform: rotate(90deg);
}

.menu-search {
  width: 100%;
  margin-bottom: 4px;
  padding: 6px 8px;
  font-size: var(--text-label);
  min-height: 30px;
}

/* A row of the menu like its options, in the menu's own text color: accent
   text with no box reads as a link. */
.menu-clear {
  width: 100%;
  margin-bottom: 4px;
  padding: 4px 8px;
  border: none;
  border-radius: var(--radius-1);
  background: none;
  color: var(--text);
  font: inherit;
  font-size: var(--text-label);
  text-align: left;
  cursor: pointer;
}

.menu-clear:hover {
  background: var(--sidebar-hover);
}

/* Bounded, because a server with fifty domains should scroll the menu rather
   than the page. */
.menu-options {
  max-height: 260px;
  overflow-y: auto;
}

.menu-option {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: 6px 8px;
  border-radius: var(--radius-1);
  font-size: var(--text-body);
  cursor: pointer;
}

.menu-option:hover {
  background: var(--sidebar-hover);
}

.menu-option input {
  flex: 0 0 auto;
  min-height: 0;
  margin: 0;
  accent-color: var(--accent);
}

/* The generic "label span" is a field caption above its input: block, muted,
   with a gap under it. Inside a menu the span is the option itself, sitting
   beside its tick box. */
.menu-option span {
  display: inline;
  flex: 1;
  min-width: 0;
  margin: 0;
  color: var(--text);
  font-size: var(--text-body);
  text-align: left;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* How many rows an option would leave, sitting at the end of its row. */
.menu-count {
  flex: 0 0 auto;
  margin-left: auto;
  padding-left: var(--space-2);
  font-size: var(--text-label);
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

/* A sortable header is a button, and has to keep the header's own type. */
.sort-header {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 0;
  border: none;
  background: none;
  color: inherit;
  font: inherit;
  letter-spacing: inherit;
  text-transform: inherit;
  cursor: pointer;
}

.sort-header:hover {
  color: var(--text);
}

/* The chevrons are a hint until the column is the one in force. */
.sort-header svg {
  opacity: 0.4;
}

.sort-header:hover svg,
.sort-header.active svg {
  opacity: 1;
}

.sort-header.active {
  color: var(--accent);
}

.menu-empty {
  padding: 8px;
  font-size: var(--text-label);
  color: var(--muted);
}

pre.message-text {
  white-space: pre-wrap;
  word-break: break-word;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 5px;
  padding: 14px;
  font-size: 13px;
  margin: 0;
}

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

   A mail server gets checked from a phone more often than from a desk: it is
   the thing you look at when somebody says "did you get my email?". The
   sidebar becomes a row of tabs across the top, forms stop trying to sit side
   by side, and every table can be scrolled sideways rather than forcing the
   page to.
*/

/* Where the toggle sits when there is no sidebar to put it in: the login and
   first-run pages, which are a centered card on an empty page. */
.page-corner {
  display: flex;
  align-items: center;
  gap: 6px;
  position: fixed;
  top: max(12px, env(safe-area-inset-top));
  right: max(12px, env(safe-area-inset-right));
  z-index: 20;
}

@media (max-width: 760px) {
  /* The rail is an overlay here, not a column: it sits above the page, slides
     in from the left, and is dismissed by tapping what is behind it. On a
     phone there is no room to give a permanent rail, and a row of tabs cannot
     hold a rail that grows. */
  .sidebar,
  .sidebar.collapsed {
    position: fixed;
    top: 0;
    bottom: 0;
    left: 0;
    z-index: 40;
    width: 264px;
    flex-basis: 264px;
    transform: translateX(-100%);
    transition: transform 160ms ease;
  }

  /* The shadow belongs to the open overlay only: a closed rail sits just past
     the left edge, and its blur would still fall across the page. */
  .sidebar.open {
    transform: none;
    box-shadow: var(--shadow-floating);
  }

  /* Collapsing is a desktop idea; the overlay is always full width. */
  .sidebar.collapsed .sidebar-label,
  .sidebar.collapsed .sidebar-brand {
    display: flex;
  }
  .sidebar.collapsed nav a {
    justify-content: flex-start;
    padding: 8px 10px;
  }

  .scrim {
    position: fixed;
    inset: 0;
    z-index: 35;
    background: rgb(0 0 0 / 45%);
  }

  .topbar {
    gap: var(--space-1);
    padding-left: max(8px, env(safe-area-inset-left));
    padding-right: max(8px, env(safe-area-inset-right));
  }

  /* The bottom keeps 84 pixels free, as the toasts do, so the last row's
     buttons can scroll clear of the agent's button instead of under it. */
  .content {
    padding: var(--space-4) max(14px, env(safe-area-inset-right)) calc(84px + env(safe-area-inset-bottom))
      max(14px, env(safe-area-inset-left));
  }

  .surface-grid {
    grid-template-columns: 1fr;
  }

  /* Tables carry more columns than a phone has room for. Scrolling the table
     itself keeps the rest of the page still. */
  table {
    display: block;
    overflow-x: auto;
    white-space: nowrap;
  }
  /* A table in a .table-wrap already has a wrapper that scrolls, so it can
     stay a table: as a block it shrank to its content, and a table of two
     narrow columns sat in the left half of its card. */
  .table-wrap > table {
    display: table;
    width: 100%;
    overflow-x: visible;
  }
  /* Subject lines and error messages are the exception: they are the content,
     and there is no pointer here to hover over a truncated one, so they wrap
     instead. */
  /* Written with the surface in front of them, because the rule that
     keeps every other cell on one line is `.table-surface td:not(.wrap)`
     and that is the more specific of the two. A media query adds no
     specificity of its own, so the plain `td.truncate` here lost and the
     lines went on not wrapping: the table grew to four times the width of
     a phone and every one of them had to be dragged into view. */
  .table-surface td.wrap,
  .table-surface td.truncate,
  td.wrap,
  td.truncate {
    max-width: none;
    white-space: normal;
    overflow: visible;
    min-width: 200px;
  }

  /* Columns that are context rather than answer. */
  .optional {
    display: none;
  }

  /* ...and the table has to be allowed to be as wide as it needs, or there
     is nothing to scroll to. `table { width: 100% }` pinned it to the
     screen's width while its cells, which do not wrap, went on needing more:
     the row overflowed the table, the surface saw a table exactly its own
     size, and the columns past the edge were simply unreachable. A contact
     with no email address showed its name, a gap, and nothing else.
     min-width: max-content lets the table grow to its content; width: 100%
     still stretches it when there is room to spare.

     Not for a table with a column that wraps, though. max-content is measured
     as if nothing wrapped, so a table holding subject lines would set its
     width from the longest one and scroll for a screen and a half -- the
     opposite of what the rule right above this does for those columns. Such a
     table squeezes as it did before; it has a column that can give. */
  .table-surface:not(:has(td.wrap, td.truncate)) table {
    min-width: max-content;
  }

  /* A table keeps its shape here and scrolls sideways, which is what a table
     does when it is wider than the screen. It runs to the edges to make that
     scroll as long as it can be: the page's padding was holding a table that
     had none to spare 24 pixels away from the glass. */
  .table-surface {
    margin-left: calc(var(--space-3) * -1);
    margin-right: calc(var(--space-3) * -1);
    border-left: none;
    border-right: none;
    border-radius: 0;
  }

  /* A filter per column needs a column header to sit under, and the table is
     already scrolling sideways here. The filters that matter most stay, in a
     stack above the table. */
  .filter-row td {
    padding: 0 4px 6px;
  }

  .row {
    flex-direction: column;
    align-items: stretch;
  }
  .row > *,
  .row > .shrink {
    width: 100%;
  }

  /* Comfortable to hit with a thumb. */
  button,
  .button,
  input,
  select {
    min-height: 40px;
  }

  /* The rows of a menu are one height: its buttons took the 40 above and its
     links, the ones that go to a page, stayed at the 37 their padding makes. */
  .menu a {
    min-height: 40px;
  }

  /* A height was not enough for the ones that are only a picture. Nothing
     gave them a width, so they stayed as wide as the icon inside them: the
     cross that dismisses a toast was 22 pixels across in a 40 pixel row, and
     the star on a conversation 28. A target has two dimensions.
     
     The icon stays the size it was — this widens the box around it and
     centres it there, rather than drawing anything bigger. Not .icon-button:
     the rule below keeps those at the size they are on the bar, deliberately,
     and this is not the place to overturn that. */
  .toast-close,
  .mailbox-star {
    display: flex;
    align-items: center;
    justify-content: center;
    min-width: 40px;
    min-height: 40px;
  }

  /* iOS zooms the whole page in when a field smaller than 16px takes focus,
     and does not zoom back out. Sizing the fields at 16px is the only way to
     prevent it without disabling zoom altogether, which would be worse. */
  input,
  select,
  textarea {
    font-size: 16px;
  }

  /* Neither of these is a form field, so the rule above does not apply and
     they keep the size they have on the bar. */
  .icon-button,
  .avatar {
    min-height: 0;
  }

  /* A DNS record value is a long unbroken string. Let it wrap instead of
     forcing the page sideways, and keep it selectable for copying. */
  code,
  .record-value {
    word-break: break-word;
    overflow-wrap: anywhere;
  }

  .auth-page {
    align-items: flex-start;
    /* The top keeps the room the wide rule gives the language and appearance
       controls. This rule used to put the top back to --space-6, which let
       the card start level with the controls, so on every phone they sat on
       its top edge: the login form, /cli and approving a program alike. */
    padding: max(56px, calc(env(safe-area-inset-top) + 48px)) max(16px, env(safe-area-inset-right))
      var(--space-6) max(16px, env(safe-area-inset-left));
  }

  /* Scrolled away with the page rather than fixed over it. A card taller
     than a phone's screen scrolled underneath fixed controls, which then sat
     on whatever field or button was passing. */
  .page-corner {
    position: absolute;
  }

  /* The wide padding left a column of under three hundred pixels on a phone,
     and every sentence on the card wrapped more than it needed to. */
  .auth-card {
    padding: 32px 24px;
  }

  .login {
    padding: var(--space-6);
  }
}

/* Nothing should ever make the page itself scroll sideways. */
html,
body {
  overflow-x: hidden;
}

/* The shell owns the height, so the document must not add its own scrollbar
   on top of the content area's. */
html,
body,
#teanode {
  height: 100%;
}

/* A short list of facts about one thing: the label on the left, the value on
   the right, aligned down the column. Used where a table would be too much
   furniture for four rows. */
.properties {
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: var(--space-2) var(--space-4);
  margin: 0;
  align-items: baseline;
}

.properties dt {
  color: var(--muted);
  font-size: 13px;
}

.properties dd {
  margin: 0;
}

/* One line the reader is meant to stop at, rather than the running commentary
   that .muted carries. Neutral by default; .good and .bad color the edge for
   something that went right or something to be careful about. */
.notice {
  border-left: 3px solid var(--border);
  padding: var(--space-2) var(--space-3);
  margin: var(--space-3) 0;
  background: var(--surface-sunken, transparent);
  border-radius: 0 var(--radius-1) var(--radius-1) 0;
}

.notice.good {
  border-left-color: var(--good);
}

.notice.bad {
  border-left-color: var(--bad);
}

@media (max-width: 600px) {
  .properties {
    grid-template-columns: 1fr;
    gap: var(--space-1);
  }

  .properties dd {
    margin-bottom: var(--space-2);
  }
}

/* --- the rail, structured ---------------------------------------------------
   Groups under their own labels, a foot that stays at the bottom however
   short the list, and the account below everything. */

/* The rail is a column of three parts: the mark, the navigation, the foot.
   The navigation is the one that can outgrow the window — a mailbox with
   many folders on a short screen — so it is the one that scrolls, and the
   foot stays where it is rather than riding up over the rows. */
.sidebar nav {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  /* Room under the last row. Without it the final item — mailbox settings,
     at the foot of a long list — sat against the edge of the scrolling area
     and touched the line under it. */
  padding-bottom: var(--space-3);
}

/* The label over a group. Sentence case and quiet: it separates, it is not
   read. */
.sidebar-group-label {
  padding: 10px 18px 4px;
  font-size: var(--text-overline);
  font-weight: 600;
  letter-spacing: 0.01em;
  color: var(--muted);
}

/* Collapsed to icons there is no room to read one, and a label with no room
   is a gray smear above the icons. */
.sidebar.collapsed .sidebar-group-label {
  display: none;
}

/* The way back out of settings mode. A row like any other, but it changes
   what the rail is showing rather than where in it you are, so it reads in
   the muted color and does not take the selected pill. */
.sidebar-back {
  display: flex;
  align-items: center;
  gap: 11px;
  margin: 1px 8px;
  padding: 8px 10px;
  border: 1px solid transparent;
  border-radius: var(--radius-2);
  color: var(--muted);
  font-size: 14px;
  font-weight: 500;
  text-decoration: none;
  white-space: nowrap;
}

.sidebar-back:hover {
  background: var(--sidebar-hover);
  color: var(--text);
}

/* Narrowing the rail, and who you are, under a rule at its foot. */
.sidebar-account {
  flex: 0 0 auto;
  padding: var(--space-2) 8px;
  border-top: 1px solid var(--border);
}

/* Reads like a navigation row rather than a button, because that is what it
   sits among. The chevron points the way the rail is about to move. */
.sidebar-collapse {
  display: flex;
  align-items: center;
  gap: 11px;
  width: 100%;
  padding: 7px 10px;
  border: 1px solid transparent;
  border-radius: var(--radius-2);
  background: none;
  color: var(--muted);
  font-size: 14px;
  font-weight: 500;
  text-align: left;
  cursor: pointer;
}

.sidebar-collapse:hover {
  background: var(--sidebar-hover);
  color: var(--text);
}

.sidebar-collapse .sidebar-icon {
  transform: rotate(180deg);
  transition: transform 120ms ease;
}

.sidebar.collapsed .sidebar-collapse .sidebar-icon {
  transform: none;
}

.account-button {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 7px 10px;
  border: 1px solid transparent;
  border-radius: var(--radius-2);
  background: none;
  color: var(--text);
  font-weight: 550;
  text-align: left;
  overflow: hidden;
}

.account-button:hover:not(:disabled) {
  background: var(--sidebar-hover);
}

.account-name {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Says the row opens something. Without it the account is a name that happens
   to be clickable, which nobody clicks. */
.account-chevron {
  display: flex;
  flex: 0 0 auto;
  color: var(--muted);
}

.sidebar.collapsed .account-button {
  justify-content: center;
  padding: 7px 0;
}

/* --- the name of the page ---------------------------------------------------
   At the top of the content, big and first. It is the only large thing on a
   page, which is what makes it findable without a rule under it. */
.page-heading-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  margin: 0 0 var(--space-4);
}

/* A page's one primary action, level with its title. */
.page-heading-actions {
  display: flex;
  flex: 0 0 auto;
  gap: var(--space-2);
}

.page-heading-actions:empty {
  display: none;
}

.page-heading {
  margin: 0;
  font-size: 30px;
  font-weight: 700;
  letter-spacing: -0.02em;
  line-height: 1.2;
}

/* What follows the name in the heading, such as "You": smaller and muted, so
   the name stays what the heading says. */
.page-heading-note {
  font-size: var(--text-body);
  font-weight: 400;
  letter-spacing: 0;
}

/* When the page's action sits under the title, the two are one header block
   rather than two things with a gap between them. */
.page-heading-row:has(+ .page-actions) {
  margin-bottom: var(--space-2);
}

@media (max-width: 760px) {
  .page-heading {
    font-size: 24px;
  }
}

/* The sentence under a field, saying what it is for. Under rather than beside:
   a hint beside a field is read after the field has already been filled in. */
/* Prices for particular models: a row per model, with the columns named
   once above them rather than four labels repeated down the dialog. The
   fields are the form's own; only the grid is here. */
/* Signing a provider in to a plan: the steps, the page, and the one-time
   code large enough to copy by eye onto another device. */
.provider-sign-in {
  margin-bottom: 10px;
}

.provider-sign-in-code {
  font-size: 1.6rem;
  letter-spacing: 0.12em;
  user-select: all;
}

.priced-models {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin-bottom: 10px;
}

/* A model takes its own line and its five prices share the next: five
   fields and a name across one line of a dialog leaves no room for the
   name, and a header cannot sit over a row that wraps, so the columns
   are named inside the fields. */
.priced-models-row {
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr)) 30px;
  gap: 6px;
  align-items: center;
  margin-bottom: 6px;
}

.priced-models-row input:first-child {
  grid-column: 1 / -1;
}

.priced-models-row input {
  font-variant-numeric: tabular-nums;
}

.priced-models-row input + input {
  text-align: right;
}

.priced-models-add {
  margin-bottom: 10px;
}

/* Narrow enough that four fields across would leave no room for a model
   name: the name takes the line, the three prices share the next, and
   the column names move into the fields, since a header cannot sit over
   two rows at once. */

/* A control standing on its own rather than inside its label — the zone
   box that appears under "always this zone" — carries the bottom margin
   the label would have given it, so the hint below does not ride up over
   it. The hint's own top margin is negative by design: it closes the gap
   under a label, and without this there is no gap to close. */
.field-standalone {
  margin-bottom: 10px;
}

.field-hint {
  margin: -6px 0 var(--space-3);
  font-size: var(--text-label);
  max-width: var(--measure);
}

/* --- lists of account things ----------------------------------------------
   Tokens, sessions: each carries a name, two or three dates, an address and
   an action. A table wide enough for all of that is a table nobody can read
   on a laptop, let alone a phone. A row can wrap; a column cannot. */
.settings-section {
  margin-bottom: var(--space-6);
}

.settings-section-head {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: var(--space-3);
  margin-bottom: var(--space-5);
}

/* The title keeps room to be read: squeezed by a row of controls beside
   it, "Use and cost" went a word to a line under the date fields. The
   controls go below it instead. */
.settings-section-head > div:first-child {
  flex: 1 1 14rem;
  min-width: 0;
}

.settings-section-head h3 {
  margin: 0 0 4px;
}

/* A card that is only a head, a page's own title with its icons, has nothing
   under it for the margin to separate from. */
.settings-section.card > .settings-section-head:last-child {
  margin-bottom: 0;
}

.settings-section-head p {
  margin: 0;
  max-width: var(--measure);
}

.settings-row {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  margin-bottom: var(--space-2);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-3);
}

/* The description keeps room to be read: with no basis, a row whose
   controls were a key button, a dropdown and two more squeezed it to a
   column three words wide. When the two do not fit side by side the
   controls go onto their own line, on the right. */
.settings-row-body {
  flex: 1 1 20rem;
  min-width: 0;
}

.settings-row-title {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-2);
  overflow-wrap: anywhere;
}

.settings-row-subtitle {
  margin-top: 3px;
  font-size: var(--text-label);
  overflow-wrap: anywhere;
}

.settings-row-actions {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  /* It may shrink, which is what lets it wrap at all: with flex 0 0 auto and
     an automatic minimum it takes its content's width whatever the room, and
     a wrap that is allowed never happens. */
  flex: 0 1 auto;
  min-width: 0;
  /* Wrapping at any width, not only on a phone.
     A row's controls are not always two buttons: a connected server carries
     four -- Turn off, Unlink, Replace token, Remove -- which come to 415px,
     and a skill puts a dropdown beside them. That fitted on a wide screen
     and overflowed everywhere between the phone breakpoint and about a
     thousand pixels, where the content column is narrower than the buttons
     and nothing was allowed to wrap: the row pushed the column wider than
     itself and the page grew a horizontal scrollbar. On a screen with room
     this changes nothing, because a row that fits never wraps. */
  flex-wrap: wrap;
  justify-content: flex-end;
  margin-left: auto;
}

/* Boxes inside boxes, on a screen with no room for them.
   ------------------------------------------------------------------------
   Measured on a 390px phone: the page gave up 28 pixels of its width, the
   card inside it another 34, and the row inside that another 34 — a hundred
   and four pixels, better than a quarter of the screen, spent on the space
   between three borders that all say the same thing. On a wide screen that
   nesting reads as structure; on a phone it reads as a column of text that
   will not fit.

   So on a phone the middle box gives up its own frame. A card at the width of
   the screen is a band rather than a card, which is what every mail program
   does at this size, and the rows inside it keep the padding that separates
   them from each other. */
@media (max-width: 600px) {
  .content {
    padding: var(--space-4) var(--space-3);
  }

  /* Full width, no side frame: the section's heading and its rows keep the
     page's own margin instead of adding a second one inside it. */
  .card,
  .settings-section.card {
    --card-padding-inline: var(--space-3);
    margin-left: calc(var(--space-3) * -1);
    margin-right: calc(var(--space-3) * -1);
    border-left: none;
    border-right: none;
    border-radius: 0;
    padding-left: var(--card-padding-inline);
    padding-right: var(--card-padding-inline);
  }

  /* A row inside one of those is already inside the page's margin, so it
     draws its own separation with less. */
  .settings-row {
    padding-left: var(--space-3);
    padding-right: var(--space-3);
    margin-left: calc(var(--space-3) * -1);
    margin-right: calc(var(--space-3) * -1);
    border-left: none;
    border-right: none;
    border-radius: 0;
  }
}

@media (max-width: 600px) {
  /* The description never shares its width with the action: an action as
     wide as its words left the description a column four words across, and
     a subtitle wrapping to four lines next to a button has a lot of nothing
     under it.

     Any action drops below, on the right, which is where it was. Stacked to
     the left, an action that is one icon (a plus, a cross) was a mark alone
     on a line under a heading, and read as something the heading had said
     rather than a button. */
  .settings-section-head {
    flex-wrap: wrap;
    gap: var(--space-3);
  }

  .settings-section-head > div:first-child {
    min-width: 100%;
  }

  .settings-section-head > *:not(:first-child) {
    margin-left: auto;
  }

  /* One button sits on the title's line instead, and the description runs
     the full width under both: the title is short, so the line has room,
     and the button stays beside what it adds to. So do a few icons marked
     head-icons (a page's rename and delete): each is a square, two or three
     fit beside a title. A row of words keeps the rule above, since beside
     the title it would squeeze it. */
  .settings-section-head:has(h3):has(> div:first-child + :is(button, .head-icons, .menu-button):last-child) {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    column-gap: var(--space-3);
    row-gap: 0;
    align-items: center;
  }

  .settings-section-head:has(h3):has(> div:first-child + :is(button, .head-icons, .menu-button):last-child) > div:first-child {
    display: contents;
  }

  .settings-section-head:has(h3):has(> div:first-child + :is(button, .head-icons, .menu-button):last-child) h3 {
    grid-row: 1;
    grid-column: 1;
    margin: 0;
  }

  .settings-section-head:has(h3):has(> div:first-child + :is(button, .head-icons, .menu-button):last-child) > div:first-child > p {
    grid-row: 2;
    grid-column: 1 / -1;
    margin-top: 4px;
  }

  .settings-section-head:has(h3):has(> div:first-child + :is(button, .head-icons, .menu-button):last-child) > :is(button, .head-icons, .menu-button) {
    grid-row: 1;
    grid-column: 2;
    margin-left: 0;
  }

  .settings-row-body {
    min-width: 100%;
  }

  /* Wrapping, because a row's controls are not always two buttons: the
     skills list puts a dropdown beside them, and 160 pixels of dropdown
     plus a button plus an icon will not fit on a phone. They go onto a
     second line, still against the right edge, rather than squeezing. */
  .settings-row-actions {
    width: 100%;
    justify-content: flex-end;
    flex-wrap: wrap;
  }

  /* A row whose actions are icons, up to three of them, keeps them beside
     the title (SettingsRow adds has-icon-actions): on a line of their own
     they sat alone under an empty band, the row twice its height, and with the name and the badge above them they
     read as belonging to nothing. Four or more (the people page) do not fit
     beside a name, and a word button does not either, so those still drop. */
  .settings-row.has-icon-actions {
    flex-wrap: nowrap;
    align-items: flex-start;
  }

  .settings-row.has-icon-actions > .settings-row-body {
    min-width: 0;
  }

  .settings-row.has-icon-actions > .settings-row-actions {
    width: auto;
    flex-wrap: nowrap;
  }

  /* A row about a person keeps the person's picture beside their name: with
     the body taking the full width, the picture sat alone on a line above
     it. A single control (a menu of what can be done to them) stays on the
     same line; more than one still drops below. */
  .settings-row:has(> .avatar, > .settings-row-mark) > .settings-row-body {
    min-width: 0;
    flex: 1 1 calc(100% - 28px - var(--space-3));
  }

  /* A wrapper of several buttons is one child too, so it is excluded: the
     users page puts five icons in one, and they do not fit beside a name. */
  .settings-row:has(> .avatar, > .settings-row-mark):has(> .settings-row-actions > :only-child):not(
      :has(> .settings-row-actions > .row-actions > :nth-child(2))
    ) {
    flex-wrap: nowrap;
  }

  .settings-row:has(> .avatar, > .settings-row-mark):has(> .settings-row-actions > :only-child):not(
      :has(> .settings-row-actions > .row-actions > :nth-child(2))
    )
    > .settings-row-actions {
    width: auto;
  }
}

/* The mark of a row about a circle stands where a person's picture does, the
   same size, so the names of a list of both line up. */
.settings-row-mark {
  display: inline-flex;
  flex: 0 0 28px;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  color: var(--muted);
}

/* Having none of something is ordinary, so it is said rather than shown as an
   error — and given the room a row would have taken, so the page does not
   collapse to a sentence squeezed against a heading. Dashed, so an empty list
   does not read as a row that is there. */
.settings-empty {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 120px;
  margin: 0;
  padding: var(--space-6) var(--space-5);
  border: 1px dashed var(--border);
  border-radius: var(--radius-3);
  text-align: center;
  text-wrap: balance;
  line-height: 1.6;
}

/* The secret and its copy button as one object.

   The button is inside the box at the right end, centred against it, and the
   text is given room to stop before it rather than running underneath. The
   bottom margin moved out here so that centring is measured against the box
   itself and not against the box plus the gap under it. */
.secret-box {
  position: relative;
  margin: 0 0 var(--space-3);
}

.secret-box .secret {
  margin: 0;
  padding-right: 40px;
}

/* Descendant rather than child: the tooltip wraps the button in a span, and
   though that span is display: contents and lays no box of its own, it is
   still the button's parent as far as a selector is concerned. */
.secret-box .copy-button {
  position: absolute;
  top: 50%;
  right: 8px;
  transform: translateY(-50%);
}

/* A secret, shown once, in a block wide enough to select in one gesture. */
.secret {
  margin: 0 0 var(--space-3);
  padding: var(--space-3);
  background: var(--field);
  border: 1px solid var(--border);
  border-radius: var(--radius-2);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 13px;
  line-height: 1.7;
  overflow-wrap: anywhere;
  white-space: pre-wrap;
}

.dialog-wide {
  max-width: 560px;
}

/* A destructive action written as text rather than as a button: it sits at
   the end of a row among ordinary information, and a red button on every row
   makes a list look like a warning. */
button.link.danger {
  color: var(--bad);
}

/* The same action as an icon. A word at the end of every row competes with
   the row's own words for the reader's attention, and "Remove" is not what
   the row is about; a small red icon says the same thing without being read.
   It keeps its name for anybody not looking at it, in aria-label and title. */
.icon-button.danger {
  color: var(--bad);
}

.icon-button.danger:hover {
  background: var(--bad-surface, var(--border));
  color: var(--bad);
}

/* A checkbox and its label on one line, which is how a checkbox reads. The
   form styles put labels in a column, which is right for a field and wrong
   for this. */
label.checkbox {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: var(--space-3);
  font-size: var(--text-label);
  color: var(--muted);
}

label.checkbox input {
  width: auto;
  /* A tick box, not a text field: the phone's thumb-sized height for inputs
     made every row of ticks 40 pixels tall. The label is what is tapped. */
  min-height: 0;
  margin: 0;
}

/* In a dialog it is a line of the form like the fields' own labels, not a
   smaller muted one beside them. */
.dialog label.checkbox {
  margin-top: var(--space-3);
  margin-bottom: var(--space-3);
  font-size: var(--text-body);
  color: var(--text);
}

/* A form is read down a column, and a field stretched across a wide screen is
   a line the eye has to travel to get back from. Wide enough for an address
   and a hostname, no wider -- and with one rhythm down it.

   It was a plain block, so what separated its children was whatever margin
   each happened to carry: a label 10px, a paragraph 15px, and a row of
   fields or a row of buttons nothing at all -- so the buttons under the
   daily brief's two fields sat flush against them while the note below had
   room. One gap here, and the margins of the children that had their own
   are given up to it, so every form-narrow reads the same way down. */
.form-narrow {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  max-width: 520px;
}

/* A control that follows a column of fields belongs to the same form and
   needs the same gap. form-narrow's own gap governs only what is inside it,
   so a submit button placed after it -- which is how the preferences page
   and others are written -- sat flush against the last field. */
.form-narrow + button,
.form-narrow + .page-actions,
.form-narrow + .row {
  margin-top: var(--space-4);
}

/* Every child gives up its own vertical margin to the gap above. Zeroing
   only the ones that obviously had margins left .page-actions carrying its
   own 16px on top of the gap, so the daily brief's buttons sat at 32 where
   every other form read 16 -- which is the same bug as before wearing a
   different number. */
.form-narrow > *,
/* Written twice on purpose: .page-actions:not(:first-child) carries a
   margin-top of its own and is more specific than a bare child selector, so
   a one-selector reset lost to it and the gap came out doubled again. This
   matches its specificity and comes later in the sheet. */
.form-narrow > *:not(:first-child) {
  margin-top: 0;
  margin-bottom: 0;
}

/* The picture button under a source editor, and the reason beside it. The
   button is the same one the rich text toolbar uses, so it carries an icon;
   here it has a word too, because there is no toolbar around it to make its
   purpose obvious. */
.editor-actions {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
  margin-top: calc(var(--space-2) * -1);
}

.editor-actions button {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.editor-actions .muted {
  font-size: var(--text-label);
  max-width: var(--measure);
}

/* --- highlighted markup ---------------------------------------------------
   Five colors and no more. A highlighter that gives every token type its own
   hue is a rainbow to read past; what a reader needs here is to tell the
   structure from the content, so the tags recede and the values stand out.
   The palette follows the page's, light and dark, so this looks like the rest
   of the dashboard rather than like somebody's editor theme. */
.code {
  --tok-punctuation: #8a8a93;
  --tok-name: #2f6f4f;
  --tok-attribute: #7a5a1e;
  --tok-value: #2b5b8c;
  --tok-comment: #9a9aa2;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme='light']) .code {
    --tok-punctuation: #7c7c85;
    --tok-name: #86c9a2;
    --tok-attribute: #d8bc7d;
    --tok-value: #93bde6;
    --tok-comment: #71717a;
  }
}

:root[data-theme='dark'] .code {
  --tok-punctuation: #7c7c85;
  --tok-name: #86c9a2;
  --tok-attribute: #d8bc7d;
  --tok-value: #93bde6;
  --tok-comment: #71717a;
}

.code .tok-tag {
  color: var(--tok-punctuation);
}
.code .tok-name {
  color: var(--tok-name);
}
.code .tok-attribute {
  color: var(--tok-attribute);
}
.code .tok-value {
  color: var(--tok-value);
}
.code .tok-comment {
  color: var(--tok-comment);
  font-style: italic;
}
.code .tok-doctype {
  color: var(--tok-comment);
}

/* --- the control above a table --------------------------------------------
   The filter fields used to be a permanent row of empty inputs under every
   header: the widest thing on the page, and used on a fraction of visits. They
   open from here now, and this row is also what gives the table air above it —
   the fields sat hard against the header rule with nothing between. */
.table-tools {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin-bottom: var(--space-3);
}

.table-tools button {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 5px 10px;
  font-size: var(--text-label);
}

/* Open, or narrowing the list: either way it is doing something, and it says
   so the way a selected row in the rail does. */
.table-tools button.active {
  border-color: var(--border-strong);
  color: var(--text);
  background: var(--field);
}

.table-tools button.link {
  padding: 5px 2px;
}

/* A page's own search box takes the row's free width, with the table's
   buttons after it, at their height. */
.table-tools input[type='search'] {
  flex: 1 1 160px;
  min-width: 0;
  padding-block: 5px;
  font-size: var(--text-label);
}

/* The box that chooses a row takes the width of the box and no more. */
.table-surface .table-choose {
  width: 1%;
  padding-right: 0;
}

.table-choose input {
  margin: 0;
}

/* Air above the fields, so they read as a row that opened rather than as part
   of the header. */
.filter-row > td {
  padding-top: var(--space-2);
}

/* --- a secret that is stored but never shown ------------------------------
   Whether one is set belongs beside the label, not in the placeholder. A
   placeholder is a hint about what to type: it disappears the moment somebody
   types, so a status written there vanishes exactly when the reader is acting
   on it. And the way to remove one hung under the field as a bare underlined
   link, in the gap between two controls, belonging to neither. */
.secret-label {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

/* Pushed to the end of the label's row, where a destructive action can be
   found without being in the way of the field. */
.secret-clear {
  margin-left: auto;
  padding: 0;
  font-size: var(--text-label);
}

/* The generic label rule makes a label's span a block with a gap under it,
   which is right for a caption and wrong for a row that also holds a tag. */
.secret-field > .secret-label {
  display: flex;
  margin-bottom: 6px;
}

/* --- editors ---------------------------------------------------------------
   A template, a layout, a message being written: the form on the left and
   what it produces on the right, side by side while the screen is wide enough
   for both, because the point of a preview is to be looked at while typing.
   Below the width where two columns would each be too narrow to read, the
   preview follows the form. */
.editor {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: var(--space-6);
  align-items: start;
}

@media (max-width: 1100px) {
  .editor {
    grid-template-columns: minmax(0, 1fr);
  }
}

.editor-preview {
  position: sticky;
  top: var(--space-6);
}

.editor-form .page-actions {
  margin-bottom: 0;
  align-items: center;
}

.editor-preview > h3 {
  margin-top: 0;
}

/* Markup and template text are read by their shape, so they are set in the
   monospace face. Long lines wrap rather than scroll: a template pasted from
   somewhere else is often one long line, and a box that scrolls sideways
   hides most of it. */
.code-editor {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 13px;
  line-height: 1.5;
  min-height: 220px;
  resize: vertical;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  tab-size: 2;
}

.plain-editor {
  min-height: 240px;
  resize: vertical;
  line-height: 1.5;
}

.locale-tabs {
  margin-top: var(--space-2);
}

/* A row of fields is followed by the next field at the distance a field is
   followed by one. */
.row.fields {
  margin-bottom: 10px;
}

/* The languages a template or layout is written in, beside its name: chips
   on the name's own line, centered on it. */
.languages {
  display: inline-flex;
  flex-wrap: wrap;
  gap: var(--space-1);
}

/* One field per variable, as many across as fit. */
.variables {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
  gap: var(--space-2) var(--space-3);
  margin-bottom: var(--space-3);
}

.preview-subject {
  margin-bottom: var(--space-2);
  font-weight: 600;
}

.preview-subject .muted {
  font-weight: 400;
}

/* One column, held to a readable width: a message with no preview beside it
   should not stretch across a wide screen either. */
.editor-single {
  grid-template-columns: minmax(0, 1fr);
  max-width: 900px;
}

/* A link dressed as a button, for an action that goes somewhere. The
   button rules above give it the look; this gives it the sizing a link
   lacks and takes the underline away. */
a.button {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  text-decoration: none;
  line-height: normal;
}

a.button.primary {
  background: var(--accent);
  color: var(--accent-text);
  border-color: var(--accent);
}

a.button:hover {
  background: var(--sidebar-hover);
}

a.button.primary:hover {
  background: var(--accent-hover);
  border-color: var(--accent-hover);
}

/* --- composing a message ---------------------------------------------------
   The local part is typed and the domain is fixed beside it: what is being
   chosen is the mailbox, and the domain was chosen a field earlier. */
.address-input {
  display: flex;
  align-items: center;
  gap: var(--space-1);
}

.address-input input {
  flex: 1;
  min-width: 0;
}

/* The wrapper and the @ are spans inside a label, which the rule for a
   label's caption would otherwise style: a caption's margin under the
   wrapper made this control taller than the one beside it, so the two
   labels no longer lined up. */
.address-input,
.address-input > span {
  margin: 0;
  font-size: inherit;
}

.address-input select {
  flex: 1;
  min-width: 0;
  width: auto;
}

/* A select draws itself a little taller than an input with the same padding,
   which put the caption of this field two pixels above its neighbor's. */
.address-input > input,
.address-input > select {
  height: 36px;
}

.address-domain {
  white-space: nowrap;
  color: var(--muted);
}

.editor-switch {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  margin-bottom: var(--space-2);
  font-size: var(--text-label);
}

.editor-switch button {
  padding: 3px 10px;
  border-radius: var(--radius-round);
  font-size: var(--text-label);
}

.editor-switch button.active {
  background: var(--text);
  color: var(--surface);
  border-color: var(--text);
}

.richtext {
  border: 1px solid var(--border);
  border-radius: var(--radius-2);
  background: var(--surface);
  margin-bottom: var(--space-3);
}

.richtext:focus-within {
  border-color: var(--text);
}

.richtext-toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 2px;
  padding: 4px;
  border-bottom: 1px solid var(--border);
}

.richtext-toolbar button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 30px;
  height: 30px;
  padding: 0 8px;
  border: none;
  border-radius: var(--radius-1);
  background: none;
  font-size: 14px;
  color: var(--text);
}

.richtext-toolbar button:hover {
  background: var(--sidebar-hover);
}

.richtext-bold {
  font-weight: 700;
}
.richtext-italic {
  font-style: italic;
}
.richtext-underline {
  text-decoration: underline;
}

.richtext-link {
  display: flex;
  gap: var(--space-1);
  margin-left: var(--space-2);
}

.richtext-link input {
  width: 260px;
  padding: 4px 8px;
}

.richtext-editor {
  min-height: 240px;
}

/* A reply written inside a conversation starts at the height of a reply, not
   of a page. It grows as it is typed — the box has no fixed height, so the
   conversation under it moves down as the message gets longer, which is what
   every mail program does with an inline reply. Two hundred and forty pixels
   of empty box above a quoted thread is a page pretending somebody has a lot
   to say. */
.mailbox-pane .richtext-editor {
  min-height: 120px;
}

/* A draft opened from the thread is a message that was already written, so its
   editor starts taller than an empty reply's. */
.mailbox-pane .mailbox-thread-compose.is-draft .richtext-editor {
  min-height: 200px;
}

/* A signature is three lines and a link, not a message. */
.signature-editor .richtext-editor,
.signature-editor textarea {
  min-height: 120px;
}

/* The editing surface is a sheet of paper, in both themes.
   
   What is in this box is a message, and a message is read on white: the
   reader shows one that way (an iframe with a white ground), and so does
   almost every mail client it will land in. Showing it on the dashboard's
   dark ground was not a theme, it was a lie about what the recipient gets
   -- and a broken one, because a message only paints the backgrounds it
   sets. An HTML message that fills its card with #ffffff and leaves the
   paragraphs alone came out in bands, light where it painted and dark
   where it did not, with any text that set no colour of its own inheriting
   the dashboard's near-white and vanishing on the message's own pale blue.
   
   The light tokens are redeclared here rather than the colours written
   out, so that every rule below reading --muted, --border-strong or
   --accent lands on a value meant for white. It also puts out a fire
   nobody lit on purpose: this box holds the message's markup in the
   dashboard's own document, where the dashboard's element rules reach it,
   and `tbody tr:hover` -- written for the data tables -- painted the rows
   of a table-built message with --sidebar-hover as the pointer crossed
   them. In dark mode that is #17171a, so hovering over a message turned
   bands of it black. Redefining the token here is what stops it, and stops
   the next such rule too. */
.richtext-editor {
  --background: #ffffff;
  --surface: #ffffff;
  --rail: #f7f7f6;
  --field: #eeeeea;
  --border: #e7e7e4;
  --border-strong: #d8d8d4;
  --text: #18181b;
  --muted: #6f6f76;
  --accent: #1f1f22;
  --accent-text: #ffffff;
  /* Nothing in a message is a row of a table of the dashboard's. */
  --sidebar-hover: transparent;

  background: var(--surface);
  color: var(--text);
  /* So a form control or a scrollbar the message carries is drawn light
     with everything else, rather than dark on the white. */
  color-scheme: light;

  padding: 12px 14px;
  outline: none;
  line-height: 1.5;
  word-wrap: break-word;
  /* A quoted message is somebody else's markup inside this box, and an
     inline position:fixed in it would otherwise leave the box and cover
     the page. */
  contain: paint;
  isolation: isolate;
}

/* The plain text tab is deliberately not paper: it stays in the dashboard's
   theme, because plain text has no colours of its own and is shown by the
   client at the other end in whatever theme that reader is using. Rich text
   on white and plain text in the theme are both honest about what the
   recipient gets; the ground changing as the tabs are switched is that
   difference, not a bug to iron out. */

/* The sheet reaches the box's corners: the toolbar keeps the dashboard's
   colours, and the paper begins under it. */
.richtext-editor {
  border-bottom-left-radius: var(--radius-2);
  border-bottom-right-radius: var(--radius-2);
}

/* The quoted message a reply carries, folded away while the answer is being
   written. Hidden rather than removed: it is still in the document and still
   in what is sent — what is being written is the answer, and the thing it
   answers is a click away and, in a conversation, on the screen already. */
.richtext-editor.quoted-hidden .teanode-quote {
  display: none;
}

.compose-quoted {
  margin: -4px 0 12px;
}

.richtext-editor:empty::before {
  content: attr(data-placeholder);
  color: var(--muted);
  pointer-events: none;
}

.richtext-editor p {
  margin: 0 0 0.6em;
}
.richtext-editor h2 {
  margin: 0.4em 0;
  font-size: 1.3em;
}
.richtext-editor blockquote {
  margin: 0.4em 0;
  padding-left: 12px;
  border-left: 3px solid var(--border-strong);
  color: var(--muted);
}
.richtext-editor a {
  color: var(--accent);
}

.attachments {
  margin: var(--space-3) 0;
}

.attachment {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: 6px 0;
  font-size: var(--text-body);
}

.attachment-name {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.attachment-actions {
  display: flex;
  align-items: center;
  gap: var(--space-3);
}

/* The release notes on the version card. A changelog entry is prose with
   bullets in it, so it keeps its line breaks and is given a bounded height:
   somebody deciding whether to upgrade should be able to read it here, and
   should not have to scroll past a year of it to reach the button. */
.upgrade-notes {
  max-height: 260px;
  overflow-y: auto;
  margin: var(--space-2) 0 0;
  padding: var(--space-3) var(--space-4);
  background: var(--surface-sunken, var(--field));
  border: 1px solid var(--border);
  border-radius: var(--radius-2);
}

/* The heading over the release notes on the version card. Small, because it
   labels a box rather than opening a section.
   
   The space above it separates it from the three rows of the version above;
   it had a quarter of that, so the label read as a fourth row of the list
   rather than as the start of something else. */
.upgrade-notes-heading {
  margin: var(--space-5) 0 0;
  font-size: var(--text-label);
  color: var(--muted);
}

/* Rendered release notes: a changelog's headings and bullets, not a wall of
   monospace with its hashes and hyphens still in it.
   
   The margins are collapsed at the ends so the box's own padding is the only
   space at the top and bottom — a paragraph's margin inside a padded box is
   the padding twice. */
.markdown > *:first-child {
  margin-top: 0;
}

.markdown > *:last-child {
  margin-bottom: 0;
}

.markdown h5 {
  margin: var(--space-4) 0 var(--space-2);
  font-size: var(--text-body);
  font-weight: 600;
  color: var(--text);
}

.markdown p {
  margin: 0 0 var(--space-2);
}

.markdown ul {
  margin: 0;
  padding-left: 1.1em;
}

.markdown li {
  margin-bottom: var(--space-2);
  line-height: 1.55;
}

.markdown li:last-child {
  margin-bottom: 0;
}

/* Inline code in a note is a file name or a version, and is read against the
   prose around it rather than as a block to copy. */
.markdown code {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.92em;
  padding: 1px 4px;
  border-radius: var(--radius-1);
  background: var(--border);
  overflow-wrap: anywhere;
}

/* The mark and, when the server has been upgraded under this page, the one
   control that fixes it. A row, so the button sits at the rail's top right
   rather than pushing the mark along. */
.sidebar-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  margin-bottom: var(--space-2);
}

/* The gap below the mark belongs to the row now, or the button beside it would
   be centered against the mark's margin box and sit a few pixels high. */
.sidebar-top .sidebar-brand {
  margin-bottom: 0;
}

/* Yellow, because it is a notice rather than a problem: nothing is broken,
   the page is simply older than the server it is talking to. */
.sidebar-refresh {
  display: flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: 28px;
  height: 28px;
  margin-right: var(--space-2);
  padding: 0;
  border: 1px solid var(--warn);
  border-radius: var(--radius-1);
  background: none;
  color: var(--warn);
}

.sidebar-refresh:hover {
  background: var(--sidebar-hover);
}

/* Collapsed to icons there is no room beside the mark, and the mark is what
   has to stay. */
.sidebar.collapsed .sidebar-refresh {
  display: none;
}

/* A dot on the one row that has something waiting on it. It says "look here"
   and nothing else, which is all a rail should say. */
.sidebar-dot,
.tab-dot {
  flex: 0 0 auto;
  width: 7px;
  height: 7px;
  margin-left: auto;
  border-radius: 50%;
  background: var(--warn);
}

/* The same dot on the tab that holds the reason for it. A dot in the rail
   takes somebody to a page of tabs, and without it here the page they land on
   says nothing about why they came. */
/* Inline-block, because width and height do nothing to an inline box: a tab
   is a line of text rather than a flex row, and the dot came out as nothing
   at all — no width, and the height of a line. */
.tab-dot {
  display: inline-block;
  vertical-align: middle;
  margin-left: 6px;
}

/* Collapsed, the dot moves onto the icon rather than disappearing: the reason
   to look is the same whether or not the rail is showing words. */
.sidebar.collapsed .sidebar-dot {
  position: absolute;
  right: 8px;
  top: 8px;
  margin-left: 0;
}

.sidebar.collapsed nav a {
  position: relative;
}

/* Read aloud, never drawn. The dot above says "look here" to somebody who can
   see it and nothing at all to somebody who cannot, and a title attribute is
   not reliably announced. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  white-space: nowrap;
  border: 0;
  clip-path: inset(50%);
}

/* The spam score's breakdown: which check fired, and what it cost. Laid out
   as chips so a message with a dozen of them stays readable, and so the sign
   of each contribution is visible without reading the number. */
.spam-checks {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1) var(--space-2);
}

.spam-check {
  display: inline-flex;
  align-items: baseline;
  gap: var(--space-1);
  white-space: nowrap;
}

.spam-check-bad {
  color: var(--bad);
}

.spam-check-good {
  color: var(--good);
}

/* The days of the week as toggle buttons, one row of seven that wraps on a
   narrow screen. A pressed day is drawn as the welcome page's chosen service
   is, in the text color with the same border width, so that choosing one
   moves nothing. */
.weekday-buttons {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1);
  margin-bottom: var(--space-3);
}

.weekday-buttons button {
  flex: 1 1 2.75rem;
  min-height: 40px;
  padding-inline: var(--space-2);
  justify-content: center;
}

.weekday-buttons button[aria-pressed='true'] {
  border-color: var(--text);
  box-shadow: inset 0 0 0 1px var(--text);
  color: var(--text);
}

/* A segmented pair, drawn in the Tag palette. First used for teaching the
   spam filter under a message's verdict; also for choosing how a message is
   rendered in the dark.

   Drawn in the Tag palette — the same tint, text color and
   35% border the verdict tags above it use — so the pressed segment reads as
   "this message is tagged not-spam" rather than as a filled button, and so it
   follows both themes through the same tokens the tags do. A solid red or
   green fill was the one saturated block on a page of tints. */
.mark-spam {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
  margin-bottom: var(--space-4);
}

.segmented {
  display: inline-flex;
  border: 1px solid var(--border);
  border-radius: var(--radius-1);
  overflow: hidden;
}

/* A segment is usually a button and sometimes a link: one of the ways into
   the knowledge graph is a page of its own, and a link is what survives a
   middle click. An anchor in this dashboard is underlined unless the thing
   it is drawn as says otherwise, so this says otherwise. */
.segmented button,
.segmented a {
  border: none;
  border-radius: 0;
  background: transparent;
  color: var(--muted);
  padding: 3px 10px;
  font-size: 12px;
  font-weight: 550;
  line-height: 1.5;
}

.segmented a {
  display: inline-flex;
  align-items: center;
  text-decoration: none;
}

.segmented > * + *,
.segmented > * + .tooltip-anchor > * {
  border-left: 1px solid var(--border);
}

.segmented button:hover:not(:disabled):not(.active),
.segmented a:hover:not(.active) {
  color: var(--text);
  background: var(--sidebar-hover);
}

.segmented button.active.bad {
  color: var(--bad);
  background: color-mix(in srgb, var(--bad) 10%, transparent);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--bad) 35%, transparent);
}

.segmented button.active.good {
  color: var(--good);
  background: color-mix(in srgb, var(--good) 10%, transparent);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--good) 35%, transparent);
}

/* The reader's choice to darken a message that paints its own light ground.
   The frame element is inverted here, as one composited layer the page owns;
   the message's pictures are inverted back inside the document so they keep
   their colors. */
.message-frame.darkened {
  filter: invert(1) hue-rotate(180deg);
  /* The filter inverts the element's own border with everything else; a
     border in the text color inverts back to roughly the border color. */
  border-color: var(--text);
}

/* Choosing how a message renders in the dark, just above the frame. */
.frame-mode {
  display: flex;
  justify-content: flex-end;
  margin-bottom: var(--space-2);
}

.segmented button.active {
  color: var(--text);
  background: var(--sidebar-hover);
}

/* How a delivery is handed on, under its head line. */
.delivery-method {
  margin: 0 0 var(--space-2);
  font-size: 13px;
}

/* Lists of things to tick: users in a group, roles of a group, permissions of
   a role. A panel with a header and a ruled-off list of rows, rather than a
   box with a label wedged into its top border. */
.check-list {
  border: 1px solid var(--border, #ddd);
  border-radius: 8px;
  padding: 0;
  /* The same step as between the fields above it, so a dialog of labels and
     lists has one rhythm rather than two. */
  margin: 0 0 var(--space-3);
}
/* The header, and the one way a legend can be one: floated to the full width
   it lays out as an ordinary block at the top of the fieldset, so it can have
   a rule under it instead of sitting in a notch in the border. */
.check-list legend {
  float: left;
  width: 100%;
  margin: 0;
  padding: 9px 12px;
  border-bottom: 1px solid var(--border, #ddd);
  color: var(--muted);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}
/* How many are ticked, read as a count beside the heading rather than as more
   heading. */
.check-list legend .muted {
  margin-left: 8px;
  font-size: 11px;
  font-weight: 400;
  letter-spacing: 0;
  text-transform: none;
}
/* Everything under a floated header has to clear it. */
.check-list > p,
.check-list-search,
.check-list-items {
  clear: both;
}
.check-list > p {
  margin: 0;
  padding: 10px 12px 0;
  font-size: 13px;
}
/* The rows scroll, not the fieldset: a search box inside a scrolling
   fieldset scrolls away with what it is searching.
   
   On the page the list is as long as it is — a nested scroller inside a page
   that scrolls is two ways to move one list, and it cut the last row in half
   to say so. The cap is there for the server with two hundred domains. */
.check-list-items {
  max-height: 60vh;
  overflow-y: auto;
  padding: 6px 12px 8px;
}
/* In a dialog it has to be short: three of these and the buttons are off the
   bottom of the screen. */
.dialog .check-list-items {
  max-height: 14rem;
}
/* A row of the list, not a field of a form. It has to say so, because the
   dialog styles every label as a field: a bottom margin meant for a labelled
   input, and a small muted block for its text. Left alone the rows sat a
   third of an inch apart in a colour that read as disabled. */
label.check-list-item {
  display: flex;
  gap: 10px;
  /* The rows are the items of a scrolling column, and a flex item shrinks by
     default: a row of two lines was squeezed to the height of one and drawn
     over the row below it. */
  flex: 0 0 auto;
  /* Beats .dialog label, which is a class and a type and would otherwise
     win: the row is not a field and must not carry a field's margin. */
  margin: 0;
  padding: 5px 0;
  min-height: 30px;
  cursor: pointer;
}
/* What the row says, which is one line or two: a permission is its name and
   its key, a role is its name and what it is for. The second line is a span
   or a code inside this one, and the dialog's rule for a field's helper text
   turned it into a block inside an inline box — so it broke out of the row
   and was drawn over the row below it. */
/* Written against the list rather than the row, to beat the page's rule for
   a label with a tick box in it — which is a class and two types, and would
   otherwise decide how these rows are aligned.
   
   The box belongs to the first line, not to the middle of a row that may be
   two lines: centred against a name and its description it read as belonging
   to the description. */
.check-list-items > label.check-list-item {
  align-items: flex-start;
  gap: 10px;
}
.check-list-item > span {
  display: block;
  flex: 1 1 auto;
  min-width: 0;
  margin: 0;
  color: inherit;
  font-size: 14px;
  line-height: 1.4;
}
.check-list-item > span span,
.check-list-item > span code {
  display: block;
  margin: 0;
  color: var(--muted);
  font-size: 13px;
  line-height: 1.35;
}
.check-list-item input {
  margin: 0;
}
.check-list-item input[type='checkbox'],
.check-list-item input[type='radio'] {
  width: 16px;
  height: 16px;
  /* Down onto the first line's centre, since the row is aligned to its top. */
  margin-top: 2px;
}

/* The row before and after a change, in the audit log. */
/* What a change was, as a table: the field, what it was, what it is. Cards
   across the width put five fields in five places and left the eye to find
   each one before it could read it. */
.audit-diff {
  margin-top: 8px;
  overflow-x: auto;
}
.audit-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 12px;
}
.audit-table th,
.audit-table td {
  padding: 4px 10px 4px 0;
  text-align: left;
  vertical-align: top;
  /* An identifier is one long word and would otherwise decide the width of
     the column it is in. */
  overflow-wrap: anywhere;
}
.audit-table thead th {
  padding-bottom: 6px;
  border-bottom: 1px solid var(--border);
  color: var(--muted);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}
.audit-table tbody th {
  width: 1%;
  white-space: nowrap;
  font-weight: 600;
}
.audit-table tbody tr + tr th,
.audit-table tbody tr + tr td {
  border-top: 1px solid var(--border);
}
/* What it was reads as what it was: struck through and muted, so the column
   that matters is the one on the right. */
.audit-table .audit-before {
  color: var(--muted);
  text-decoration: line-through;
}
.audit-diff pre {
  white-space: pre-wrap;
  word-break: break-word;
  font-size: 12px;
  max-height: 20rem;
  overflow: auto;
}
.audit-filter {
  display: flex;
  align-items: center;
  gap: 8px;
}

/* --- mailbox ------------------------------------------------------------ */

/* The rail in mailbox mode: which mailbox, then its folders. */
.sidebar-mailbox {
  padding: 4px 12px 8px;
}
.sidebar-mailbox .select {
  width: 100%;
  font-size: 13px;
}
.sidebar nav a .sidebar-count {
  margin-left: auto;
  font-size: 11px;
  font-variant-numeric: tabular-nums;
  color: var(--muted);
}
.sidebar nav a.unread .sidebar-label {
  font-weight: 600;
}
.sidebar nav a.unread .sidebar-count {
  color: var(--text);
  font-weight: 600;
}
.sidebar.collapsed .sidebar-count,
.sidebar.collapsed .sidebar-mailbox {
  display: none;
}
.sidebar nav a[data-depth='1'] {
  padding-left: 32px;
}
.sidebar nav a[data-depth='2'] {
  padding-left: 48px;
}
.sidebar nav a[data-depth='3'] {
  padding-left: 64px;
}
/* Collapsed to icons, the rail has no room for a tree: every icon sits on
   the same axis. The phone's overlay always shows labels, so it keeps the
   indent. */
.sidebar.collapsed nav a[data-depth] {
  padding-left: 0;
}
@media (max-width: 760px) {
  .sidebar.collapsed nav a[data-depth='1'] {
    padding-left: 32px;
  }
  .sidebar.collapsed nav a[data-depth='2'] {
    padding-left: 48px;
  }
  .sidebar.collapsed nav a[data-depth='3'] {
    padding-left: 64px;
  }
}

/* The mailbox page: the list of a folder beside the message being read.
   The rail already spent one column on the folders, so the page has two. */
/* The mailbox measures itself, not the window.
   
   Two panes side by side need room, and how much room there is depends on
   the rail beside them: with it open, a 1290px window leaves the mailbox
   about 600px, the list takes its 380 and the message is read in 220 --
   narrow enough that the header table broke one character to a line, "Aval
   on Tren ding". The rule that drops to one pane was a media query, so it
   was asking the window, which was 1290 and fine. The container query below
   asks the only width that decides anything. */
.mailbox-frame {
  container-type: inline-size;
  container-name: mailbox;
  /* It takes the height left on the page rather than growing to its
     content, so the list inside it is the thing that scrolls. The rows
     have overflow-y: auto, but a scroller only scrolls when something
     above it says how tall it may be, and only a minimum was ever said:
     with a hundred threads loaded the frame stood five thousand seven
     hundred pixels tall in a window of eleven hundred, the rows never
     overflowed, and neither they nor the page moved. The knowledge
     navigator had the same fault, found first because its folders are
     longer. */
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
}

/* The page this frame sits on becomes the column that bounds it, and only
   this page: every other one is a document that grows and is scrolled
   whole, which is what a block container already does. */
main.content:has(> .mailbox-frame) {
  display: flex;
  flex-direction: column;
}
/* Only the frame gives up height. The breadcrumb hides its overflow, so
   left to shrink it was squeezed to a sliver above the heading. */
main.content:has(> .mailbox-frame) > :not(.mailbox-frame) {
  flex-shrink: 0;
}
.mailbox {
  display: grid;
  grid-template-columns: minmax(280px, 380px) minmax(0, 1fr);
  gap: 0;
  /* Fills the frame rather than standing on a minimum of its own, which
     was all that was ever said about its height. */
  flex: 1 1 auto;
  min-height: 0;
  border: 1px solid var(--border);
  border-radius: 8px;
  overflow: hidden;
  background: var(--surface);
}
.mailbox-list {
  border-right: 1px solid var(--border);
  display: flex;
  flex-direction: column;
  min-width: 0;
  /* A grid item refuses to shrink below its content without this, so the
     rows below would take their height from the whole folder again. */
  min-height: 0;
}
/* The strip across the top of a list: the box that searches it, and beside
   or under that the control that narrows it. Named for the shape rather than
   for the mailbox it was first written for -- the knowledge navigator is the
   same strip above the same kind of list, and a page should not have to wear
   another page's name to be drawn like it. */
.list-toolbar {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  align-items: center;
  padding: 8px;
  border-bottom: 1px solid var(--border);
  /* How tall everything in this row is. Written down rather than left to
     each control's own arithmetic, because the two have to agree and
     neither can see the other: the buttons take a line of their own once
     the box has the row, and on a narrow window the two are sized by
     different rules entirely.
     35.5 is what the box comes to here: 13px type on a 19.5px line, 7 and
     7 of padding, a pixel of border each side. */
  --toolbar-control: 35.5px;
}
.list-toolbar input[type='search'] {
  flex: 1 1 140px;
  min-width: 0;
  min-height: var(--toolbar-control);
  font-size: 13px;
  /* Growing into the whole row is a change of basis, and a basis animates.
     Without this the box arrives at full width in one frame, which reads as
     the toolbar having been replaced rather than having opened. */
  transition: flex-basis 140ms ease;
}

/* Searching takes the row.
   The column is 360 pixels wide and the box and the three ways in come to
   exactly that, so there is no slack for the box to grow into: something
   has to move. What moves is the set of buttons, onto the line below,
   which the toolbar already wraps onto at narrow widths. The alternative
   was hiding them while the box has focus, and a control that disappears
   at the moment somebody reaches for it is worse than one that moves.
   :placeholder-shown is the filled half of this: it holds only while the
   box is empty, so a box with something typed in it stays open after the
   focus has gone elsewhere. */
.list-toolbar input[type='search']:focus,
.list-toolbar input[type='search']:not(:placeholder-shown) {
  flex-basis: 100%;
}

/* And the ways in stand aside while it does.
   They wrapped to the line below at first, which kept them reachable and
   made the row taller at the moment somebody started typing -- so the list
   they were about to read moved down the screen. Out of the way entirely
   is better: the row stays one row, the box has the width, and the three
   come back the moment the box is empty and let go. What is being done
   while the box has focus is searching by name, and these three are the
   ways in that are not that. */
.list-toolbar input[type='search']:focus + .segmented,
.list-toolbar input[type='search']:not(:placeholder-shown) + .segmented {
  display: none;
}

/* As tall as the box beside it.
   The two were 28 and 35.5, which left the row looking like two rows that
   had not lined up. It is done by stretching and not by giving the buttons
   the box's larger type and padding, because that made them twelve pixels
   wider -- and the box, the gap and the buttons already came to exactly
   the 342 this column has, so they stopped fitting on one line and the box
   wrapped before anybody had touched it.
   The floor is for the line the buttons have to themselves once the box
   has taken the row: stretching alone would let them fall back to their
   own 28 there, so focusing the box would shrink them. */
.list-toolbar .segmented {
  align-self: stretch;
  min-height: var(--toolbar-control);
}

.list-toolbar .segmented button,
.list-toolbar .segmented a {
  font-size: 12px;
  padding: 4px 8px;
  height: 100%;
}
.mailbox-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  align-items: center;
  /* The same left inset as a row of the list, so the box that ticks all of
     them sits in the column of the boxes that tick one. */
  padding: 6px 10px;
  border-bottom: 1px solid var(--border);
  font-size: 12px;
  /* As tall with nothing selected as with something: the row of actions
     appears when the first message is ticked, and without this the list
     under it jumped down by fifteen pixels at the moment of the tick — so
     the second message somebody meant to tick had moved. The number is the
     icon buttons, the padding around them and the rule under them. */
  min-height: 47px;
}
/* The browser's own margin on a checkbox would put it four pixels to the
   right of the column it belongs to. */
.mailbox-actions input[type='checkbox'] {
  margin: 0;
}
/* Nothing to act on in an empty list: no band above "Nothing here". */
.mailbox-actions[hidden] {
  display: none;
}
.mailbox-actions button,
.mailbox-actions select {
  font-size: 12px;
  padding: 3px 8px;
}
/* Emptying the trash acts on the whole folder, not on what is ticked, so
   it stands apart at the far end of the row. */
.mailbox-actions .mailbox-empty-trash {
  margin-left: auto;
}
.mailbox-rows {
  list-style: none;
  margin: 0;
  padding: 0;
  overflow-y: auto;
  flex: 1 1 auto;
}
/* A row that can be swiped: the slot holds still and clips, the row slides
   over what letting go will do. */
.mailbox-row-slot {
  position: relative;
  overflow: hidden;
}
.mailbox-row-reveal {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: 0 var(--space-4);
  color: var(--accent-text);
  background: var(--muted);
  font-size: 13px;
  font-weight: 600;
  transition: background 120ms ease;
}
.mailbox-row-reveal.seen {
  justify-content: flex-start;
}
.mailbox-row-reveal.archive {
  justify-content: flex-end;
}
.mailbox-row-reveal.ready.seen {
  background: var(--accent);
}
.mailbox-row-reveal.ready.archive {
  background: var(--good);
}
.mailbox-row {
  position: relative;
  background: var(--background);
  touch-action: pan-y;
  display: grid;
  grid-template-columns: auto auto minmax(0, 1fr) auto;
  gap: 8px;
  align-items: start;
  padding: 8px 10px;
  border-bottom: 1px solid var(--border);
  cursor: pointer;
  font-size: 13px;
}
.mailbox-row:hover {
  background: var(--field);
}
.mailbox-row.active {
  background: var(--sidebar-hover);
}
.mailbox-row.settling {
  transition: transform 180ms ease;
}
.mailbox-row.unread .mailbox-row-from,
.mailbox-row.unread .mailbox-row-subject {
  font-weight: 600;
}
.mailbox-row-from {
  display: flex;
  align-items: center;
  gap: 6px;
}

/* A name is as long as it is — a list of recipients doubly so — and it used
   to be a bare run of text in this line, which a flex box cannot shorten. So
   it pushed everything after it out of the row instead: the count, and the
   badge saying an answer was begun and left, which is the one thing in the
   row somebody needs to see. The name gives way now, and the marks stay. */
.mailbox-row-who {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}

.mailbox-row-from > :not(.mailbox-row-who) {
  flex: 0 0 auto;
}
.mailbox-row-from,
.mailbox-row-subject {
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}
.mailbox-row-subject {
  color: var(--muted);
}
.mailbox-row.unread .mailbox-row-subject {
  color: var(--text);
}
.mailbox-row-when {
  display: flex;
  align-items: center;
  gap: 5px;
  font-size: 11px;
  color: var(--muted);
  white-space: nowrap;
}

/* What a message's checks said, in the width of one character. A list of
   fifty cannot spend a line each on SPF, DKIM, DMARC and a spam score, and
   one that said nothing at all for a message that failed them would be worse
   than one that never mentioned them. The whole of it is in the tooltip. */
.verdict-mark {
  display: inline-flex;
  align-items: center;
  color: var(--muted);
  opacity: 0.55;
}

.verdict-mark.good {
  color: var(--good);
  opacity: 0.5;
}

/* Not subtle, these two. A message that failed its checks is the one thing
   in a list of names worth interrupting for. */
.verdict-mark.warn {
  color: var(--warn);
  opacity: 1;
}

.verdict-mark.bad {
  color: var(--bad);
  opacity: 1;
}

/* The same answer in words, on the message itself, where there is room. */
.verdict-detail.warn {
  color: var(--warn);
}

.verdict-detail.bad {
  color: var(--bad);
}
.mailbox-row input[type='checkbox'] {
  margin: 3px 0 0;
}
.mailbox-star {
  background: none;
  border: 0;
  padding: 0;
  margin: 0;
  color: var(--muted);
  font-size: 15px;
  line-height: 1;
  cursor: pointer;
}
.mailbox-star.on {
  color: #d99a1b;
}
/* The strip across the bottom of a list: which of its rows are shown, and
   the ways to its other pages. Named for the shape rather than for the mailbox
   it was first written for, the same way the strip across the top is, since
   the knowledge navigator ends the same kind of list and a page should not
   have to wear another page's name to be drawn like it. */
.list-foot {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 8px;
  padding: 6px 10px;
  border-top: 1px solid var(--border);
  font-size: 12px;
  color: var(--muted);
}
.mailbox-pane {
  padding: 12px 16px;
  min-width: 0;
  overflow-x: auto;
}
.mailbox-pane-head {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 12px;
  align-items: baseline;
  margin-bottom: 8px;
}
.mailbox-pane-head h2 {
  margin: 0;
  font-size: 18px;
  flex: 1 1 100%;
  text-wrap: balance;
}
/* --- our own dropdown --------------------------------------------------------
   A native select's list is drawn by the operating system, in the operating
   system's colors: on a dark page it opens as a white rectangle. Where a
   control is part of the furniture rather than part of a form to fill in,
   that is worth drawing ourselves. */
.select {
  display: inline-flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  min-width: 160px;
  padding: 7px 10px;
  border: 1px solid var(--border);
  border-radius: var(--radius-2);
  background: var(--surface);
  color: var(--text);
  /* The text size of a field (an input takes the page's 15px, and 16px on a
     phone below), set here rather than inherited from the parent, so a
     dropdown reads the same wherever it sits. */
  font: inherit;
  font-size: 15px;
  text-align: left;
  cursor: pointer;
}

@media (max-width: 760px) {
  .select {
    font-size: 16px;
  }
}

/* A select holding a number needs the width of a number. The 160px above is
   for one holding words — "Every status", "The last seven days" — and on a
   phone it is nearly half the screen, which is how the control that chooses
   how many rows to show came to push "383 messages" and "1-50 of 383" onto
   two lines each. Wide enough for the longest of its options, so choosing a
   different one does not move what is beside it. */
.select.select-number {
  min-width: 0;
  width: 4.75rem;
}

.select:hover:not(:disabled) {
  border-color: var(--border-strong);
}

.select:focus-visible {
  /* The same darker border an input takes, and the ring a button that is not
     a text field needs, so a keyboard can see where it is. */
  outline: 2px solid var(--text);
  outline-offset: 1px;
  border-color: var(--text);
}

/* In a form, the width of an input; the list under it is the same. */
.select.block {
  display: flex;
  width: 100%;
  min-width: 0;
}

.select:disabled {
  opacity: 0.6;
  cursor: default;
}

.select-value.placeholder {
  color: var(--muted);
}

.select-search {
  width: 100%;
  margin: 0 0 4px;
  font-size: 14px;
}

.select-empty {
  padding: 7px 10px;
  font-size: 13px;
}

/* The heading over a group of options, such as the income spending
   categories after the spending ones: smaller than an option and not one. */
.select-group {
  padding: 8px 10px 2px;
  color: var(--muted);
  font-size: var(--text-label);
  font-weight: 600;
}

/* The list under the box that finds a page in the graph. Its rows are the
   select's rows, two lines instead of one: two pages can be called the same
   thing and only the path tells them apart, so the path is drawn under the
   name rather than left to be guessed at. */
/* Named through the list it sits in, because the rule it has to beat —
   the one that makes an ordinary option a single line — is written that
   way too, and a bare class loses to it however late it appears. */
.select-list .page-picker-row {
  display: flex;
  flex-direction: column;
  align-items: stretch;
}

/* Both lines are cut rather than wrapped. A path is one long word with
   nowhere to break, and letting it wrap would make one row as tall as three
   and the list a different height every time it is typed into. */
.page-picker-name,
.page-picker-path {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.page-picker-path {
  color: var(--muted);
  font-size: var(--text-label);
}

.select-value {
  /* Not the small gray of a field's caption: inside a label the caption rule
     (label span) reached the value of a dropdown too, so a dropdown showed
     13px gray text, pushed up by the caption's bottom margin, beside the
     input boxes' own text. */
  color: inherit;
  font-size: inherit;
  margin: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Inside a field's label the value is what was chosen, not the field's name:
   "label span" would draw it small and grey, like the name above it. */
label .select-value {
  display: block;
  margin-bottom: 0;
  color: var(--text);
  font-size: inherit;
}

label .select-value.placeholder {
  color: var(--muted);
}

.select-list {
  max-width: calc(100vw - 16px);
  position: fixed;
  z-index: 60;
  max-height: 320px;
  padding: 4px;
  overflow-y: auto;
  border: 1px solid var(--border);
  border-radius: var(--radius-2);
  background: var(--surface);
  box-shadow: var(--shadow-floating);
}

.select-list button {
  display: block;
  width: 100%;
  min-height: 0;
  padding: 7px 10px;
  border: 0;
  border-radius: var(--radius-1);
  background: transparent;
  color: var(--text);
  font: inherit;
  font-size: 14px;
  text-align: left;
  white-space: nowrap;
  cursor: pointer;
}

/* The row under the pointer and the row the keyboard is on are the same
   thing, so they look the same.

   The page's own background was the wrong surface to mark it with: in light
   it is the same white as the list it is drawn on, so the mark was invisible
   and the arrow keys moved nothing anybody could see. This is the token
   every other floating list here marks a row with. */
.select-list button.active {
  background: var(--sidebar-hover);
}

.select-list button[aria-selected='true'] {
  font-weight: 600;
}

/* --- tooltips ----------------------------------------------------------------
   The dashboard's own, rather than the browser's. A native title waits about
   a second, is drawn in the operating system's colors, and cannot wrap — a
   timestamp with a zone name comes out as one long line in a font nobody
   chose. Rendered into the body and positioned from the anchor's box, so a
   row inside a scrolling list does not clip it. */
.tooltip-anchor {
  display: contents;
}

.tooltip {
  position: fixed;
  z-index: 60;
  transform: translate(-50%, -100%);
  /* Never wider than the window, whatever the label is. */
  max-width: min(280px, calc(100vw - 16px));
  /* An address is one word with no space in it to break at, so a maximum
     width alone did nothing: the box grew to the length of the address and
     ran off the side of the screen. This lets it break mid-word, which for an
     address is the only place there is. */
  overflow-wrap: anywhere;
  padding: 5px 9px;
  border: 1px solid var(--border);
  border-radius: var(--radius-2);
  background: var(--surface);
  box-shadow: var(--shadow-floating);
  color: var(--text);
  font-size: 12px;
  line-height: 1.45;
  text-align: center;
  text-wrap: balance;
  white-space: normal;
  pointer-events: none;
}

/* Under the thing it describes, for something near the top of the window
   where there is no room above it. */
.tooltip.below {
  transform: translate(-50%, 0);
}

/* --- a conversation ---------------------------------------------------------
   The messages of one conversation, newest first. A message that has been
   read is a line; opening it puts the message under that line, so the line
   stays where it was and the reader's place does not move. */
.mailbox-thread {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.mailbox-thread-count {
  margin: 0;
  font-size: 13px;
}

/* The answer being written sits above the newest message, which is where it
   will be once it is sent. Its own card, so it reads as a thing being made
   rather than as another message of the conversation. */
.mailbox-thread-compose {
  margin-bottom: 12px;
}

.mailbox-thread-compose .compose {
  margin-bottom: 0;
}

.mailbox-message {
  border: 1px solid var(--border);
  border-radius: var(--radius-3);
  background: var(--surface);
  overflow: hidden;
}

/* The line that names a message: the part that opens and closes it, and at
   its end the message's own menu, which is a button and so cannot be inside
   another one. */
.mailbox-message-head {
  display: flex;
  align-items: center;
  gap: 4px;
  padding-right: 8px;
}

/* The whole line opens and closes the message, so there is nothing to aim
   at. A button rather than a div, so the keyboard reaches it and a screen
   reader is told what it does. */
.mailbox-message-summary {
  display: flex;
  align-items: baseline;
  gap: 8px;
  flex: 1;
  min-width: 0;
  min-height: 0;
  padding: 10px 12px;
  border: 0;
  border-radius: 0;
  background: transparent;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.mailbox-message-summary:hover {
  background: var(--background);
}

/* The menu keeps its size while the summary beside it takes the room. */
.mailbox-message-menu {
  flex: 0 0 auto;
}

.mailbox-message-menu .message-menu {
  margin: 0;
}

/* The menu on a message's line is a row action, not a control on a form. A
   bordered, filled button on a line 41px tall reads as a slab — and on a
   phone, where every button is 40px tall so a thumb can reach it, it is very
   nearly the whole line. Sized and drawn like the icon buttons it sits among
   instead, with the thumb's worth of room kept as the hit area. */
.mailbox-message-menu .message-menu-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  min-height: 0;
  padding: 0;
  border: 0;
  border-radius: 6px;
  background: transparent;
  color: var(--muted);
  font-size: 16px;
}

.mailbox-message-menu .message-menu-button:hover {
  background: rgba(127, 127, 127, 0.16);
  color: var(--text);
}

@media (max-width: 600px) {
  .mailbox-message-menu .message-menu-button {
    width: 34px;
    height: 34px;
  }
}

.mailbox-message.unread .mailbox-message-who {
  font-weight: 600;
}

.mailbox-message-who {
  flex: 0 0 auto;
  max-width: 40%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 14px;
}

/* The subject stands in for the message while it is closed, and gives way to
   the message itself when it opens. */
.mailbox-message-subject {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 13px;
  color: var(--muted);
}

/* The time a message arrived, and beside it whether it is really from who it
   says. Who it was addressed to is not here: for a mailbox with one address
   that is a line saying what the reader already knows, above every message
   they open. It is in the menu with the rest of the details. */
.mailbox-message-when {
  display: flex;
  align-items: center;
  gap: 5px;
  flex: 0 0 auto;
  margin-left: auto;
  font-size: 12px;
  color: var(--muted);
  white-space: nowrap;
}

/* Padded on every side. It had no padding at the top because the line saying
   who the message was addressed to carried its own, and when that line went
   whatever came first — the details, or the notice about remote images — sat
   against the border. */
.mailbox-message-body {
  padding: 12px;
  border-top: 1px solid var(--border);
}

/* The first thing in the body is spaced by the padding above it, so it does
   not add its own on top. */
.mailbox-message-body > :first-child {
  margin-top: 0;
}

/* An open message is the one being read; the closed ones around it are a
   list, and should not compete with it. */
.mailbox-message.open > .mailbox-message-head {
  background: var(--background);
}

@media (max-width: 600px) {
  /* Three things on one line do not fit. The time keeps its place at the end
     and the subject wraps under the name. */
  .mailbox-message-summary {
    flex-wrap: wrap;
  }

  .mailbox-message-who {
    max-width: none;
    flex: 1 1 auto;
  }

  .mailbox-message-subject {
    flex-basis: 100%;
  }
}

.mailbox-pane-meta {
  font-size: 13px;
  color: var(--muted);
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  /* A label and its value line up on the text, not on the box.
     
     Stretch is the default, and it gives the two cells the same height and
     puts the content at the top of each -- so a value whose first line box
     is taller than the label's has its words sitting lower inside a cell
     that measures identical. Anything in the value can do that: an inline
     button, a different font resolving on a phone, a browser's own metrics.
     It is why this looked right on a desktop and wrong on a phone, and why
     measuring the cells said the rows were aligned when the reader could
     see they were not. Baseline is the alignment the row actually wants. */
  align-items: baseline;
  gap: 2px 10px;
  margin-bottom: 12px;
}
.mailbox-pane-meta dt {
  margin: 0;
}
.mailbox-pane-meta dd {
  margin: 0;
  color: var(--text);
  overflow-wrap: anywhere;
}
/* What the agent worked out is the end of the message's details and not a
   section of its own: the same rows, the same spacing. There was a gap here
   to mark where the headers stop and the agent's reading starts, and it read
   as the list having come apart -- which is the thing merging the two lists
   was meant to stop. Both halves answer what this server knows about this
   message, so they look like one list, because they are one. */

.mailbox-sorting-list {
  margin: 0;
  padding-left: 1.1rem;
}

/* And the button does not grow the line it is on in the first place: it
   takes the line-height of the text around it rather than whatever height
   the browser gives a button, which is not the same on a phone as on a
   desktop. The baseline alignment above makes the row right either way;
   this keeps the row from growing to accommodate a button nobody asked to
   be tall. */
.mailbox-pane-meta button.link {
  line-height: inherit;
  vertical-align: baseline;
}

.mailbox-pane-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-bottom: 12px;
}
.mailbox-pane-actions button {
  font-size: 12px;
  padding: 3px 8px;
}
/* A finger is not a pointer. The box that selects a conversation and the star
   that flags it were a thirteen-pixel column each, which on a phone is a
   target you aim at rather than press. The row they sit in is forty pixels
   tall; the width is what was missing. */
@media (max-width: 760px) {
  .mailbox-row input[type='checkbox'],
  .mailbox-actions input[type='checkbox'] {
    width: 24px;
    height: 24px;
  }
  .mailbox-star {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 28px;
    height: 28px;
    /* Back into the column it sits in, so widening the target does not move
       the subject line across. */
    margin: 0 -4px;
  }
}

/* On a phone the row of actions is one line that scrolls, not two lines that
   stack. Ten icons need four hundred pixels and a phone has three hundred and
   ninety, so they wrapped — and a second row of them pushed the message down
   and left one icon sitting alone under nine. Sideways, the row reads as a
   row, and the ones past the edge are a swipe away. */
@media (max-width: 760px) {
  .mailbox-actions,
  .mailbox-pane-actions {
    flex-wrap: nowrap;
    overflow-x: auto;
    /* No bar drawn over the icons: on a touch screen there is none to draw,
       and the row is visibly cut off at the edge, which is the affordance. */
    scrollbar-width: none;
  }
  .mailbox-actions::-webkit-scrollbar,
  .mailbox-pane-actions::-webkit-scrollbar {
    display: none;
  }
  /* Icons keep their size rather than being squeezed to fit the width. */
  .mailbox-actions > *,
  .mailbox-actions button,
  .mailbox-pane-actions > *,
  .mailbox-pane-actions button,
  .mailbox-pane-actions .menu-button {
    flex: 0 0 auto;
  }
}
.mailbox-placeholder {
  color: var(--muted);
  padding: 40px 16px;
  text-align: center;
}
/* One pane at a time once there is not room for two: the list, or the
   message, with the back link between them. 860 is the width at which the
   list at its narrowest and a message at a readable measure stop fitting
   together. */
@container mailbox (max-width: 860px) {
  .mailbox {
    grid-template-columns: minmax(0, 1fr);
  }
  .mailbox-list {
    border-right: 0;
  }
  .mailbox.reading .mailbox-list {
    display: none;
  }
  .mailbox:not(.reading) .mailbox-pane {
    display: none;
  }
}

/* A browser without container queries keeps the window-sized rule, which is
   what this was before: right on a phone, and no worse than it was on a
   desktop with the rail open. */
@supports not (container-type: inline-size) {
  @media (max-width: 860px) {
    .mailbox {
      grid-template-columns: minmax(0, 1fr);
    }
    .mailbox-list {
      border-right: 0;
    }
    .mailbox.reading .mailbox-list {
      display: none;
    }
    .mailbox:not(.reading) .mailbox-pane {
      display: none;
    }
  }
}

/* On a phone the mailbox is not a panel on a page, it is the page. Four
   frames were drawn between the screen's edge and the message inside it —
   the page's padding, the mailbox's border, the pane's padding, the
   message's own box — and each one took a bite out of a width that had none
   to spare: a message rendered in 302px of a 390px screen.
   
   So the outer frames go. The mailbox runs to the edges, the pane keeps
   enough padding to hold a message off the glass, and the message keeps its
   own border, because the messages in a thread are separate things and that
   border is what says so. */
@media (max-width: 600px) {
  .mailbox {
    margin-left: calc(var(--space-3) * -1);
    margin-right: calc(var(--space-3) * -1);
    border-left: none;
    border-right: none;
    border-radius: 0;
  }

  .mailbox-pane {
    padding: 12px 8px;
  }

  .mailbox-message-body {
    padding: 10px 8px;
  }

  /* And the actions wrap instead of scrolling. Sideways scrolling on a row
     of icons is a gesture nobody makes on a row of icons: nine actions in a
     360px pane put 111px of them past the edge, the overflow menu among
     them, reachable only by dragging a toolbar that does not look draggable.
     A second line costs one row of height and hides nothing. */
  .mailbox-pane-actions {
    flex-wrap: wrap;
    overflow-x: visible;
  }
}
.mailbox-actions select,
.mailbox-pane-actions select {
  width: auto;
  flex: 0 0 auto;
}

/* A rule in the mailbox settings: its name and switches on one line, then
   each condition and each action on a line of its own. */
.rule h4 {
  margin: 14px 0 6px;
  font-size: 13px;
}
.rule-head {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 16px;
  align-items: center;
}
.rule-head > input {
  flex: 1 1 200px;
  min-width: 0;
}
.rule-head .row-actions {
  margin-left: auto;
  display: flex;
  gap: 10px;
}
.rule-line {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  align-items: center;
  margin-bottom: 6px;
}
.rule-line select,
.rule-line input {
  width: auto;
  flex: 0 1 auto;
  min-width: 0;
}
.rule-line input[type='text'],
.rule-line input:not([type]),
.rule-line input[type='email'] {
  flex: 1 1 160px;
}
.rule .check {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin: 0;
  white-space: nowrap;
}
.compose-editor-switch {
  margin: 12px 0 6px;
}
/* What is attached to what is being written, as a row of chips rather than a
   list down the page. A stack of one file per line pushes the buttons that
   send the message off the bottom of a composer that is already tall, and
   three files are three words. */
.attachments-kept ul,
.attachments ul,
.uploads {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  list-style: none;
  margin: 6px 0;
  padding: 0;
}

.attachments-kept li,
.attachments ul li,
.uploads li {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  max-width: 320px;
  padding: 4px 6px 4px 10px;
  border: 1px solid var(--border);
  border-radius: var(--radius-2);
  background: var(--surface);
  font-size: 12px;
}

/* The name is what is looked for, so it takes the room and it is what gets
   cut when there is not enough. */
.attachments-kept li > :first-child,
.attachments ul li > :first-child,
.uploads .upload-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Taking one off is a mark on the chip, not a word beside it. */
.attachments-kept li button,
.attachments ul li button {
  display: inline-flex;
  padding: 2px 4px;
  border: none;
  border-radius: var(--radius-1);
  background: none;
  color: var(--muted);
  font: inherit;
  font-size: 12px;
  cursor: pointer;
}

.attachments-kept li button:hover,
.attachments ul li button:hover {
  color: var(--bad);
}

.uploads li.failed {
  border-color: var(--bad);
}

.uploads progress {
  width: 60px;
}

/* Sign in through an identity provider: one button per provider, under the
   password form and the passkey button, the same width as them. */
.sso-buttons {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-top: 12px;
}
.sso-buttons .button {
  display: block;
  text-align: center;
}
.mailbox-row-link {
  color: inherit;
  text-decoration: none;
  min-width: 0;
  display: block;
}
.mailbox-row-link:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: 4px;
}

/* Narrowing a search: the fields under the search box, shown on request. */
.mailbox-narrowing {
  flex: 1 1 100%;
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 8px 0 2px;
  border-top: 1px solid var(--border);
  margin-top: 4px;
  font-size: 12px;
}
.mailbox-narrowing label {
  margin: 0;
}
.mailbox-narrowing label > span {
  display: block;
  color: var(--muted);
  margin-bottom: 2px;
}
.mailbox-narrowing input,
.mailbox-narrowing select {
  font-size: 12px;
  width: 100%;
}
.mailbox-narrowing .check {
  display: flex;
  align-items: center;
  gap: 6px;
}
.mailbox-narrowing .check input {
  width: auto;
}
.mailbox-narrowing-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 6px;
}
.mailbox-narrowing-actions {
  display: flex;
  gap: 6px;
  margin-top: 4px;
}
.mailbox-narrowing-actions button {
  font-size: 12px;
  padding: 4px 10px;
}
/* How many messages the conversation holds, beside who wrote them. A number
   rather than a word: the row is already carrying a name, a subject and a
   time, and "3 messages" would crowd all three out on a phone. */
.mailbox-row-count {
  margin-left: 6px;
  padding: 0 5px;
  border: 1px solid var(--border);
  border-radius: 8px;
  font-size: 11px;
  font-weight: 500;
  font-variant-numeric: tabular-nums;
  color: var(--muted);
}

/* An answer begun and left. Said in the list, because the conversation looks
   finished without it and the half-written reply is two folders away. */
.mailbox-row-draft {
  margin-left: 6px;
  padding: 0 5px;
  border-radius: 8px;
  background: var(--background);
  font-size: 11px;
  font-weight: 500;
  color: var(--warn);
}

/* The agent's help with a reply: a line to say what it should do, and the
   button that asks. Above the editor, because that is where the answer
   lands. */
.compose-agent {
  display: flex;
  gap: 8px;
  margin: 8px 0;
}

.compose-agent input {
  flex: 1;
  min-width: 0;
}

.compose-agent button {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  white-space: nowrap;
}

/* The agent page's lists of memories and schedules, and the drawer's task
   list. */
.agent-memory-list {
  list-style: none;
  margin: 0 0 12px;
  padding: 0;
}

.agent-memory-list li {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  padding: 8px 0;
  border-top: 1px solid var(--border);
}

.agent-memory-list li p {
  margin: 4px 0 0;
}

.agent-memory-list .mailbox-row-chip {
  margin-left: 6px;
}

.agent-memory-form {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.row.wrap {
  flex-wrap: wrap;
}

/* The agent's task list, between the transcript and the box the person
   types in: read, not changed. One line an item, a long one cut with an
   ellipsis and whole in its tooltip; the list scrolls when it is long. */
.agent-drawer-todo {
  flex: none;
  padding: 6px 12px;
  border-top: 1px solid var(--border);
  font-size: 12.5px;
}

.agent-drawer-todo ul {
  list-style: none;
  margin: 0;
  padding: 0;
  max-height: 120px;
  overflow-y: auto;
}

.agent-drawer-todo li {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  line-height: 20px;
}

/* The mark sits at the top of its line: a ring for a step still open, a
   tick in a filled ring for one done. */
.agent-drawer-todo-mark {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 14px;
  height: 14px;
  margin-top: 3px;
  border: 1.5px solid var(--muted);
  border-radius: 50%;
  color: var(--background);
}

.agent-drawer-todo li.done .agent-drawer-todo-mark {
  border-color: var(--muted);
  background: var(--muted);
}

.agent-drawer-todo-text {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.agent-drawer-todo li.done .agent-drawer-todo-text {
  color: var(--muted);
  text-decoration: line-through;
}

/* A cut step opens to its whole text on a tap or Enter, and closes again. */
.agent-drawer-todo-text[role='button'] {
  cursor: pointer;
}

.agent-drawer-todo li.open .agent-drawer-todo-text {
  white-space: normal;
  overflow-wrap: anywhere;
}

/* The marks in the head for what of the person's own is attached. */
/* The day's budget as a ring in the drawer's head, and as a bar on the
   agent's page. One colour rule for both, in the tokens' own words. */
.agent-drawer-budget {
  color: var(--muted);
}

.agent-budget-track {
  fill: none;
  stroke: var(--border);
  stroke-width: 2.5;
}

.agent-budget-fill {
  fill: none;
  stroke-width: 2.5;
  stroke-linecap: round;
  transition: stroke-dasharray 200ms ease;
}
.agent-budget-fill.good {
  stroke: var(--good);
}
.agent-budget-fill.warn {
  stroke: var(--warn);
}
.agent-budget-fill.bad {
  stroke: var(--bad);
}

.agent-budget {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin: 8px 0 10px;
  max-width: 420px;
}

.agent-budget-said {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  font-size: 12.5px;
}

.agent-budget-bar {
  position: relative;
  height: 6px;
  border-radius: 3px;
  background: var(--border);
  overflow: hidden;
}

/* Positioned so it is drawn over the forecast band, which starts a little
   under its rounded end so no gap shows where the two meet. */
.agent-budget-bar-fill {
  position: relative;
  display: block;
  height: 100%;
  border-radius: 3px;
  transition: width 200ms ease;
}

/* Where the month is heading, past what has gone: faint stripes in the
   fill's own color, ending in a firmer edge so the end can be read. A
   band heading over the end of the bar ends in a solid edge instead. */
.agent-budget-bar-band {
  --band-color: var(--good);
  position: absolute;
  top: 0;
  bottom: 0;
  background: repeating-linear-gradient(
    -45deg,
    color-mix(in srgb, var(--band-color) 40%, transparent) 0 2px,
    color-mix(in srgb, var(--band-color) 18%, transparent) 2px 4px
  );
  box-shadow: inset -2px 0 0 color-mix(in srgb, var(--band-color) 75%, transparent);
  transition:
    left 200ms ease,
    width 200ms ease;
}
.agent-budget-bar-band.warn {
  --band-color: var(--warn);
}
.agent-budget-bar-band.bad {
  --band-color: var(--bad);
}
.agent-budget-bar-band.over {
  box-shadow: inset -4px 0 0 var(--band-color);
}
.agent-budget-bar-fill.good {
  background: var(--good);
}
.agent-budget-bar-fill.warn {
  background: var(--warn);
}
.agent-budget-bar-fill.bad {
  background: var(--bad);
}

/* A filter the page applied from outside the table, shown as a chip
   among the table's tools with its own clearing. */
.table-pinned {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 2px 4px 2px 10px;
  border: 1px solid var(--border);
  border-radius: 999px;
  font-size: 0.85em;
}

/* The reading's progress in the bootstrap row: the bar, the count and
   the guess, stacked, where the row's subtitle goes. */
.agent-reading {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  min-width: 0;
  padding-block: var(--space-1);
}

/* The bar and the share it has reached, on one line, so the number is
   read with the bar rather than hunted for in the sentence below it. */
.agent-reading-track {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-width: 0;
}

.agent-reading .agent-budget-bar {
  flex: 1 1 auto;
  min-width: 0;
  max-width: 28rem;
  height: 8px;
  border-radius: 4px;
}

.agent-reading .agent-budget-bar-fill {
  border-radius: 4px;
}

.agent-reading-percent {
  flex: none;
  font-variant-numeric: tabular-nums;
  font-size: var(--text-label);
  color: var(--muted);
}

/* The files a night decided against opening, under the source that
   carried them: the name, and the reason on its own line so a long one
   does not push the name off a phone. */
.dialog-scroll {
  max-height: min(60vh, 520px);
  overflow-y: auto;
}

.agent-declined-files {
  list-style: none;
  margin: var(--space-1) 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

.agent-declined-files li {
  display: flex;
  flex-direction: column;
  min-width: 0;
  overflow-wrap: anywhere;
}

/* A reason, then the files it was given for. Written with the list in
   front of them: the rule above is `.agent-declined-files li`, which
   matches these too and is worth exactly as much, so a plain
   `.agent-declined-group > ul > li` lost on being written first and every
   file stood in its own column with nothing marking the group. */
.agent-declined-files .agent-declined-group > p {
  margin: var(--space-3) 0 var(--space-1);
}

.agent-declined-files .agent-declined-group > ul {
  list-style: none;
  margin: 0;
  padding: 0 0 0 var(--space-3);
  border-left: 1px solid var(--border);
}

.agent-declined-files .agent-declined-group > ul > li {
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  gap: var(--space-2);
  align-items: baseline;
  padding: 2px 0;
}

.agent-declined-files .muted {
  font-size: var(--text-label);
}

.agent-drawer-device {
  color: var(--muted);
}

.agent-drawer-tab {
  padding: 4px 12px;
  border-bottom: 1px solid var(--border);
  font-size: 12px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The agent's drawer: a panel on the right of every page, and the button
   that opens it when it is closed. */
/* The button that opens the drawer floats over everything, so everything
   needs somewhere to end that is not under it.
   It is 44 pixels tall, 20 from the bottom and 20 from the right, and on a
   narrow window it sat on the last row of whatever was being read -- the
   aliases of the last domain, the "sign this out" of the last session --
   which could then not be pressed at all. Not hidden: a row a person can
   see and cannot reach is worse than one they have to scroll to. So the
   scrolling area ends 84 pixels above its own bottom on a narrow window,
   which is the button, its margin, and a little air. */
.agent-drawer-toggle {
  position: fixed;
  right: 20px;
  bottom: calc(20px + env(safe-area-inset-bottom));
  z-index: 30;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  border: 1px solid var(--border);
  border-radius: 22px;
  background: var(--background);
  color: var(--accent, inherit);
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
  cursor: pointer;
}

/* The drawer floats over the page at the corner where its button was: a
   panel the page keeps its own shape under, rather than a column that
   squeezes the reader. On a phone it takes the screen. */
/* The drawer as a page of its own, framed by the browser extension: it is
   the frame, so nothing floats and nothing is rounded. */
/* The drawer as the whole of its own page, which is how the browser
   extension's panel frames it. It fills the frame, whatever size the
   person has dragged the panel to: stretched rather than centred, because
   a centred drawer of its own fixed height left a band of empty page
   under the composer. */
.drawer-page {
  height: 100vh;
  height: 100dvh;
  display: flex;
  align-items: stretch;
  justify-content: stretch;
  background: var(--background);
}
.agent-drawer.standalone {
  position: static;
  flex: 1 1 auto;
  width: 100%;
  height: 100%;
  max-height: 100%;
  border: 0;
  border-radius: 0;
  box-shadow: none;
}

/* A thin scrollbar inside the drawer. The default one is as wide as a
   scrollbar on a page, and the drawer is a column of 440 pixels. */
.agent-drawer {
  scrollbar-width: thin;
  scrollbar-color: var(--border) transparent;
}

.agent-drawer *::-webkit-scrollbar {
  width: 8px;
  height: 8px;
}

.agent-drawer *::-webkit-scrollbar-track {
  background: transparent;
}

.agent-drawer *::-webkit-scrollbar-thumb {
  border-radius: 4px;
  background: var(--border);
}

.agent-drawer *::-webkit-scrollbar-thumb:hover {
  background: var(--muted);
}

.agent-drawer {
  position: fixed;
  right: 20px;
  bottom: 20px;
  z-index: 30;
  display: flex;
  flex-direction: column;
  width: min(440px, calc(100vw - 40px));
  height: min(760px, calc(100vh - 40px));
  height: min(760px, calc(100dvh - 40px));
  border: 1px solid var(--border);
  border-radius: 14px;
  background: var(--background);
  box-shadow: 0 12px 40px rgba(0, 0, 0, 0.22);
  overflow: hidden;
}

@media (max-width: 720px) {
  .agent-drawer {
    inset: 0;
    width: 100vw;
    height: 100vh;
    height: 100dvh;
    border: 0;
    border-radius: 0;
  }
}

/* On a wide window the box can be moved by its header and resized by its
   edges. Where the person left it is written onto the element as custom
   properties, and only this query reads them: on a phone the box takes
   the screen whatever was remembered, with nothing to move. */
.agent-drawer-grip {
  display: none;
}

@media (min-width: 721px) {
  .agent-drawer.placed:not(.standalone) {
    top: var(--agent-drawer-top);
    left: var(--agent-drawer-left);
    right: auto;
    bottom: auto;
    width: var(--agent-drawer-width);
    height: var(--agent-drawer-height);
  }

  .agent-drawer-head.movable {
    cursor: move;
    touch-action: none;
  }

  .agent-drawer.repositioning {
    user-select: none;
  }

  .agent-drawer.repositioning iframe {
    pointer-events: none;
  }

  .agent-drawer-grip {
    display: block;
    position: absolute;
    z-index: 6;
    touch-action: none;
  }

  .agent-drawer-grip.top,
  .agent-drawer-grip.bottom {
    left: 20px;
    right: 20px;
    height: 6px;
    cursor: ns-resize;
  }

  .agent-drawer-grip.left,
  .agent-drawer-grip.right {
    top: 20px;
    bottom: 20px;
    width: 6px;
    cursor: ew-resize;
  }

  /* Larger than the corner's rounding, which clips what can be pressed:
     at the rounding's size a corner grip was almost all outside the box. */
  .agent-drawer-grip.top-left,
  .agent-drawer-grip.top-right,
  .agent-drawer-grip.bottom-left,
  .agent-drawer-grip.bottom-right {
    width: 20px;
    height: 20px;
  }

  .agent-drawer-grip.top,
  .agent-drawer-grip.top-left,
  .agent-drawer-grip.top-right {
    top: 0;
  }

  .agent-drawer-grip.bottom,
  .agent-drawer-grip.bottom-left,
  .agent-drawer-grip.bottom-right {
    bottom: 0;
  }

  .agent-drawer-grip.left,
  .agent-drawer-grip.top-left,
  .agent-drawer-grip.bottom-left {
    left: 0;
  }

  .agent-drawer-grip.right,
  .agent-drawer-grip.top-right,
  .agent-drawer-grip.bottom-right {
    right: 0;
  }

  .agent-drawer-grip.top-left,
  .agent-drawer-grip.bottom-right {
    cursor: nwse-resize;
  }

  .agent-drawer-grip.top-right,
  .agent-drawer-grip.bottom-left {
    cursor: nesw-resize;
  }
}

.agent-drawer-head {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 10px;
  border-bottom: 1px solid var(--border);
}

.agent-drawer-conversation {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  flex: 1;
  min-width: 0;
  padding: 4px 6px;
  border: 0;
  background: none;
  font: inherit;
  font-weight: 600;
  color: inherit;
  text-align: left;
  cursor: pointer;
}

.agent-drawer-title {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  /* The title is the one thing on this row that gives way.
     
     Without it the span's min-width is auto, which for a line that may not
     wrap is the width of the whole title -- so a long one does not shrink,
     the row overflows, and the only items left that can give are the two
     icons. The spark beside the title was squeezed to a sliver, which is
     what the marks either side of a conversation's name were. It came back
     on reopening because the title it reopened with was shorter. */
  min-width: 0;
}

/* And the icons in that row never give way: they are 14px squares, and a
   square with a size and nothing said about shrinking is still a flex item
   that may be shrunk. Belt and braces with the line above -- with the title
   shrinking the row no longer overflows, and if anything ever makes it
   overflow again, the text is what goes. */
.agent-drawer-conversation > svg {
  flex: 0 0 auto;
}

/* The goal on the head of the drawer: one icon whatever the state,
   because the head has the conversation's name, the attached marks and
   the budget ring on it and no room for words. The colour is the state
   -- muted with none, the accent while the agent works, the warning
   colour while it waits for the person, muted again once the goal is
   met -- and the dialog behind it says the rest. */
.agent-drawer-goal {
  flex: 0 0 auto;
  color: var(--muted);
}

.agent-drawer-goal.working {
  color: var(--accent);
}

.agent-drawer-goal.waiting {
  color: var(--warn);
}

.agent-drawer-goal.met {
  color: var(--muted);
}

/* A check-in line is a button, and the words the turn was given open
   under it in the transcript's own type, wrapped, never wider than the
   drawer. */
.agent-checkin-toggle {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  cursor: pointer;
}

.agent-checkin-toggle:hover {
  color: var(--text);
}

.agent-checkin-prompt {
  margin: var(--space-2) 0 0;
  padding: var(--space-2) var(--space-3);
  border-left: 2px solid var(--border);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  font: inherit;
  font-size: var(--text-label);
}

/* The drawer's mark for what this chat left running on a computer: a
   terminal whose cursor blinks while anything still runs. */
.agent-drawer-background.running {
  color: var(--accent);
}

.agent-drawer-background.running .terminal-icon-cursor {
  animation: terminal-cursor-blink 1.1s steps(1, end) infinite;
}

@keyframes terminal-cursor-blink {
  50% {
    opacity: 0;
  }
}

@media (prefers-reduced-motion: reduce) {
  .agent-drawer-background.running .terminal-icon-cursor {
    animation: none;
  }
}

/* The head's dropdowns share the frame of the list of conversations
   (.agent-drawer-list): a small muted title, then a line per thing, or a
   short form. */
.head-menu-title {
  display: flex;
  align-items: center;
  min-height: 28px;
  padding: 0 8px;
  flex-shrink: 0;
}

.head-menu-title strong {
  color: var(--muted);
  font-size: 12px;
}

.head-menu-row {
  display: flex;
  align-items: center;
  gap: 8px;
  min-height: 32px;
  padding: 0 8px;
  flex-shrink: 0;
  white-space: nowrap;
}

.head-menu-row svg {
  flex: none;
  color: var(--muted);
}

.head-menu-row-name {
  flex: none;
  max-width: 60%;
  overflow: hidden;
  text-overflow: ellipsis;
  font-weight: 550;
}

.head-menu-row-detail {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  font-size: 12px;
}

.head-menu-usage {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 4px 8px 6px;
}

.head-menu-usage-said {
  display: flex;
  justify-content: space-between;
  gap: 8px;
}

.head-menu-usage p {
  margin: 0;
  font-size: 12px;
}

.head-menu-actions {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
}

/* The background commands, dropped down from the head as the list of
   conversations is (.agent-drawer-list gives the frame): a small title,
   then the rows or one command's output. */
.background-menu {
  gap: 0;
}

.background-menu-head {
  display: flex;
  align-items: center;
  gap: 6px;
  min-height: 32px;
  padding: 2px 6px 6px;
  flex-shrink: 0;
}

.background-menu-head strong {
  font-size: 12px;
  color: var(--muted);
}

.background-menu-empty {
  margin: 4px 8px 8px;
}


.background-menu-head .background-stop {
  margin-left: auto;
}

/* Stop: a small red button with the stop square, not a link, because it
   ends something running on the person's machine. */
.background-stop {
  display: inline-flex;
  flex: none;
  align-items: center;
  gap: 4px;
  padding: 3px 9px;
  border: 1px solid color-mix(in srgb, var(--bad) 45%, transparent);
  border-radius: 999px;
  background: none;
  color: var(--bad);
  font-size: 12px;
  font-weight: 600;
  line-height: 1.4;
  cursor: pointer;
}

.background-stop:hover:not(:disabled) {
  background: color-mix(in srgb, var(--bad) 10%, transparent);
}

.background-stop:disabled {
  opacity: 0.6;
  cursor: default;
}

/* One command's output fills the dropdown and scrolls within itself: the
   title with back and Stop, and the command above it, stay where they are
   however much it printed. */
.background-menu.showing-output {
  height: min(60vh, 420px);
  overflow: hidden;
}

.background-menu.showing-output .background-output {
  display: flex;
  flex: 1;
  flex-direction: column;
  min-height: 0;
}

.background-menu.showing-output .background-output-stream {
  display: flex;
  flex: 1 1 0;
  flex-direction: column;
  min-height: 0;
}

.background-menu.showing-output .background-output-text {
  flex: 1;
  min-height: 48px;
  max-height: none;
}

/* The list: rows divided by a rule rather than a card each, the command
   first in the code face, two lines at most with the whole of it on
   hover, and how it stands under it in small muted type. */
.background-commands {
  margin: 0;
  padding: 0;
  list-style: none;
}

.background-command {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  border-bottom: 1px solid var(--border);
}

.background-command:last-child {
  border-bottom: none;
}

.background-command-open {
  display: flex;
  flex: 1;
  flex-direction: column;
  align-items: stretch;
  gap: 4px;
  min-width: 0;
  padding: 10px 6px;
  border: none;
  border-radius: var(--radius-1);
  background: none;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.background-command-open:hover {
  background: var(--sidebar-hover);
}

/* The drawer's rows: one line each, the state and then the command. */
.background-commands.compact .background-command-open {
  flex-direction: row;
  align-items: center;
  gap: 8px;
  padding: 7px 6px;
}

.background-commands.compact .background-command-open .tag {
  flex: none;
  padding: 0 6px;
  font-size: 11px;
}

.background-commands.compact .background-command-line {
  flex: 1;
  min-width: 0;
}

.background-command-line {
  display: block;
  overflow: hidden;
  font-size: 12px;
  line-height: 1.5;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.background-command-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 2px 8px;
  margin: 0;
  font-size: 12px;
}

.background-command-meta .mono {
  font-size: inherit;
}

.background-command-meta .tag {
  padding: 0 6px;
  font-size: 11px;
}


/* The whole command, however long: a command can be pages of pipeline,
   so it scrolls in a box of its own rather than pushing the output out of
   the list. */
.background-output-command {
  display: block;
  flex-shrink: 0;
  max-height: 5.5em;
  margin: 0;
  padding: 6px 10px;
  overflow-y: auto;
  border: 1px solid var(--border);
  border-radius: var(--radius-1);
  background: var(--field);
  font-size: 12px;
  line-height: 1.5;
  white-space: pre-wrap;
  word-break: break-all;
}

.background-menu .background-output {
  padding: 4px 6px 8px;
}

/* The output view: how it stands, then the command, then what it
   printed, each under a small heading with room between them. */
.background-output {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.background-output-section h4 {
  margin: 0 0 6px;
  color: var(--muted);
  font-size: 12px;
  font-weight: 600;
}

.background-output-section > p {
  margin: 0;
}

.background-output-truncated {
  margin: 0 0 var(--space-1);
  font-size: var(--text-label);
}

/* What a command printed, wrapped rather than scrolled sideways, and
   scrolled within itself so a long log does not run off the list. */
.background-output-text {
  max-height: min(36vh, 260px);
  margin: 0;
  padding: var(--space-2) var(--space-3);
  overflow-y: auto;
  border: 1px solid var(--border);
  border-radius: var(--radius-1);
  background: var(--field);
  font-size: 12px;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

/* What a survey or a subagent came back with: text to read, drawn as
   the transcript draws it, and scrolled within itself so a long report
   leaves the runs and the close button in view. */
.background-work-result {
  max-height: min(56vh, 520px);
  padding: var(--space-2) var(--space-3);
  overflow-y: auto;
  border: 1px solid var(--border);
  border-radius: var(--radius-1);
  background: var(--field);
  overflow-wrap: anywhere;
}

.background-work-runs {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1) var(--space-3);
}

/* The conversations, hanging from the title like a menu: a row each, the
   open one marked, its actions appearing under the pointer, and a first
   row for starting another. */
.agent-drawer-backdrop {
  position: absolute;
  inset: 0;
  z-index: 4;
}

.agent-drawer-list {
  position: absolute;
  top: 44px;
  left: 8px;
  right: 8px;
  z-index: 5;
  display: flex;
  flex-direction: column;
  max-height: min(60vh, 420px);
  overflow-y: auto;
  padding: 6px;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--background);
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.18);
  font-size: 13px;
}

.agent-drawer-list-row {
  display: flex;
  align-items: center;
  gap: 6px;
  min-height: 32px;
  padding: 0 4px 0 8px;
  border-radius: 6px;
  color: var(--text);
  /* The list is a column that scrolls, so a row must keep its own height
     rather than be squeezed to fit: a row carrying both a title and a
     summary is two lines tall, and shrinking it ran the two together. */
  flex-shrink: 0;
}

.agent-drawer-list-row:hover,
.agent-drawer-list-row:focus-within {
  background: var(--sidebar-hover);
}

.agent-drawer-list-row.active {
  font-weight: 600;
}

.agent-drawer-list-row.more {
  border: 0;
  background: none;
  font: inherit;
  color: var(--muted);
  cursor: pointer;
  margin-top: 4px;
  border-top: 1px solid var(--border);
  border-radius: 0 0 6px 6px;
}

.agent-drawer-list-row.more:hover {
  color: var(--text);
}

.agent-drawer-list-heading {
  flex-shrink: 0;
  margin-top: 6px;
  padding: 6px 8px 2px;
  border-top: 1px solid var(--border);
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.agent-drawer-list-row.archived .agent-drawer-list-title {
  color: var(--muted);
}

.agent-drawer-list-title {
  /* A title and, under it, the summary: two lines that each keep to
     themselves. The name and the summary do their own trimming, so the
     button trims nothing. */
  display: flex;
  flex-direction: column;
  gap: 1px;
  flex: 1;
  min-width: 0;
  padding: 6px 0;
  border: 0;
  background: none;
  font: inherit;
  color: inherit;
  text-align: left;
  cursor: pointer;
  overflow: hidden;
}

.agent-drawer-list-name {
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.agent-drawer-list-actions {
  display: inline-flex;
  gap: 2px;
  opacity: 0;
  transition: opacity 120ms ease;
}

.agent-drawer-list-row:hover .agent-drawer-list-actions,
.agent-drawer-list-row:focus-within .agent-drawer-list-actions {
  opacity: 1;
}

.agent-drawer-list-actions .icon-button {
  width: 28px;
  height: 28px;
}

.agent-drawer-rename {
  flex: 1;
  min-width: 0;
}

/* Sideways it clips rather than scrolls: a message swiped across must not
   make the conversation wider than the drawer. */
.agent-drawer-transcript {
  flex: 1;
  overflow-x: clip;
  overflow-y: auto;
  padding: 12px;
  display: flex;
  flex-direction: column;
  gap: 8px;
  font-size: 13.5px;
  line-height: 1.5;
}

/* The way back to what the drawer did not load: one line at the top of
   the transcript, styled as the divider is, gone once everything is in. */
.agent-drawer-earlier {
  align-self: center;
  font-size: 0.85em;
  padding: 4px 10px;
}

.agent-drawer-empty {
  margin: auto 0;
  text-align: center;
}

.agent-line {
  max-width: 100%;
  border-radius: 10px;
  padding: 8px 10px;
  white-space: pre-wrap;
  word-break: break-word;
}

.agent-line.user {
  align-self: flex-end;
  max-width: 85%;
  background: var(--accent, var(--field));
  color: var(--background);
}

.agent-line.assistant {
  /* As wide as what it says, and no wider. Stretched, a two-word answer was
     a bubble the width of the drawer with two words adrift at the left of
     it, which reads as something having gone missing. Not capped at 85% the
     way the person's own messages are, though: an answer carrying a table,
     a code block or a picture should have the whole width when it needs it,
     and fit-content gives it only when it does. */
  align-self: flex-start;
  max-width: 100%;
  /* A wash of the text color over the page rather than --field: in the dark
     theme --field is one shade off the page (#18181b on #141416), so the
     bubbles of the agent and of the other people in a chat all but vanished
     into the drawer. Derived from the two tokens, so each theme gets its own
     contrast and light stays close to what it was. */
  background: color-mix(in srgb, var(--text) 8%, var(--background));
  white-space: normal;
}

.agent-line.assistant .markdown > :first-child {
  margin-top: 0;
}

/* A displayed formula in an answer: on a line of its own, and scrolled
   sideways when it is wider than the drawer rather than widening it. */
.markdown-formula {
  margin: var(--space-2) 0;
  overflow-x: auto;
  overflow-y: hidden;
}

/* The math fonts each system ships, named, since the generic math family
   finds none on some: without one a sum sign stays the size of a letter and
   brackets do not stretch. */
.markdown math {
  font-family:
    'STIX Two Math', 'Cambria Math', 'Noto Sans Math', 'Latin Modern Math', 'DejaVu Math TeX Gyre',
    'TeX Gyre Pagella Math', math;
  font-size: 1.1em;
}

.markdown-formula math {
  margin: 0;
}

.markdown mtd {
  padding: 0.2em 0.5em;
}

.markdown mtd.formula-aligned {
  padding: 0.2em 0;
}

.markdown mtd.formula-left {
  text-align: left;
}

.markdown mtd.formula-right {
  text-align: right;
}

/* A picture in an answer: on a line of its own, under the words it goes
   with, at most the width of the line and a sensible height, so a tall
   product shot does not push the answer off the screen. Beside the words
   it squeezed them into a column on a phone. */
.markdown-picture {
  display: block;
  width: fit-content;
  margin: 6px 0;
}

.markdown-picture img {
  display: block;
  max-width: min(100%, 320px);
  max-height: 240px;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: var(--surface);
  object-fit: contain;
}

.agent-line.assistant .markdown > :last-child {
  margin-bottom: 0;
}

.agent-line.tool,
.agent-line.note {
  padding: 0 4px;
  font-size: 12px;
  color: var(--muted);
}

/* A turn the agent took toward the goal on its own. The message that
   opens it is framing written for the model, not a person's words, so it
   is one quiet line where their bubble would be. */
.agent-line.checkin {
  /* A column: the line, and under it, when opened, the words the turn
     was given. In a row the words squeezed the line into a strip. */
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 4px;
  padding: 0 4px;
  font-size: 12px;
  color: var(--muted);
}

.agent-line.checkin > .agent-checkin-prompt {
  align-self: stretch;
}

.agent-line.confirmation {
  border: 1px solid var(--warn);
  background: var(--field);
}

.agent-line.confirmation p {
  margin: 0 0 8px;
}

.agent-line.confirmation .row {
  display: flex;
  gap: 6px;
}

/* A question's choices, one of which is picked and then submitted: two to
   a row, so a long choice still reads whole. */
.agent-question-choices {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));
  gap: 6px;
}

.agent-question-choices button.chosen {
  border-color: var(--accent);
  color: var(--accent);
}

.agent-question-actions {
  display: flex;
  justify-content: flex-end;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: 8px;
}

.agent-line.error {
  border: 1px solid var(--danger, var(--warn));
  background: var(--field);
  font-size: 12px;
}

/* The replies the last answer offered, just above the box: pressed, one
   is sent as it reads. They wrap rather than scroll, and the longest is
   still read whole. */
/* The replies the last answer offered, under it in the transcript. The
   transcript's own gap and padding space them from the answer and from the
   end; on the box they had none below and sat on its border. */
.agent-suggested-replies {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

.agent-suggested-replies button {
  border-radius: 999px;
  padding: 4px 12px;
  font-size: 13px;
  text-align: left;
  white-space: normal;
}

/* What the agent is waiting for, directly above the box: what it needs
   is read where the person is about to give it, rather than somewhere up
   the transcript they would have to scroll back for. It goes as soon as
   they send. */
.agent-drawer-goal-waiting {
  display: flex;
  align-items: flex-start;
  gap: 6px;
  padding: 8px 12px;
  border-top: 1px solid var(--border);
  background: var(--field);
  font-size: 12px;
  color: var(--muted);
}

.agent-drawer-goal-waiting > svg {
  flex: 0 0 auto;
  margin-top: 2px;
  color: var(--warn);
}

.agent-drawer-input {
  display: flex;
  gap: 8px;
  align-items: flex-end;
  padding: 10px;
  /* Above the iPhone's home indicator when the drawer fills the screen. */
  padding-bottom: calc(10px + env(safe-area-inset-bottom));
  border-top: 1px solid var(--border);
}

/* The bar and the box read as one composer, so the rule between them
   goes the way it does for the files waiting to be sent. */
.agent-drawer-goal-waiting ~ .agent-drawer-input {
  border-top: 0;
}

/* One row, everything in it 36px tall: the paperclip, the box on one
   line, the send mark. The box grows with the words, from the bottom, so
   the marks stay beside its last line. */
.agent-drawer-input textarea {
  flex: 1;
  min-width: 0;
  box-sizing: border-box;
  height: 36px;
  min-height: 36px;
  max-height: 160px;
  padding: 7px 10px;
  font-size: 14px;
  line-height: 20px;
  resize: none;
  overflow-y: auto;
}

.agent-drawer-input .icon-button {
  flex: 0 0 auto;
  width: 36px;
  height: 36px;
}

/* Send and Stop are the size of the paperclip beside them: a round mark
   each, not a word. */
/* The microphone, lit while it listens. */
.agent-drawer-input .agent-voice.is-listening {
  color: var(--bad);
}

/* Voice mode, in the box's place: the meter, what is being heard, and the
   way out. As tall as the box, so the conversation above does not move. */
.agent-drawer-voice-mode {
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: 56px;
  padding: 10px;
  border-top: 1px solid var(--border);
}

.agent-drawer-voice-caption {
  flex: 1;
  min-width: 0;
  margin: 0;
  font-size: 14px;
  line-height: 1.35;
  /* Two lines of what is being heard; a long utterance shows its end. */
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  overflow: hidden;
}

/* Hanging up is the red handset, as on a phone. */
.agent-drawer-voice-mode .agent-drawer-voice-end {
  flex: none;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  background: var(--bad);
  color: #fff;
}

/* A muted microphone is shown plainly: the person must not forget it. */
.agent-drawer-voice-mode .agent-voice-mute[aria-pressed='true'] {
  color: var(--bad);
}

.voice-meter {
  flex: none;
  display: inline-flex;
  align-items: center;
  gap: 3px;
  width: 34px;
  height: 32px;
}

.voice-meter-bar {
  flex: 1;
  height: 100%;
  border-radius: 3px;
  background: var(--muted);
  transform: scaleY(0.15);
  transform-origin: center;
  transition: background-color 0.2s;
  will-change: transform;
}

.voice-meter.is-hearing .voice-meter-bar {
  background: var(--leaf);
}

/* The voice and a quiet button to hear it, side by side at every width:
   the select takes the room, the button only its words, and both stand as
   tall as each other. */
.speech-voice-row {
  display: flex;
  align-items: stretch;
  gap: 8px;
}

.speech-voice-row > label {
  flex: 1;
  min-width: 0;
  margin: 0;
}

.speech-voice-listen {
  flex: none;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 0 12px;
  font-size: 14px;
  white-space: nowrap;
}

/* The meter as a button: plain, and only a button while an answer plays. */
.agent-drawer-voice-mode .agent-drawer-voice-meter {
  flex: none;
  display: inline-flex;
  padding: 4px;
  border: none;
  background: none;
  border-radius: var(--radius-2);
}

.agent-drawer-voice-mode .agent-drawer-voice-meter:disabled {
  opacity: 1;
  cursor: default;
}

.voice-meter.is-speaking .voice-meter-bar {
  background: var(--accent);
}

.agent-drawer-input .agent-send {
  border-radius: 50%;
  background: var(--accent);
  color: var(--accent-text);
}

.agent-drawer-input .agent-send:disabled {
  opacity: 0.4;
  cursor: default;
}

.agent-drawer-input .agent-stop,
.agent-drawer-voice-mode .agent-stop {
  border-radius: 50%;
  border: 1px solid var(--border);
  font-size: 12px;
}

/* What is about to go with the next turn: the threads pointed at and the
   files picked, each removable, above the box. */
.agent-drawer-pending {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 10px 10px 0;
  border-top: 1px solid var(--border);
}

.agent-drawer-pending .agent-references,
.agent-drawer-pending .agent-attachments {
  margin: 0;
}

/* The rule sits above what is picked, so the picked things and the box
   read as one composer. */
.agent-drawer-pending ~ .agent-drawer-input {
  border-top: 0;
}

.agent-attachments,
.agent-references {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: 6px;
}

.agent-line.user .agent-references {
  margin: 0 0 6px;
}

/* The question a late answer answers, or the message a reply answers,
   above it and quieter: either may be far up the conversation by now. A
   long one shows its start. */
.agent-line .agent-in-reply-to {
  margin: 0 0 6px;
  padding-left: 8px;
  border-left: 2px solid currentColor;
  opacity: 0.75;
  font-size: 0.9em;
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* A message that can be replied to or copied. On a touch screen it
   follows a finger swiped across, and the browser keeps the vertical
   scroll; let go, it springs back. */
.agent-line-swipeable {
  position: relative;
  touch-action: pan-y;
  transition: transform 0.18s ease-out;
}

/* While it moves, nothing in it is selected and no callout opens. */
.agent-line-swipeable.swiping {
  transition: none;
  user-select: none;
  -webkit-user-select: none;
  -webkit-touch-callout: none;
}

/* The mark that shows in the room the swipe opens, brighter the nearer
   letting go is to acting: Reply on the right of a line swiped left, Copy
   on the left of one swiped right. */
.agent-swipe-action {
  position: absolute;
  top: 50%;
  display: inline-flex;
  transform: translateY(-50%);
  color: var(--muted);
  pointer-events: none;
}

.agent-swipe-action.reply {
  left: calc(100% + 16px);
}

.agent-swipe-action.copy {
  right: calc(100% + 16px);
}

/* A swipe whose copy the browser refused: the mark becomes a button to
   tap, which the browser does let copy. */
.agent-swipe-action.copy.waiting {
  padding: 8px;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--background);
  color: var(--text);
  pointer-events: auto;
}

/* Copied: the mark, and the button, turn to a green tick for a moment. */
.agent-swipe-action.copied,
.agent-line-actions .icon-action.copied {
  color: var(--good);
}

/* With a mouse, Copy and Reply in the message's corner on hover; reached
   by keyboard as well. Never on a touch screen, where the swipes are. The
   corner sits above the bubble, over its top edge only, so it never covers
   the words of the message it acts on. */
.agent-line-actions {
  position: absolute;
  bottom: calc(100% - 6px);
  right: 6px;
  align-items: center;
  /* Its own width: a short message would otherwise squeeze it to its own. */
  width: max-content;
  white-space: nowrap;
  display: inline-flex;
  gap: 2px;
  padding: 2px;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--background);
  color: var(--muted);
  opacity: 0;
  pointer-events: none;
}

.agent-line-actions .icon-action {
  padding: 2px;
}

/* A bubble on the left keeps the corner at its left end, as the recording's
   passage does: a short message with five buttons grows the corner into the
   room beside it, not past the drawer's edge, where the first button was cut
   off and the person could not react to "OK". */
.agent-line.assistant .agent-line-actions {
  right: auto;
  left: 6px;
}

/* A recording's passage on the left keeps the corner at its left end, over
   the speaker's name: a short passage with many buttons grows the corner into
   the empty space beside it rather than past the conversation's edge, where
   it was cut off. */
.recording-turn:not(.own) .agent-line-actions {
  right: auto;
  left: 6px;
}

.agent-line-actions-label {
  padding: 0 4px;
  font-size: var(--text-label);
  font-variant-numeric: tabular-nums;
}

.agent-line-swipeable:focus-within .agent-line-actions {
  opacity: 1;
  pointer-events: auto;
}

@media (hover: hover) {
  .agent-line-swipeable:hover .agent-line-actions {
    opacity: 1;
    pointer-events: auto;
  }
}

.agent-line-actions .icon-action:hover {
  color: var(--text);
}

/* A finger's tap focuses the bubble and opens its reactions; the corner,
   which would sit on the text under the picker, stays shut on a touch
   screen, where a tap and the swipes do what its buttons do. */
@media (hover: none) {
  .agent-line-swipeable:focus-within .agent-line-actions {
    opacity: 0;
    pointer-events: none;
  }
}

/* The picker opens where the corner is: with it open, the corner goes. */
.agent-line-swipeable:has(.agent-reaction-picker) .agent-line-actions {
  opacity: 0;
  pointer-events: none;
}

/* Reactions on a message: small badges on the bubble's lower corner, the
   outer one, as a chat app draws them. A bubble with any keeps room under
   it so the badges do not sit on the next message. */
.agent-line-swipeable:has(.agent-reactions) {
  margin-bottom: 10px;
}

.agent-reactions {
  position: absolute;
  bottom: -13px;
  display: inline-flex;
  align-items: center;
  gap: 1px;
  padding: 2px 5px;
  border-radius: 999px;
  /* One quiet capsule for all of them, cut out of the bubble by a ring of
     the drawer's own background, as a chat app sets a tapback. */
  background: var(--field);
  box-shadow:
    0 0 0 2px var(--background),
    var(--shadow-raised);
}

.agent-line.user .agent-reactions {
  right: 10px;
}

.agent-line.assistant .agent-reactions {
  left: 10px;
}

.agent-reaction {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 18px;
  height: 18px;
  min-height: 0;
  padding: 0;
  border: 0;
  border-radius: 999px;
  background: none;
  color: var(--text);
  font: inherit;
  font-size: 13px;
  font-weight: normal;
  line-height: 1;
}

/* The person's own, which a click takes away. */
button.agent-reaction {
  cursor: pointer;
}

button.agent-reaction:hover {
  transform: scale(1.15);
}

button.agent-reaction:focus-visible {
  outline: 2px solid var(--leaf);
  outline-offset: 1px;
}

/* The quick reactions, over the message the React button was pressed on,
   kept inside the drawer's width: the person's bubbles are on the right,
   so it opens leftward from there, and the agent's rightward. */
.agent-reaction-picker {
  position: absolute;
  top: -42px;
  z-index: 5;
  display: inline-flex;
  gap: 2px;
  padding: 4px;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--surface);
  box-shadow: var(--shadow-floating);
  white-space: nowrap;
}

.agent-line.user .agent-reaction-picker {
  right: 0;
}

.agent-line.assistant .agent-reaction-picker {
  left: 0;
}

.agent-reaction-choice {
  width: 30px;
  height: 30px;
  min-height: 0;
  padding: 0;
  border: none;
  border-radius: 999px;
  background: none;
  cursor: pointer;
  font: inherit;
  font-size: 17px;
  line-height: 1;
}

.agent-reaction-choice:hover,
.agent-reaction-choice:focus-visible {
  background: var(--field);
}

.agent-reaction-choice.current {
  background: var(--field);
  box-shadow: inset 0 0 0 1px var(--accent);
}

/* The row and the panel of more emoji are siblings in one group, so the
   panel is not a part of the menu and the row's pill does not clip it. The
   group draws nothing of its own. */
.agent-reaction-picker-group {
  display: contents;
}

.agent-reaction-more-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--muted);
}

.agent-reaction-more-button[aria-expanded='true'] {
  background: var(--field);
}

/* Every other emoji, over the message under the row: a box to type or paste
   one, then the common ones by category in a grid that scrolls. It opens
   the way the row does, leftward from the person's bubbles and rightward
   from the agent's, and is never wider than the screen less its gutters. */
.agent-reaction-more {
  position: absolute;
  top: 4px;
  z-index: 6;
  display: flex;
  flex-direction: column;
  gap: 6px;
  width: min(300px, calc(100vw - 32px));
  padding: 8px;
  border: 1px solid var(--border);
  border-radius: 14px;
  background: var(--surface);
  color: var(--text);
  box-shadow: var(--shadow-floating);
  white-space: normal;
  text-align: left;
}

.agent-line.user .agent-reaction-more {
  right: 0;
}

.agent-line.assistant .agent-reaction-more {
  left: 0;
}

.agent-reaction-entry {
  display: flex;
  gap: 6px;
}

.agent-reaction-entry input {
  flex: 1;
  min-width: 0;
  /* 16px keeps a phone from zooming in on the box. */
  font-size: 16px;
}

.agent-reaction-entry button {
  flex: none;
}

.agent-reaction-entry-hint {
  margin: 0;
  color: var(--bad);
  font-size: var(--text-label);
}

.agent-reaction-categories {
  max-height: 220px;
  overflow-y: auto;
  overscroll-behavior: contain;
}

.agent-reaction-categories h4 {
  margin: 6px 0 2px;
  color: var(--muted);
  font-size: var(--text-label);
  font-weight: 600;
}

.agent-reaction-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(34px, 1fr));
}

.agent-reaction-grid .agent-reaction-choice {
  width: 100%;
  height: 34px;
}

/* Bigger for a thumb, and still one row across a phone. */
@media (max-width: 600px) {
  .agent-reaction-picker {
    top: -48px;
  }

  /* Nine across, ten with a reaction of the person's own that is not a
     quick one, still inside 390px beside the bubble's gutter. */
  .agent-reaction-choice {
    width: 34px;
    height: 34px;
  }
}

/* A finger needs 44px, which nine emoji and the more button do not fit
   across a phone: the row scrolls sideways inside its pill instead of
   overflowing the drawer, and the grid of more emoji keeps its wrapping. */
@media (pointer: coarse) {
  .agent-reaction-picker {
    max-width: calc(100vw - 32px);
    overflow-x: auto;
    scrollbar-width: none;
  }

  .agent-reaction-choice {
    flex: none;
    width: 44px;
    height: 44px;
  }

  .agent-reaction-grid {
    grid-template-columns: repeat(auto-fill, minmax(44px, 1fr));
  }

  .agent-reaction-grid .agent-reaction-choice {
    width: 100%;
    height: 44px;
  }
}

/* The message the next turn answers, above the box: who said it and its
   start, with a way to drop it. */
.agent-reply-quote {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  padding: 6px 8px;
  border-left: 3px solid var(--accent);
  border-radius: 8px;
  background: var(--field);
  color: var(--muted);
}

.agent-reply-quote-body {
  flex: 1;
  min-width: 0;
}

.agent-reply-quote-title {
  color: var(--text);
  font-size: 12px;
  font-weight: 600;
}

.agent-reply-quote-text {
  font-size: 12.5px;
  white-space: pre-wrap;
  word-break: break-word;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.agent-attachment-chip,
.agent-reference-chip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  max-width: 100%;
  padding: 2px 8px;
  border-radius: 10px;
  border: 1px solid var(--border);
  background: var(--field);
  color: var(--text);
  font-size: 12px;
  text-decoration: none;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.agent-reference-photo {
  display: block;
  flex: none;
  width: 32px;
  height: 32px;
  margin: 0 2px 0 -6px;
  border-radius: 7px;
  object-fit: cover;
  background: var(--border);
}

/* A photo's chip is as tall as its picture, which is the picture of it. */
.agent-reference-chip:has(.agent-reference-photo) {
  padding-block: 1px;
}

/* The cross that takes a chip off: an icon action, at the chip's size
   rather than a row's. */
.agent-attachment-chip .icon-action,
.agent-reference-chip .icon-action,
.attachments-kept li .icon-action,
.attachments ul li .icon-action,
.uploads li .icon-action {
  width: 20px;
  height: 20px;
  padding: 0;
}

.agent-attachment-image {
  display: block;
  max-width: 160px;
  max-height: 120px;
  border-radius: 8px;
  object-fit: cover;
}

/* What an answer was read from, under the answer: the picture itself, or
   the file's name where it is not a picture, with where it was posted.
   Small enough that a transcript is still a transcript -- the picture
   opens into the lightbox to be looked at closely -- and never wider than
   the bubble it sits in, which on a phone is most of the screen. */
.agent-cited {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-top: 6px;
  min-width: 0;
}

.agent-cited-file {
  display: block;
  min-width: 0;
  overflow-wrap: anywhere;
}

.agent-cited-file .muted {
  display: block;
  font-size: var(--text-label);
}

.agent-cited-image {
  display: block;
  max-width: min(260px, 100%);
  max-height: 180px;
  width: auto;
  height: auto;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--field);
}

/* A compaction still being written: the line breathes until the note is
   done, since the turn is otherwise quiet for a while. */
.agent-line.note.pending {
  animation: agent-note-pending 1.6s ease-in-out infinite;
}

@keyframes agent-note-pending {
  50% {
    opacity: 0.45;
  }
}

@media (prefers-reduced-motion: reduce) {
  .agent-line.note.pending {
    animation: none;
  }
}

/* A compaction's line opens to the note it wrote, read as prose. */
.agent-note-detail {
  padding-left: 10px;
  border-left: 2px solid var(--border);
  font-size: 13px;
  color: var(--text);
}

/* The switches for what the conversation shows, under the day's usage in
   its dropdown. */
.head-menu-switches {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 6px 12px 8px;
}

/* Under the day's usage, a rule between them; alone, none. */
.head-menu-usage + .head-menu-switches {
  border-top: 1px solid var(--border);
}

/* A row each, the size of the menu's own text, and none of the margin a
   checkbox on a settings page has. */
.head-menu-switches label.checkbox {
  margin-top: 0;
  padding: 6px 0;
  color: var(--text);
  font-size: 13.5px;
  cursor: pointer;
}

/* A tool line opens to what was asked and what came back. */
.agent-tool-toggle {
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  cursor: pointer;
  text-align: left;
}

.agent-tool-detail {
  margin: 4px 0 6px 12px;
}

/* A table of counts: the numbers right-aligned in a tabular face, so the
   columns read down as columns. */
.numbers-table .numeric {
  text-align: right;
  font-variant-numeric: tabular-nums;
}

/* The prices compared with the bill keep their words whole: in the
   dialog a heading broke mid-word ("Configur" over "ed"). The table scrolls
   inside .table-wrap where it does not fit. */
.price-check-table th,
.price-check-table td {
  white-space: nowrap;
}

/* The kind of token, on its own line under the model. */
.price-check-kind {
  display: block;
}

/* A line under a table saying how to read it: apart from the table's last
   rule, where .field-hint, made to sit under a field, pulls itself up. */
.table-note {
  margin: var(--space-2) 0 0;
  font-size: var(--text-label);
}

/* The finance tables' last column is the one the agent's floating button
   covers where a window is narrow, so it keeps the button's width of room
   at its end; scrolling to the end brings it clear. */
@media (max-width: 900px) {
  .finance-credit-table th:last-child,
  .finance-credit-table td:last-child,
  .finance-transactions-table .table-surface th:last-child,
  .finance-transactions-table .table-surface td:last-child {
    padding-right: 64px;
  }
}

/* On a phone the credit table gives its card name two lines and its cells
   less padding, so the Used column is reached with a short scroll. */
@media (max-width: 600px) {
  .finance-credit-table th,
  .finance-credit-table td {
    padding-left: 6px;
    padding-right: 6px;
  }

  .finance-credit-table td:first-child {
    min-width: 7rem;
    white-space: normal;
    overflow-wrap: anywhere;
  }

  .finance-credit-table th:last-child,
  .finance-credit-table td:last-child {
    padding-right: 64px;
  }
}

/* A table wider than its panel scrolls sideways inside it, staying a
   table. Without this the usage table pushed its card, and then the whole
   page, wider than the window. */
.table-wrap {
  max-width: 100%;
  overflow-x: auto;
}

/* The memory check's scores: one small panel a source, side by side where
   there is room and one under another on a phone, each with the latest
   score and a line of the ones before it. */
.memory-check-scores {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
  gap: var(--space-3);
  margin-bottom: var(--space-3);
}

.memory-check-score {
  min-width: 0;
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-1);
}

.memory-check-score-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--space-2);
}

.memory-check-score-head strong {
  font-size: var(--text-title);
  font-variant-numeric: tabular-nums;
}

.memory-check-score-note {
  margin: var(--space-1) 0 0;
  font-size: var(--text-label);
}

/* The sparkline's scale is the whole of zero to a hundred, so this line
   is where a move of a few points can be read. */
.memory-check-score-change {
  margin: var(--space-1) 0 0;
  font-size: var(--text-label);
  font-variant-numeric: tabular-nums;
}

/* Shrinks with its panel rather than pushing it wider; the height stays,
   so the points keep their shape. */
.memory-check-sparkline {
  display: block;
  max-width: 100%;
  margin-top: var(--space-1);
}

.memory-check-sparkline-baseline {
  stroke: var(--border);
  stroke-width: 1;
}

.memory-check-sparkline-line {
  fill: none;
  stroke: var(--accent);
  stroke-width: 2;
  stroke-linejoin: round;
  stroke-linecap: round;
}

.memory-check-sparkline-point {
  fill: var(--accent);
  stroke: var(--surface);
  stroke-width: 2;
}

.agent-code {
  position: relative;
  margin: 4px 0 6px;
}

.agent-code pre {
  max-height: 240px;
  margin: 0;
  padding: 6px 28px 6px 8px;
  overflow: auto;
  border-radius: 6px;
  background: var(--field);
  font-size: 11px;
  white-space: pre-wrap;
  word-break: break-word;
}

.agent-code-copy {
  position: absolute;
  top: 2px;
  right: 2px;
  width: 24px;
  height: 24px;
  opacity: 0;
  transition: opacity 120ms ease;
}

.agent-code:hover .agent-code-copy,
.agent-code-copy:focus-visible {
  opacity: 1;
}

/* JSON as an editor colours it: the keys in the leaf, numbers and the
   three words in the warm, strings as the text is. */
.json-key {
  color: var(--leaf);
}

.json-number,
.json-literal {
  color: var(--warn);
}

.agent-usage {
  margin-top: 6px;
  font-size: 11px;
}

.agent-drawer-rename input {
  width: 100%;
}

.agent-drawer.dragging {
  outline: 2px dashed var(--accent, var(--muted));
  outline-offset: -4px;
}

.agent-drawer-drop {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: color-mix(in srgb, var(--background) 80%, transparent);
  pointer-events: none;
  font-weight: 600;
}

/* The agent is about to answer: said where the conversation is read, with
   the ways out beside it. Warm, because it is the one thing here that acts
   without a person. */
.mailbox-held-reply {
  margin: 0 0 12px;
  padding: 8px 10px;
  border: 1px solid var(--warn);
  border-radius: 6px;
  background: var(--field);
  font-size: 13px;
}

.mailbox-held-reply-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
}

.mailbox-held-reply-actions {
  display: inline-flex;
  gap: 6px;
  margin-left: auto;
}

.mailbox-held-reply-text {
  margin: 8px 0 0;
  padding-left: 22px;
  white-space: pre-wrap;
  line-height: 1.5;
}

.agent-reply-status.held {
  color: var(--warn);
  font-weight: 600;
}

/* The agent's summary of a conversation, folded at the top of the reader. A
   button for the fold, so the keyboard reaches it. */
.mailbox-thread-summary {
  margin: 0 0 12px;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--field);
}

.mailbox-thread-summary-head {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  padding: 6px 10px;
  border: 0;
  background: none;
  font: inherit;
  font-size: 13px;
  font-weight: 500;
  color: inherit;
  text-align: left;
  cursor: pointer;
}

.mailbox-thread-summary-head .muted {
  font-weight: 400;
}

.mailbox-thread-summary-chevron {
  margin-left: auto;
  color: var(--muted);
  /* The icon on the text's baseline rather than below it: an inline svg sits
     on the baseline as a letter does, which leaves it low in a row of text. */
  display: inline-flex;
  align-items: center;
}

.mailbox-thread-summary-text {
  margin: 0;
  padding: 0 10px 10px 32px;
  font-size: 13px;
  line-height: 1.5;
  white-space: pre-wrap;
}

/* What the agent worked out, in the width of a word: the category, whether
   an answer is waited for, and a mark when it said the message matters
   today. Quiet, because every row may carry one. */
/* At most a mark and one chip, kept whole and pushed to the end of the line,
   so the chips of every row stand in one column and it is the sender's name
   that gives way, with an ellipsis, when the row is narrow. */
.mailbox-row-insight {
  display: inline-flex;
  align-items: center;
  min-width: 0;
  max-width: 55%;
}

.mailbox-row-from > .mailbox-row-insight {
  flex: 0 0 auto;
  margin-left: auto;
}
.mailbox-row-who {
  min-width: 8ch;
}

.mailbox-row-chip {
  margin-left: 6px;
  padding: 0 5px;
  border-radius: 8px;
  background: var(--field);
  font-size: 11px;
  font-weight: 500;
  color: var(--muted);
  white-space: nowrap;
  /* Shortened rather than cut. The column it sits in is as wide as the
     row can spare and it clips what does not fit, so on a phone the word
     ended mid-letter: "Promotio", "Notificat", "Soc". A word ending in an
     ellipsis reads as a word too long for the room; a word ending in the
     middle reads as a fault. */
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
}

.mailbox-row-priority {
  margin-left: 6px;
  min-width: 16px;
  padding: 0 4px;
  border-radius: 8px;
  background: var(--warn);
  color: var(--background);
  font-size: 11px;
  font-weight: 700;
  text-align: center;
  line-height: 16px;
}

.mailbox-row-folder {
  display: inline-block;
  font-size: 11px;
  padding: 0 5px;
  margin-right: 6px;
  border-radius: 3px;
  background: var(--field);
  color: var(--muted);
}

/* Files on their way up, one bar each. */
.uploads {
  list-style: none;
  margin: 6px 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 4px;
  font-size: 13px;
}
.uploads progress {
  display: block;
  width: 100%;
  max-width: 420px;
  height: 6px;
  margin-top: 2px;
}
.uploads li.failed .upload-name {
  text-decoration: line-through;
}

/* The mailbox's message menu: a dot-dot-dot at the top right of the message. */
.message-menu {
  display: flex;
  justify-content: flex-end;
  margin-bottom: 8px;
}
.message-menu-button {
  font-size: 18px;
  line-height: 1;
  padding: 2px 10px;
}
.message-headers {
  margin: 4px 0 12px;
  font-size: 12px;
}
.message-headers-title {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  margin-bottom: 4px;
  font-weight: 600;
}
.message-headers pre {
  max-height: 240px;
  overflow: auto;
  margin: 0;
}
/* A header block as a list of names and values. Received: is four folded
   lines of one header, and read as it came off the wire the reader has to
   find where each one ends before they can find the one they want. */
.header-list {
  max-height: 320px;
  overflow: auto;
  margin: 0;
  border: 1px solid var(--border);
  border-radius: var(--radius-2);
}
.header-row {
  display: grid;
  grid-template-columns: minmax(0, 160px) minmax(0, 1fr);
  gap: 12px;
  padding: 5px 10px;
  border-bottom: 1px solid var(--border);
}
.header-row:last-child {
  border-bottom: 0;
}
.header-row dt {
  color: var(--muted);
  font-weight: 600;
  overflow-wrap: anywhere;
}
.header-row dd {
  margin: 0;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  /* The value keeps its own line breaks — a Received: or an
     Authentication-Results: is written in several — and wraps the rest. */
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}
/* One column on a phone: a name of 160px and a value beside it leaves the
   value forty characters wide. */
@media (max-width: 600px) {
  .header-row {
    grid-template-columns: minmax(0, 1fr);
    gap: 2px;
  }
}
.contacts-filter {
  max-width: 320px;
  margin-bottom: 10px;
}

/* A row's own actions, side by side with air between them. */
.row-actions {
  display: inline-flex;
  gap: 10px;
  align-items: center;
}

/* Two labels that are long in some languages: they wrap under each other
   on a narrow phone rather than run off it. */
.memory-check-actions {
  flex-wrap: wrap;
  justify-content: flex-end;
}

/* What a mail program is told: long, and better wrapped than clipped. */
.program-settings td.mono {
  white-space: normal;
  overflow-wrap: anywhere;
}
@media (max-width: 600px) {
  .hide-narrow {
    display: none;
  }
}

.folder-name {
  display: inline-flex;
  align-items: center;
  gap: 8px;
}
.folder-name svg {
  flex: 0 0 auto;
  opacity: 0.7;
}

/* Renaming or moving a folder happens in its own row: the field, then the
   buttons, wrapping onto a second line where the row is narrow rather than
   pushing the table sideways. */
.inline-form {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
}
.inline-form input,
.inline-form select {
  flex: 1 1 160px;
  min-width: 0;
  max-width: 320px;
  width: auto;
  margin: 0;
}
@media (max-width: 600px) {
  .inline-form input,
  .inline-form select {
    flex-basis: 120px;
  }
}

/* A row's actions as icons with their names in the tooltip: four of them fit
   beside a folder's name on a phone, where four words did not. */
.icon-action {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  min-height: 0;
  padding: 0;
  border: 0;
  border-radius: 6px;
  background: transparent;
  color: var(--muted);
  cursor: pointer;
}
/* A pinned folder's button is drawn at full strength rather than as an
   affordance waiting to be noticed, and it carries the pin with a line
   through it: the button undoes the pin, and should not be the same picture
   as the one that puts it there. */
.icon-action:hover:not(:disabled),
.icon-action.pinned {
  color: var(--text);
}
.icon-action:hover:not(:disabled) {
  background: rgba(127, 127, 127, 0.16);
}
.icon-action.danger {
  color: var(--bad);
}
.icon-action:disabled {
  opacity: 0.5;
  cursor: default;
}
.folders-table td {
  vertical-align: middle;
}
.folders-table .row-actions {
  gap: 2px;
}

/* The message menu at the end of the reader's action row, and the cross
   that closes the headers box. */
.reader-menu-slot {
  display: inline-flex;
  margin-left: auto;
}
.reader-menu-slot .message-menu-button {
  font-size: 16px;
  padding: 4px 10px;
}
.message-headers-close {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  min-height: 0;
  padding: 0;
  border: 0;
  border-radius: 6px;
  background: transparent;
  color: var(--muted);
  cursor: pointer;
}
.message-headers-close:hover {
  color: var(--text);
}

/* A dialog names what it is about, and an address can be longer than a
   phone is wide: the words break rather than the dialog growing past the
   screen. */
.dialog {
  max-width: min(420px, calc(100vw - 32px));
  overflow-wrap: anywhere;
}

/* People and their groups, side by side: the accounts on the left, the
   groups on the right, so who is in what is one page rather than two. On a
   phone they stack, groups under people. */
.access-columns {
  display: grid;
  grid-template-columns: minmax(0, 3fr) minmax(0, 2fr);
  gap: var(--space-4);
  align-items: start;
}
@media (max-width: 900px) {
  .access-columns {
    grid-template-columns: minmax(0, 1fr);
  }
}
.access-filter {
  margin: 0 0 8px;
}
/* A group on a person's row, and a group's own name: both choose that group,
   and the chosen one is the one that reads as pressed. */
.access-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  margin: 2px 0;
}
.access-chip {
  padding: 1px 8px;
  text-decoration: none;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: transparent;
  color: var(--muted);
  font-size: 13px;
  cursor: pointer;
}
/* Who a message is from, as a picture: the logo a sending domain publishes,
   or the first letter of its name on a colour derived from it. Square with a
   soft corner rather than a circle, because a brand's mark is drawn to fill a
   square and a circle crops it. */
.sender-logo {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  overflow: hidden;
  border-radius: var(--radius-2);
  color: #fff;
  font-weight: 600;
  line-height: 1;
  /* A logo is a stranger's file: it is drawn inside its own box and cannot
     paint over anything around it. */
  contain: paint;
}
.sender-logo img {
  width: 100%;
  height: 100%;
  object-fit: contain;
}
/* The mark a domain publishes, beside what is known about it. */
.domain-logo {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin-bottom: var(--space-3);
}
.domain-logo-about {
  min-width: 0;
  font-size: 13px;
}

/* The exemption above frees a notice from the measure because a notice
   usually sits among full-width rows, where a capped one reads as a mistake.
   This card has no rows: it is prose, a button, and this. Left full width it
   is the one thing on the card running to 1060px while every passage around
   it stops at 617, which reads as the mistake instead. Two class names so it
   outweighs that exemption by specificity rather than by !important. */
p.notice.domain-logo-note {
  max-width: var(--measure);
}

/* A name with its mark beside it, on one line. */
.sender-row {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
}

/* A contact's name, and on a phone its address under it, where the Email
   column no longer fits beside it. */
.contact-name {
  display: flex;
  flex-direction: column;
}
/* A postal address is the longest thing in the row and the least often
   there: it may shrink below the usual floor of a truncated column, so the
   row's actions stay on the screen. */
td.truncate:has(.contact-postal) {
  min-width: 8rem;
}
.contact-email-line {
  display: none;
  font-size: 12px;
}
@media (max-width: 760px) {
  .contact-email-line {
    display: block;
  }
}

/* A mailing list in the list of them, laid out the way a conversation is: the
   mark, then who and how much, then when and the way out. */
.subscription-row {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  gap: 10px;
  align-items: center;
  padding: 8px 10px;
  border-bottom: 1px solid var(--border);
  cursor: pointer;
  font-size: 13px;
}
.subscription-row:hover {
  background: var(--field);
}
.subscription-row.active {
  background: var(--sidebar-hover);
}
.subscription-row.unread .subscription-row-name {
  font-weight: 600;
}
.subscription-row-link {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
  padding: 0;
  border: 0;
  background: transparent;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
}
.subscription-row-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.subscription-row-meta {
  color: var(--muted);
  font-size: 12px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* What was asked of this list, in the colour of an answer rather than of a
   warning: leaving one is a thing that went right. */
/* The empty reading pane: a sentence with the drawing above it, both held in
   the middle of the space rather than at the top of it. */
/* The empty reading pane, which is a drawing and a line under it — not the
   same thing as the empty list next door, which is a sentence in a box and
   wants the padding a sentence wants. They shared a class, and this rule,
   being the later of the two, took that padding away: the sentence in the
   list ended up against both edges. */
.mailbox-pane-placeholder {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  flex: 1;
  min-height: 240px;
  /* None: the line runs to the edges of the pane, and a sentence under it
     carries its own room. */
  padding: 0;
  color: var(--muted);
  text-align: center;
  overflow: hidden;
}

/* The pane gives up its padding for a placeholder, so the line reaches the
   sides. It keeps it for a message, which should not be against the edge. */
.mailbox-pane:has(> .mailbox-pane-placeholder) {
  padding: 0;
}

/* Edge to edge, and no taller than the line it holds. The box was the
   drawing's own proportions, which left a band of nothing under the wave and
   pushed the sentence away from it; this crops to the wave instead. */
.envelope-trail {
  width: 100%;
  height: auto;
  aspect-ratio: 1200 / 190;
  max-height: 40%;
  color: var(--leaf);
  pointer-events: none;
}

/* Under the line rather than over it: the drawing is the thing to look at,
   and the sentence says what to do about it. */
.mailbox-pane-placeholder > span {
  padding: 0 var(--space-4);
}

/* Writing a message, at the head of the folders. It is not one of them: the
   folders below it hold mail and carry counts, and this makes a message. The
   accent says so without a second shape in the rail. */
.sidebar-compose {
  color: var(--accent);
  font-weight: 600;
}

/* What they sign in with, beside what to call them. Set apart by weight and
   size rather than by a separator, because it is a different kind of fact
   about the same person. */
.access-user-username {
  margin-left: var(--space-2);
  font-size: 13px;
  font-weight: 400;
}

/* An arrow that turns rather than being replaced.
   A control that opens something shows which way it goes: down to open, and
   the same arrow turned over once it is open. Swapping it for a different
   arrow redraws the button at the moment it is pressed, which reads as a
   flicker rather than as a movement — and leaves nothing on the screen that
   shows the two states are one thing. */
.chevron {
  transition: transform 180ms ease;
}

[aria-expanded='true'] .chevron {
  transform: rotate(180deg);
}

@media (prefers-reduced-motion: reduce) {
  .chevron {
    transition: none;
  }
}

/* What is attached, above the message rather than under it: one line of
   names, wrapping when there are more than a line's worth. */
.attachment-strip {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin: 0 0 var(--space-3);
  padding: 0;
  list-style: none;
}

.attachment-strip a {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  max-width: 320px;
  padding: 4px 10px;
  border: 1px solid var(--border);
  border-radius: var(--radius-2);
  background: var(--surface);
  color: var(--text);
  font-size: 12px;
  text-decoration: none;
}

.attachment-strip a:hover {
  border-color: var(--accent);
}

/* The name is what is being looked for, so it is the part that gets the room
   and the part that is cut when there is not enough. */
.attachment-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Where the program says what just happened: the foot of the window, at the
   left of the page and not of the rail, which is as wide as the window's
   left margin where the person's own menu is, newest at the bottom. It was the
   top right corner, where it covered the buttons that a page keeps at the
   right of its header (New event, Upload, Share) and took the click of
   whoever reached for the next one; the foot is where nothing is acted on,
   and the right of it is the agent's button and the upload tray. */
.toasts {
  /* Where the strip begins: at the content, which is where the rail ends. */
  --toasts-left: calc(var(--rail-width, 232px) + var(--space-4));
  position: fixed;
  bottom: calc(var(--space-4) + env(safe-area-inset-bottom));
  left: var(--toasts-left);
  z-index: 80;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  /* The strip takes no clicks; each message does. Otherwise an invisible
     column down the side of the window would swallow them. */
  pointer-events: none;
}

.toast {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  /* Ends before the agent's drawer and the upload tray (--toast-edge, set
     from where they are). */
  max-width: min(420px, calc(var(--toast-edge, calc(100vw - var(--space-4))) - var(--toasts-left, var(--space-4))));
  padding: 10px 10px 10px 14px;
  border: 1px solid var(--border);
  border-radius: var(--radius-3);
  background: var(--surface);
  box-shadow: var(--shadow-floating);
  color: var(--text);
  font-size: 13px;
  pointer-events: auto;
  animation: toast-in 140ms ease-out;
}

/* A failure is marked down the edge rather than by painting the whole thing
   red: it is being told something, not being stopped. */
.toast.failed {
  border-left: 3px solid var(--bad);
}

.toast-message {
  flex: 1;
  min-width: 0;
  overflow-wrap: anywhere;
}

/* The way back, and the only thing here worth a colour. */
.toast-action {
  flex: 0 0 auto;
  padding: 4px 8px;
  border: 1px solid transparent;
  border-radius: var(--radius-1);
  background: none;
  color: var(--accent);
  font: inherit;
  font-weight: 600;
  cursor: pointer;
}

.toast-action:hover {
  border-color: var(--border);
}

.toast-close {
  flex: 0 0 auto;
  display: flex;
  padding: 4px;
  border: none;
  border-radius: var(--radius-1);
  background: none;
  color: var(--muted);
  cursor: pointer;
}

.toast-close:hover {
  color: var(--text);
}

@keyframes toast-in {
  from {
    opacity: 0;
    transform: translateY(6px);
  }
}

@media (prefers-reduced-motion: reduce) {
  .toast {
    animation: none;
  }
}

/* Where the rail is a drawer and not a column, the strip starts at the
   window's own margin. */
@media (max-width: 760px) {
  .toasts {
    --toasts-left: var(--space-4);
  }
}

/* With the drawer or the tray leaving too little room beside the rail, the
   strip starts at the window's margin, over the rail, above the account row
   at its foot. */
.toasts.over-rail {
  --toasts-left: var(--space-4);
  bottom: calc(var(--toast-lift, 72px) + env(safe-area-inset-bottom));
}

/* On a phone the strip is the width of the screen, because 420px of message
   beside 30px of margin is neither, and stands clear of the agent's button,
   which keeps the same 84 pixels free. A page that has the upload tray there
   too puts the toasts above it (the tray says how tall it is). */
@media (max-width: 600px) {
  .toasts {
    bottom: calc(84px + env(safe-area-inset-bottom) + var(--photo-tray-height, 0px));
    left: var(--space-2);
    right: var(--space-2);
  }
}

/* A copy of the pressed control's box, which is what the circle is clipped
   to, at the height of what holds the control (set by ripple.ts). The
   control itself is not touched, so no layout anywhere changes to make room
   for this, and nothing here can take a click meant for what is under it. */
.ripple-clip {
  position: fixed;
  overflow: hidden;
  pointer-events: none;
}

.ripple {
  position: absolute;
  transform: translate(-50%, -50%) scale(0);
  border-radius: 50%;
  /* The text colour of whatever was pressed, at the faintest weight that is
     still visible on both grounds: the mark belongs to the control rather
     than being a colour of its own. */
  background: currentColor;
  opacity: 0.18;
  animation: ripple-out 450ms ease-out forwards;
}

@keyframes ripple-out {
  to {
    transform: translate(-50%, -50%) scale(1);
    opacity: 0;
  }
}

/* Nothing moves for somebody who asked for less movement. The listener also
   returns early, so this is the second of two locks on the same door. */
@media (prefers-reduced-motion: reduce) {
  .ripple {
    display: none;
  }
}

/* The list of shortcuts, key beside meaning. A definition list because that
   is what it is, and a grid so the keys line up in a column of their own. */
.shortcut-list {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--space-2) var(--space-3);
  align-items: center;
  margin: var(--space-2) 0 0;
}
/* One list per kind of key: this page's, going somewhere, and anywhere. */
.shortcut-section {
  margin-top: var(--space-4);
}
/* The places and the page's own keys, two to a line where there is room: a
   dozen of each one under another pushed the rest out of the dialog. */
@media (min-width: 700px) {
  .shortcut-section-go .shortcut-list,
  .shortcut-section-page .shortcut-list {
    grid-template-columns: auto 1fr auto 1fr;
  }
}
.shortcut-section h4 {
  margin: 0;
  font-size: 13px;
  font-weight: 600;
}
/* Between the two keys of g then i, small enough that the keys stay what
   the eye finds. */
.shortcut-then {
  margin: 0 var(--space-1);
  color: var(--muted);
  font-size: 12px;
}
.shortcut-list > div {
  display: contents;
}
.shortcut-list dt {
  margin: 0;
}
.shortcut-list dd {
  margin: 0;
  color: var(--muted);
  font-size: 13px;
}
.shortcut-list kbd {
  display: inline-block;
  min-width: 1.6em;
  padding: 2px 6px;
  border: 1px solid var(--border);
  border-bottom-width: 2px;
  border-radius: var(--radius-1);
  background: var(--surface-sunken, transparent);
  font-family: var(--mono, monospace);
  font-size: 12px;
  text-align: center;
}

.subscription-row-left {
  color: var(--muted);
  font-size: 12px;
}

.subscription-row-left.bad {
  color: var(--bad);
}
.subscription-row-when {
  display: flex;
  align-items: center;
  gap: 6px;
  color: var(--muted);
  font-size: 12px;
  white-space: nowrap;
}

/* A group's name on a person's row is a link to that group, so on a phone it
   is worth being able to hit. */
@media (max-width: 760px) {
  .access-chip {
    padding: 4px 10px;
  }
}
.access-chip:hover {
  color: var(--text);
}
.access-chip.chosen {
  color: var(--text);
  border-color: var(--text);
}

/* The groups tab: the groups to choose from, who is in the one chosen, and
   what it carries — three panels side by side, because they are one subject
   read together. Every one of them used to be inside a dialog. */
.access-groups,
.access-roles {
  display: grid;
  gap: var(--space-4);
  align-items: start;
}
.access-groups {
  grid-template-columns: minmax(0, 250px) minmax(0, 1fr) minmax(0, 320px);
}
.access-roles {
  grid-template-columns: minmax(0, 250px) minmax(0, 1fr);
}
/* The panels are narrower than a page, and a heading beside the one button
   that adds to it has to be able to fall onto its own line. */
.access-groups .settings-section-head,
.access-roles .settings-section-head {
  flex-wrap: wrap;
}
.access-picker {
  display: none;
  gap: var(--space-2);
  align-items: center;
  margin-bottom: var(--space-2);
}
.access-picker .select {
  flex: 1 1 auto;
}

/* Narrower than three: what a group carries goes under it, full width, and
   the list of groups keeps its column. */
@media (max-width: 1200px) {
  .access-groups {
    grid-template-columns: minmax(0, 250px) minmax(0, 1fr);
  }
  .access-attached {
    grid-column: 1 / -1;
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
    gap: var(--space-3);
    align-items: start;
  }
}
@media (max-width: 900px) {
  .access-groups,
  .access-roles {
    grid-template-columns: minmax(0, 1fr);
  }
  /* One column: a list of groups above a list of members above two more
     lists is a page of scrolling to reach what was chosen, so the choice
     becomes a dropdown and the list of them goes away. */
  .access-picker {
    display: flex;
  }
  .access-groups > .settings-section:first-of-type,
  .access-roles > .settings-section:first-of-type {
    display: none;
  }
}
/* A group or a role in the list beside what it holds, and the actions on the
   one being read.
   
   This is navigation — a list of things where one is being looked at — so it
   is said the way the rail says it: a raised pill on the row you are on,
   rather than a rule under every row and an underline on one of them. */
.access-pick-row {
  display: flex;
  align-items: center;
  gap: 0;
  margin: 1px 0;
  /* No padding of its own: the name is the button, and it carries the
     padding, so a press anywhere on the row lands on it — the row's own
     edges used to swallow a click beside the word. */
  padding: 0;
  border: 1px solid transparent;
  border-radius: var(--radius-2);
  color: var(--muted);
  /* The height of the row that carries the pencil and the bin, held by every
     row that does not. The actions appear on the row being read, and without
     this that row grew by seven pixels and pushed every row under it down —
     so choosing a group moved the list you were choosing from. */
  min-height: 42px;
}
.access-pick-row:hover {
  background: var(--sidebar-hover);
  color: var(--text);
}
.access-pick-row .row-actions {
  padding-right: 8px;
}
.access-pick-row.chosen {
  background: var(--surface);
  border-color: var(--border);
  box-shadow: var(--shadow-raised);
  color: var(--text);
}
.access-pick-name {
  flex: 1 1 auto;
  align-self: stretch;
  min-width: 0;
  padding: 6px 8px;
  border: 0;
  background: transparent;
  color: inherit;
  font-size: 14px;
  font-weight: 500;
  text-align: left;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  cursor: pointer;
}

.access-idp-note {
  margin: var(--space-2) 0 0;
  font-size: 13px;
}

/* A dialog fits the screen it is on: what does not fit scrolls inside it,
   and the actions stay at its foot. */
.dialog {
  max-height: calc(100dvh - 2 * var(--space-4));
  /* Sideways is hidden rather than left to resolve to auto, which is what a
     single overflow-y does and what would make every dialog in the app a
     horizontal scroller. */
  overflow: hidden auto;
}

/* On a phone a dialog is the screen rather than a card floating on it.
   A 420px card inside a 390px window left a margin on either side and a
   scrim behind it that did nothing but take the width away from the form --
   and a form is what a dialog on a phone almost always is. Full width, full
   height, square, so every field gets the whole screen. */
@media (max-width: 640px) {
  .dialog-scrim {
    padding: 0;
    align-items: stretch;
  }

  .dialog {
    max-width: none;
    width: 100%;
    max-height: none;
    min-height: 100dvh;
    border: none;
    border-radius: 0;
    box-shadow: none;
    /* Room under the last control for the home bar, so the buttons are not
       sitting on it. */
    padding-bottom: max(var(--space-4), env(safe-area-inset-bottom));
  }
}

@media (max-width: 760px) {
  /* A checkbox is not a thumb-sized control: the row around it is. Sizing
     the box itself to 40px stretched every line of a list of people into
     its own paragraph, and a group with a dozen members became a dialog
     nobody could read. */
  .check-list-item input[type='checkbox'],
  .check-list-item input[type='radio'] {
    min-height: 0;
  }
  label.check-list-item {
    padding: 8px 0;
    min-height: 40px;
  }
  .check-list-item input[type='checkbox'] {
    width: 18px;
    height: 18px;
  }
  /* Lists inside a dialog on a phone: tall enough to work in, short enough
     that three of them do not bury the buttons. Measured in dvh like the
     dialog around them, so a retracting toolbar moves both together. */
  .dialog .check-list-items {
    max-height: 40dvh;
  }
}

/* A list of things to tick, once there are enough of them to search: the
   search sits under the legend, the rows scroll under it, and what is
   already chosen is at the top and reads as chosen. */
.check-list-search {
  width: calc(100% - 24px);
  margin: 10px 12px 0;
}
.check-list-items {
  display: flex;
  flex-direction: column;
}
/* Ticked rows read as ticked from the box, which is the control this row is
   about. Weight would say it again, in a width that differs from the rows
   around it. */
.check-list-item.chosen {
  color: var(--text);
}

/* The groups as a row of chips over the people, for the width where the
   groups themselves are below rather than beside: a filter you have to
   scroll past what it filters to reach is not one. */
.access-narrow-filter {
  display: none;
  margin-bottom: 8px;
}
@media (max-width: 900px) {
  .access-narrow-filter {
    display: flex;
  }
}

/* --- the drawer, continued ----------------------------------------------- */

/* The agent at work and nothing said yet: three dots breathing. */
.agent-line.thinking {
  align-self: flex-start;
  padding: 10px 14px;
  border-radius: 10px;
  background: var(--field);
}

.agent-dots {
  display: inline-flex;
  gap: 4px;
}

.agent-dots i {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--muted);
  animation: agent-dot 1.2s ease-in-out infinite;
}

.agent-dots i:nth-child(2) {
  animation-delay: 0.2s;
}

.agent-dots i:nth-child(3) {
  animation-delay: 0.4s;
}

@keyframes agent-dot {
  0%,
  80%,
  100% {
    opacity: 0.3;
    transform: translateY(0);
  }
  40% {
    opacity: 1;
    transform: translateY(-3px);
  }
}

@media (prefers-reduced-motion: reduce) {
  .agent-dots i {
    animation: none;
    opacity: 0.7;
  }
}

/* Where the day changes. */
.agent-day {
  align-self: center;
  margin: 4px 0;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

/* A way back to the end, for somebody who scrolled up to read. */
.agent-drawer-jump {
  position: absolute;
  right: 16px;
  bottom: 96px;
  width: 32px;
  height: 32px;
  border: 1px solid var(--border);
  border-radius: 50%;
  background: var(--background);
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.18);
}

/* The search box, and the button that starts one at its end. */
.agent-drawer-list-head {
  display: flex;
  align-items: center;
  gap: 4px;
  margin-bottom: 4px;
}

.agent-drawer-search {
  flex: 1 1 0;
  min-width: 0;
  font-size: 13px;
}

.agent-drawer-list-summary {
  font-size: 11px;
  font-weight: 400;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

/* A question or approval waits in this conversation. */
.agent-drawer-list-waiting {
  color: var(--warn);
  font-weight: 600;
}

.agent-drawer-list-actions .icon-button.danger:hover {
  color: var(--bad);
}

/* What the agent made: a frame of its own under the line that made it. */
.agent-artifact {
  margin: 6px 0 8px;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--background);
  overflow: hidden;
  /* It never gives way. With the tool lines shown this card sits inside
     one, which is an ordinary block; with them hidden it is a child of the
     transcript itself, which is a flex column -- and a flex item shrinks
     by default, so the picture inside it was squashed to a band while the
     same picture a line above was the right height. */
  flex: 0 0 auto;
}

.agent-artifact-head {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 2px 4px 2px 8px;
  border-bottom: 1px solid var(--border);
  font-size: 11px;
  color: var(--muted);
}
.agent-artifact-head .icon-button {
  padding: 3px;
}

.agent-artifact-title {
  flex: 1;
  min-width: 0;
  font-weight: 600;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.agent-artifact-frame {
  display: block;
  width: 100%;
  height: 320px;
  border: 0;
  background: #ffffff;
}

.agent-artifact-body {
  padding: 10px 12px;
  max-height: 320px;
  overflow: auto;
}

/* A file handed over: the picture or the player fills the box's width
   and stays a sensible height; a sound is a bar. */
.agent-file-media {
  display: block;
  width: 100%;
  max-height: 360px;
  object-fit: contain;
  background: var(--surface);
}
.agent-file-audio {
  display: block;
  width: 100%;
  padding: 8px;
  box-sizing: border-box;
}
.agent-file-caption {
  padding: 6px 10px;
  font-size: 12px;
  color: var(--muted);
}

.agent-code-language {
  position: absolute;
  top: 6px;
  right: 30px;
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

/* The markdown an answer is written in, beyond what a changelog needs. */
.markdown ol {
  margin: 0 0 var(--space-2);
  padding-left: 1.4em;
}

.markdown h4 {
  margin: var(--space-4) 0 var(--space-2);
  font-size: var(--text-body);
  font-weight: 700;
}

.markdown blockquote {
  margin: 0 0 var(--space-2);
  padding: 2px 10px;
  border-left: 3px solid var(--border);
  color: var(--muted);
}

.markdown hr {
  margin: var(--space-3) 0;
  border: 0;
  border-top: 1px solid var(--border);
}

.markdown .table-wrap {
  margin: 0 0 var(--space-2);
}

.markdown table {
  font-size: 12.5px;
}

.markdown .agent-code {
  margin: 0 0 var(--space-2);
}

.markdown .agent-code pre {
  font-size: 12px;
  max-height: 400px;
}

/* A box that narrows a long list to the rows with the words in them. */
.settings-filter {
  width: 100%;
  max-width: 360px;
  margin-bottom: var(--space-3);
}

.agent-drawer-readonly {
  padding: 8px 12px;
  border-top: 1px solid var(--border);
  font-size: 12px;
}

/* A quiet red for a button that takes something back — revoke, cancel a
   held reply — outlined like the default, so that the one filled red
   button on a page stays the confirming one. */
button.danger:not(.primary):not(.icon-action):not(.icon-button):not(.link) {
  color: var(--bad);
  border-color: color-mix(in srgb, var(--bad) 40%, var(--border));
}

button.danger:not(.primary):not(.icon-action):not(.icon-button):not(.link):hover:not(:disabled) {
  background: var(--bad-surface, var(--sidebar-hover));
}

/* A small action beside a title, inside a card that is itself inside a
   conversation: sized to the text it sits by. */
.agent-artifact-action,
a.button.agent-artifact-action {
  padding: 2px 8px;
  font-size: 12px;
  font-weight: 500;
  text-decoration: none;
}

/* One subject of a card, with a Save of its own: a heading, its fields,
   the button. A rule above separates it from the subject before. */
/* Two short boxes side by side, for parts of one thing: a town and a region,
   a postcode and a country. They stack on a narrow screen, where two boxes of
   half a phone's width are two boxes nobody can type in. */
.form-row {
  display: flex;
  gap: var(--space-3);
}

.form-row > label {
  flex: 1 1 0;
  min-width: 0;
}

@media (max-width: 640px) {
  .form-row {
    display: block;
  }
}

.settings-subform {
  margin-top: var(--space-5);
  padding-top: var(--space-4);
  border-top: 1px solid var(--border);
}

/* Inside a card, a part's rule runs from the card's border to its border, as
   the card's own edges do, rather than stopping at the padding: a line that
   stops short reads as underlining the text above it. The padding comes back
   inside, so nothing in the part moves. The same for the heavier rule between
   two mailboxes of the sources card. */
.card .settings-subform,
.card .agent-source + .agent-source {
  margin-left: calc(var(--card-padding-inline) * -1);
  margin-right: calc(var(--card-padding-inline) * -1);
  padding-left: var(--card-padding-inline);
  padding-right: var(--card-padding-inline);
}

.settings-subform h4,
.settings-subform h3 {
  margin: 0 0 var(--space-1);
}

.settings-subform > p.muted {
  margin: 0 0 var(--space-3);
}

.settings-subform .page-actions {
  margin-top: var(--space-3);
}

/* Chosen things, each removable. */
.chip-list {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-bottom: 6px;
}

.chip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 2px 4px 2px 10px;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--field);
  font-size: var(--text-label);
}

.chip-remove {
  padding: 0 6px;
  border: none;
  background: none;
  color: var(--muted);
  font-size: 14px;
  line-height: 1;
}

.chip-remove:hover:not(:disabled) {
  background: none;
  color: var(--text);
}

/* One mailbox as a source, among others in the same card: a heavier rule
   between two than between the subjects inside one. */
.agent-source + .agent-source {
  margin-top: var(--space-6);
  padding-top: var(--space-5);
  border-top: 2px solid var(--border);
}

.agent-source > .settings-section-head {
  margin-bottom: var(--space-3);
}

.agent-source > .settings-section-head h4 {
  margin: 0 0 2px;
}

/* A tool line is a row of the transcript, not a control to aim a thumb at:
   the phone's taller buttons would space the lines out like paragraphs. */
@media (max-width: 600px) {
  .agent-tool-toggle {
    min-height: 28px;
  }
}

/* A button with a small mark before its word. */
button.with-icon {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

/* The main conversation heads the picker, marked, with a rule under it. */
.agent-drawer-list-row.main {
  border-bottom: 1px solid var(--border);
  margin-bottom: 4px;
  padding-bottom: 8px;
  border-radius: 0;
}

.agent-drawer-list-row.main .agent-drawer-list-name {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-weight: 600;
}

/* The tool policy is an accordion: one bordered list, a family per
   row — the chevron, its name, how many tools, the word for the whole —
   and, opened, its tools indented under it, each with a word of its own. */
.tool-policy {
  margin-top: var(--space-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-3);
  overflow: hidden;
}

.tool-policy-family + .tool-policy-family {
  border-top: 1px solid var(--border);
}

.tool-policy-head {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: 8px 14px;
  background: var(--surface);
}

.tool-policy-head:hover {
  background: var(--sidebar-hover);
}

.tool-policy-toggle {
  flex: 1;
  display: flex;
  align-items: center;
  gap: 10px;
  min-width: 0;
  padding: 6px 0;
  border: 0;
  border-radius: 0;
  background: none;
  text-align: left;
  font-weight: 600;
  font-size: 14px;
}

.tool-policy-toggle:hover:not(:disabled) {
  background: none;
}

.tool-policy-toggle svg {
  flex: 0 0 auto;
  color: var(--muted);
}

.tool-policy-toggle .muted {
  font-weight: 400;
  font-size: var(--text-label);
}

.tool-policy-tools {
  margin: 0;
  padding: 0;
  background: var(--background);
  border-top: 1px solid var(--border);
}

/* A tool is a line of the family, not a card of its own: rows divided by
   rules that run the whole width, indented under the family's name. */
.tool-policy-tools .settings-row {
  margin: 0;
  padding: 8px 14px 8px calc(14px + 24px);
  border: 0;
  border-radius: 0;
  background: transparent;
}

.tool-policy-tools .settings-row + .settings-row {
  border-top: 1px solid var(--border);
}

.tool-policy-tools .settings-row {
  padding-top: 6px;
  padding-bottom: 6px;
}

.tool-policy-tools .settings-row-title {
  font-family: var(--font-mono, ui-monospace, monospace);
  font-size: 13px;
}

/* The note about a family whose tools arrive as they connect. It sits on
   the same indent as a tool's row, so it lines up with the names it
   stands beside instead of against the edge. */
.tool-policy-tools > .muted {
  margin: 0;
  padding: 10px 14px 10px calc(14px + 24px);
  max-width: 68ch;
}

.tool-policy-tools .settings-row + .muted {
  border-top: 1px solid var(--border);
}

/* The usage card's range and grouping, on one line. */
.usage-range {
  display: flex;
  align-items: flex-end;
  gap: var(--space-3);
  flex-wrap: wrap;
}

.usage-range input[type='date'] {
  min-width: 9.5rem;
}

/* The usage, drawn above its table, in the page's own colours so it reads
   in either theme: output the full leaf, fresh input a paler leaf, and
   cached input a neutral grey, the part that costs a tenth. */
.usage-chart {
  --usage-input: color-mix(in srgb, var(--leaf) 62%, var(--surface));
  --usage-cached: color-mix(in srgb, var(--muted) 45%, var(--surface));
  --usage-output: var(--leaf);
  /* Its width is the card's, never its own drawing's: sized by what it
     drew, the chart could not shrink with a narrowing window. */
  min-width: 0;
  margin-bottom: var(--space-4);
}

.usage-chart-head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--space-3);
  flex-wrap: wrap;
  margin-bottom: var(--space-3);
}

.usage-chart-total {
  font-size: 22px;
  font-weight: 650;
  letter-spacing: -0.01em;
  font-variant-numeric: tabular-nums;
}

.usage-chart-caption {
  font-size: var(--text-label);
}

.usage-chart-plot {
  position: relative;
  width: 100%;
  min-width: 0;
  overflow: hidden;
}

.usage-chart-plot svg {
  display: block;
  /* A finger dragged sideways moves the tooltip along the keys; up and
     down still scrolls the page. */
  touch-action: pan-y;
  width: 100%;
  height: auto;
  overflow: visible;
}

.usage-chart-grid {
  stroke: var(--border);
  stroke-width: 1;
  shape-rendering: crispEdges;
}

.usage-chart-axis {
  fill: var(--muted);
  font-size: 11px;
  font-variant-numeric: tabular-nums;
}

.usage-chart-column {
  transition: opacity 120ms ease;
}

.usage-chart-column.input,
.usage-chart-rank-bar.input,
.usage-chart-swatch.input {
  fill: var(--usage-input);
  background: var(--usage-input);
}

.usage-chart-column.cached,
.usage-chart-rank-bar.cached,
.usage-chart-swatch.cached {
  fill: var(--usage-cached);
  background: var(--usage-cached);
}

.usage-chart-column.output,
.usage-chart-column.single,
.usage-chart-rank-bar.output,
.usage-chart-rank-bar.single,
.usage-chart-swatch.output {
  fill: var(--usage-output);
  background: var(--usage-output);
}

.usage-chart-dim .usage-chart-column {
  opacity: 0.35;
}

.usage-chart-legend {
  display: flex;
  gap: var(--space-4);
  flex-wrap: wrap;
  margin-top: var(--space-2);
  font-size: var(--text-label);
  color: var(--muted);
}

.usage-chart-legend span,
.usage-chart-tooltip-line {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.usage-chart-swatch {
  display: inline-block;
  width: 10px;
  height: 10px;
  border-radius: 3px;
}

.usage-chart-tooltip {
  position: absolute;
  top: 0;
  z-index: 2;
  /* Never wider than the plot it is held inside (ChartTooltip). */
  min-width: min(11rem, 100%);
  max-width: 100%;
  box-sizing: border-box;
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-1);
  background: var(--surface);
  box-shadow: var(--shadow-floating);
  font-size: var(--text-label);
  pointer-events: none;
}

.usage-chart-tooltip-day {
  font-weight: 650;
  margin-bottom: 4px;
}

.usage-chart-tooltip-line {
  display: flex;
  width: 100%;
}

.usage-chart-tooltip-line span {
  color: var(--muted);
}

.usage-chart-tooltip-line strong {
  margin-left: auto;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

/* What the reader would otherwise work out from the lines above, set off
   under them by a rule; the empty swatch keeps its words in their column. */
.usage-chart-tooltip-note {
  margin-top: 4px;
  padding-top: 4px;
  border-top: 1px solid var(--border);
}

.usage-chart-swatch.none {
  background: transparent;
}

.usage-chart-ranked {
  display: grid;
  gap: 8px;
}

.usage-chart-rank {
  display: grid;
  grid-template-columns: minmax(6rem, 14rem) 1fr auto;
  align-items: center;
  gap: var(--space-3);
  font-size: var(--text-label);
}

.usage-chart-rank-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.usage-chart-rank-track {
  display: flex;
  height: 12px;
  border-radius: var(--radius-round);
  background: var(--field);
  overflow: hidden;
}

.usage-chart-rank-bar {
  height: 100%;
}

.usage-chart-rank-value {
  min-width: 4.5rem;
  text-align: right;
  font-variant-numeric: tabular-nums;
}

@media (max-width: 600px) {
  .usage-chart-rank {
    grid-template-columns: minmax(4rem, 8rem) 1fr auto;
  }
}

/* The calendar.
   Every colour here comes from a token, so the month grid reads in both
   themes without a second palette. The grid is the only place that draws its
   own lines: a table would carry a header and a sort, and a month is neither
   a list nor sorted. */
.calendar-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  margin: 0.75rem 0;
}

.calendar-move {
  display: flex;
  align-items: center;
  gap: 0.4rem;
}

.calendar-heading {
  margin-left: 0.4rem;
  font-weight: 600;
  /* Digits that do not shift the heading as the months change under it. */
  font-variant-numeric: tabular-nums;
}

.calendar-month {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  /* One row for the weekday names, then six weeks that share what is left. */
  grid-template-rows: auto repeat(6, minmax(6.5rem, 1fr));
  min-height: 100%;
  gap: 1px;
  background: var(--border);
  border: 1px solid var(--border);
  border-radius: 8px;
  overflow: hidden;
}

.calendar-weekday {
  background: var(--rail);
  color: var(--muted);
  font-size: 0.75rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  padding: 0.4rem 0.5rem;
  text-align: center;
}

.calendar-day {
  background: var(--surface);
  position: relative;
  min-height: 6.5rem;
  /* Fills whatever the grid is given; the minimum above is the floor. */
  height: 100%;
  padding: 0.3rem;
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
}

.calendar-day.outside {
  background: var(--rail);
}

/* The empty part of a day, which is where somebody presses to put something
   in it. Underneath the date and the entries so that those keep their own
   clicks, and invisible: the cell itself is the affordance. */
.calendar-day-add {
  position: absolute;
  inset: 0;
  z-index: 0;
  border: none;
  background: none;
  padding: 0;
  cursor: pointer;
}

.calendar-day-number,
.calendar-day-entries {
  position: relative;
  z-index: 1;
}

.calendar-day.outside .calendar-day-number {
  color: var(--muted);
}

.calendar-day.today .calendar-day-number {
  background: var(--accent);
  color: var(--accent-text);
}

/* A square box with the date centred in it, so the mark on today is a circle
   rather than a pill. Padding around the digits makes the shape depend on how
   many there are -- one digit round, two an oval -- and a date that changes
   shape through the month is the kind of thing that looks like a mistake
   because it is one. */
.calendar-day-number {
  align-self: flex-start;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.8rem;
  height: 1.8rem;
  border: none;
  background: none;
  color: var(--text);
  font: inherit;
  font-variant-numeric: tabular-nums;
  padding: 0;
  border-radius: 50%;
  cursor: pointer;
}

.calendar-day-number:hover {
  background: var(--sidebar-hover);
}

/* On a touch screen every button is given a comfortable height, and that made
   this one taller without making it wider: the mark on today came out an oval.
   A target has two dimensions -- the same reason the toast's cross and the
   conversation star are widened -- and a circle is what a calendar draws round
   today. Here rather than beside those, because it has to win against the size
   set above it, and between two rules of the same weight the later one wins. */
@media (max-width: 760px) {
  .calendar-day-number {
    width: 40px;
    height: 40px;
  }
}

.calendar-day.today .calendar-day-number:hover {
  background: var(--accent-hover);
}

/* The entries scroll inside their own day rather than stretching the row:
   a Tuesday with nine things on it must not make every other week taller. */
.calendar-day-entries {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  overflow-y: auto;
  /* Takes what the cell has left rather than a fixed slice of it, and
     min-height:0 is what lets it shrink enough to scroll instead of
     stretching the row. */
  flex: 1;
  min-height: 0;
}

.calendar-entry {
  display: flex;
  align-items: baseline;
  gap: 0.35rem;
  width: 100%;
  text-align: left;
  border: 1px solid color-mix(in srgb, var(--leaf) 35%, transparent);
  background: color-mix(in srgb, var(--leaf) 14%, var(--surface));
  color: var(--text);
  border-radius: 5px;
  padding: 0.15rem 0.35rem;
  font: inherit;
  font-size: 0.8rem;
  cursor: pointer;
}

.calendar-entry:hover {
  border-color: var(--border-strong);
}

.calendar-entry.busy {
  opacity: 0.6;
}

/* Whole-day things read as the same kind of thing as the rest, tinted the
   same way; they are told apart by sitting in the all-day strip and by the
   solid edge, not by being a different colour entirely. */
.calendar-entry.all-day {
  background: color-mix(in srgb, var(--leaf) 22%, var(--surface));
  border-color: color-mix(in srgb, var(--leaf) 45%, transparent);
  border-left: 3px solid var(--leaf);
}

/* The lock on the person's own private events: small, in the text color's
   muted shade, and sitting on the line with the title. */
.calendar-lock {
  display: inline-flex;
  margin-right: 0.25rem;
  vertical-align: -1px;
  color: var(--muted);
}

.calendar-entry-time {
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  font-size: 0.75rem;
  flex: none;
}

/* A title too long for its box is cut rather than wrapped, so a day with one
   long title stays the height of a day with one short one. */
.calendar-entry-title {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Calendars other than the person's own: a circle's, and the ones shared with
   them. Each is told apart by a color, which every entry takes through the
   --leaf the rules above already read, so a shared calendar needs no rules of
   its own. The caller's own calendar keeps the accent. */
.calendar-list {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem 1rem;
  margin: 0.5rem 0 0;
}

.calendar-chip {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  /* No label margin: the row's own gap spaces them, and on a phone the
     margin and a 40 pixel tick box made each chip a row of its own. */
  margin: 0;
  font-size: 0.85rem;
  cursor: pointer;
}

.calendar-chip input {
  /* A tick box is not a text field, so the phone's thumb-sized height for
     inputs does not apply to it. */
  width: auto;
  min-height: 0;
  margin: 0;
}

.calendar-chip-swatch {
  flex: none;
  width: 0.7rem;
  height: 0.7rem;
  border-radius: 3px;
  background: var(--leaf);
}

.calendar-chip-level {
  font-size: 0.75rem;
}

.calendar-bar-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}

.calendar-tone-0 {
  --leaf: var(--avatar-0);
}
.calendar-tone-1 {
  --leaf: var(--avatar-1);
}
.calendar-tone-2 {
  --leaf: var(--avatar-2);
}
.calendar-tone-3 {
  --leaf: var(--avatar-3);
}
.calendar-tone-4 {
  --leaf: var(--avatar-4);
}
.calendar-tone-5 {
  --leaf: var(--avatar-5);
}
.calendar-tone-6 {
  --leaf: var(--avatar-6);
}
.calendar-tone-7 {
  --leaf: var(--avatar-7);
}

/* When someone is busy and nothing else is shared: a gray block that says so
   and cannot be opened, since there is nothing in it. */
.calendar-busy-block {
  --leaf: var(--muted);
  cursor: default;
  font-style: italic;
}

/* A busy block says whose it is in words that may be longer than the block is
   wide ("Busy · teen"); when it is tall enough for a second line they wrap
   instead of being cut to "Busy · t…". */
.calendar-placed.calendar-busy-block:not(.compact) .calendar-placed-title {
  overflow-wrap: anywhere;
  white-space: normal;
}

.calendar-placed.calendar-busy-block:hover {
  background: color-mix(in srgb, var(--leaf) 18%, var(--surface));
}

.calendar-shares-heading,
.recording-shares-heading {
  margin: var(--space-3) 0 0;
}

.calendar-shares,
.recording-shares {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  margin: 0.5rem 0 var(--space-4);
}

.calendar-share-row,
.recording-share-row {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.calendar-share-name,
.recording-share-name {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The level of a share, which is changed where it is shown: as wide as its
   words, and narrower than that on a phone, where the name is cut first. */
.calendar-share-row select {
  flex: 0 1 12rem;
  width: auto;
  min-width: 0;
}

/* A day with its free stretches: the day on its own line, the times under it
   and wrapping as they need to. In the row of a share the name takes what the
   rest leaves and is cut, which cut a day to an ellipsis once a day had
   several stretches. */
.calendar-free-day {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.calendar-free-day-name {
  font-weight: 600;
}

/* Finding a time together: the people who shared a calendar, one to a line,
   then the days and the hours as two columns of fields. */
.calendar-find-people {
  display: flex;
  flex-direction: column;
  margin-bottom: var(--space-4);
}

/* The rows of people are the dialog's own checkbox rows, at the size and the
   gap of the fields beside them rather than twice the height. */
.calendar-find-people label.checkbox {
  margin: var(--space-1) 0 0;
}

.calendar-find-people > .muted {
  margin-top: var(--space-2);
}

.calendar-find-range {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-3);
}

.calendar-viewed p {
  margin: 0.25rem 0;
}

.calendar-viewed-notes {
  white-space: pre-wrap;
}

/* The page fills the area it is given and does not grow past it.
   The shell already has one scroller -- the content column -- and a grid with
   its own height inside it made two nested ones: you scrolled the grid, hit
   its end, and then the page moved. One scrollbar, on the thing with more to
   show than fits. */
.calendar-page {
  display: flex;
  flex-direction: column;
  min-height: 0;
  /* The height is measured and set from the page itself; see the comment
     there. A percentage cannot see the breadcrumb and the title it sits
     below, and overshoots by exactly their height. */
}

.calendar-page > .tabs,
.calendar-page > .card,
.calendar-page > .calendar-bar {
  flex-shrink: 0;
}

/* A pending change takes space above the grid; keep the controls visible and
   let all six weeks remain reachable in the remaining height. */
.calendar-month-scroll {
  flex: 1;
  min-height: 0;
  overflow: auto;
}

/* The time grid: hours down the side, a column per day, and events placed
   where they actually fall. A calendar that lists a day's events in order
   tells you what is on; one that places them tells you what your day looks
   like, which is the question people open a calendar to answer. */
.calendar-grid-scroll {
  border: 1px solid var(--border);
  border-radius: 8px;
  overflow: auto;
  /* Takes what the page has left rather than a guessed fraction of the
     window; min-height:0 is what lets a flex child shrink enough to scroll. */
  flex: 1;
  min-height: 0;
  background: var(--surface);
}

.calendar-grid {
  display: grid;
  /* The columns are set from the view: a gutter, then one per day. */
  position: relative;
}

/* The day names stay visible while the hours scroll under them.
   Above everything the hours carry, the line marking now included: that
   line is drawn over the events on purpose, and painting it over the date
   as well put a red rule through "SUN 13" whenever now was earlier than
   the hour the grid had been scrolled to. */
.calendar-grid-head,
.calendar-grid-corner {
  position: sticky;
  top: 0;
  z-index: 5;
  background: var(--rail);
  border-bottom: 1px solid var(--border);
}

.calendar-grid-corner {
  border-right: 1px solid var(--border);
}

.calendar-grid-head {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.1rem;
  padding: 0.4rem 0.25rem;
  border: none;
  border-bottom: 1px solid var(--border);
  border-left: 1px solid var(--border);
  color: var(--text);
  font: inherit;
  cursor: pointer;
}

.calendar-grid-head:hover {
  background: var(--sidebar-hover);
}

.calendar-grid-weekday {
  font-size: 0.7rem;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--muted);
}

/* A square box with the date centred in it, so the mark on today below is a
   circle rather than a pill whose shape depends on how many digits the date
   has. */
.calendar-grid-date {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.9rem;
  height: 1.9rem;
  font-size: 1.15rem;
  font-variant-numeric: tabular-nums;
  line-height: 1.1;
}

.calendar-grid-head.today .calendar-grid-date {
  background: var(--accent);
  color: var(--accent-text);
  border-radius: 50%;
}

.calendar-grid-head.today .calendar-grid-weekday {
  color: var(--text);
}

.calendar-grid-allday-label {
  font-size: 0.7rem;
  color: var(--muted);
  padding: 0.3rem 0.4rem;
  text-align: right;
  position: sticky;
  top: var(--calendar-head, 3.4rem);
  z-index: 4;
}

.calendar-grid-allday {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  padding: 0.25rem;
  border-left: 1px solid var(--border);
  border-bottom: 1px solid var(--border);
  background: var(--surface);
  position: sticky;
  top: var(--calendar-head, 3.4rem);
  /* Sticky like the row above it, and over the hours for the same reason:
     what stays put has to cover what scrolls under it. */
  z-index: 4;
}

.calendar-hours {
  border-right: 1px solid var(--border);
}

/* The label sits at the line it names rather than inside the hour below it,
   which is what makes it read as "this is where nine o'clock is". */
.calendar-hour {
  position: relative;
  font-size: 0.7rem;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

.calendar-hour span {
  position: absolute;
  top: -0.55rem;
  right: 0.4rem;
}

.calendar-column {
  position: relative;
  border-left: 1px solid var(--border);
}

.calendar-column.today {
  background: color-mix(in srgb, var(--accent) 4%, transparent);
}

.calendar-slot {
  border-bottom: 1px solid var(--border);
  /* The pointer says the empty space is something you can use. */
  cursor: pointer;
}

.calendar-slot:hover {
  background: var(--sidebar-hover);
}

/* An event sits where it starts and is as tall as it lasts.
   Coloured rather than grey: on a grid of faint hour lines an event has to
   read as a thing on the page, and the accent here is deliberately not a
   colour, so the leaf is what there is. */
.calendar-placed {
  position: absolute;
  display: flex;
  flex-direction: column;
  gap: 0.05rem;
  overflow: hidden;
  text-align: left;
  padding: 0.15rem 0.3rem;
  border: 1px solid color-mix(in srgb, var(--leaf) 45%, transparent);
  border-left: 3px solid var(--leaf);
  border-radius: 4px;
  background: color-mix(in srgb, var(--leaf) 18%, var(--surface));
  color: var(--text);
  font: inherit;
  font-size: 0.75rem;
  line-height: 1.2;
  cursor: pointer;
}

.calendar-placed:hover {
  background: color-mix(in srgb, var(--leaf) 30%, var(--surface));
}

/* Too short for two lines: the time and the title share one, because a
   half-hour meeting that shows only "09:00" has lost the one thing it had
   to say. */
.calendar-placed.compact {
  flex-direction: row;
  align-items: baseline;
  gap: 0.3rem;
  padding-top: 0.05rem;
}

.calendar-placed.compact .calendar-placed-title {
  min-width: 0;
}

.calendar-placed.busy {
  opacity: 0.6;
}

.calendar-placed.cancelled .calendar-placed-title {
  text-decoration: line-through;
}

.calendar-placed-time {
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  font-size: 0.7rem;
}

.calendar-placed-title {
  font-weight: 500;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.calendar-placed-where {
  color: var(--muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Where we are now. Drawn over the events so it is not buried by a meeting
   that is happening, which is exactly when somebody looks for it -- and
   under the day names and the whole-day row, which stay put while it
   scrolls past them. */
.calendar-now {
  position: absolute;
  left: 0;
  right: 0;
  height: 0;
  border-top: 2px solid #d1453b;
  z-index: 3;
  pointer-events: none;
}

.calendar-now::before {
  content: '';
  position: absolute;
  left: -4px;
  top: -5px;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: #d1453b;
}

.calendar-agenda {
  display: flex;
  flex-direction: column;
  border: 1px solid var(--border);
  border-radius: 8px;
  overflow-y: auto;
  flex: 1;
  min-height: 0;
}

/* A line of text where a list would be: an empty agenda, or the wait before
   one arrives. Centred in the space the list would have filled, because the
   agenda fills the page and a sentence pinned to the top-left corner of a
   tall empty box reads as something that failed to load. */
.calendar-notice {
  display: flex;
  flex: 1;
  align-items: center;
  justify-content: center;
  min-height: 120px;
  margin: 0;
  padding: var(--space-6) var(--space-5);
  text-align: center;
}

.calendar-agenda-row {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.5rem 0.75rem;
  background: var(--surface);
  border-bottom: 1px solid var(--border);
}

.calendar-agenda-row:last-child {
  border-bottom: none;
}

.calendar-agenda-when {
  display: flex;
  flex-direction: column;
  width: 9rem;
  flex: none;
  font-variant-numeric: tabular-nums;
}

.calendar-agenda-what {
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
  flex: 1;
  min-width: 0;
}

.calendar-agenda-title {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* A number and the word for what it counts, side by side: "Every [2] weeks". */
.repeat-interval {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

.repeat-interval input {
  flex: none;
  width: 6rem;
}

/* The sentence a custom repeat makes, with the button that changes it at the
   end of the line, or under it when the line is short of room. */
.calendar-repeat-summary {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-2);
  margin-bottom: var(--space-3);
}

.calendar-repeat-summary p {
  margin: 0;
}

.calendar-repeat-summary > div {
  flex: 1 1 14rem;
}

.field-row {
  display: flex;
  gap: 0.75rem;
}

.field-row > label {
  flex: 1;
}

/* On a phone the month grid keeps its seven columns -- a month restacked into
   a list is not a month -- and scrolls sideways instead, wide enough that a
   day is still a day. The day view and the agenda are the ones worth reaching
   for on a phone, and neither needs sideways room. */
@media (max-width: 600px) {
  /* Seven columns of a phone's width cannot hold a chip with a time and a
     title, so an event is a dot in its calendar's color here and the day,
     which a press on it opens, writes them out. */
  .calendar-month {
    grid-template-rows: auto repeat(6, minmax(4.5rem, 1fr));
  }

  .calendar-month .calendar-day {
    min-height: 4.5rem;
    padding: 0.15rem;
    align-items: center;
  }

  .calendar-month .calendar-day-entries {
    flex-direction: row;
    flex-wrap: wrap;
    justify-content: center;
    align-content: flex-start;
    gap: 3px;
    /* Two rows of dots at most: the day has the rest. */
    max-height: 22px;
    overflow: hidden;
    pointer-events: none;
  }

  .calendar-month .calendar-entry {
    flex: none;
    width: 8px;
    height: 8px;
    min-width: 0;
    min-height: 0;
    padding: 0;
    border: none;
    border-radius: 50%;
    background: var(--leaf);
    overflow: hidden;
    font-size: 0;
  }

  .calendar-month .calendar-entry.calendar-busy-block {
    background: var(--muted);
    opacity: 0.5;
  }

  .calendar-month .calendar-entry > * {
    display: none;
  }

  .calendar-month .calendar-weekday {
    padding: 0.4rem 0;
    letter-spacing: 0;
  }

  .calendar-grid-scroll {
    max-height: min(64vh, 40rem);
  }

  .calendar-agenda-when {
    width: 7rem;
  }

  /* A row of five tabs and a button does not fit; the tab strip already
     scrolls sideways, so the action goes under it rather than squeezing it. */
  .calendar-bar {
    gap: 0.5rem;
  }
}

/* The invitation card, above a message that carried one. */
.invitation-card,
.invitation-note {
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--rail);
  padding: 0.75rem 0.9rem;
  margin: 0 0 0.75rem;
}

.invitation-note {
  display: flex;
  align-items: baseline;
  gap: 0.6rem;
}

/* The card the agent offers: what a message carries that belongs somewhere
   else, with the line it was read from underneath.
   
   The pieces below are a heading, a small form, the addresses and numbers
   that were found, the quoted line, and the two buttons — five things with
   nothing between them but a label's own ten pixels, which on a phone read
   as one block of text with boxes in it. Each of the three that follow the
   form is given the room that says it is a separate thing. */
.invitation-card .form-narrow > p {
  margin: -4px 0 0;
}

.invitation-card blockquote {
  margin: 0.8rem 0 0;
  padding-left: 0.7rem;
  border-left: 2px solid var(--border-strong);
  font-size: 13px;
  line-height: 1.45;
}

.invitation-card .page-actions-end {
  margin-top: 0.9rem;
}

.invitation-card.cancelled .invitation-summary {
  text-decoration: line-through;
  text-decoration-color: var(--border-strong);
}

.invitation-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.6rem;
}

.invitation-mark {
  font-size: 0.7rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
}

.invitation-off {
  font-size: 0.75rem;
  color: var(--muted);
}

.invitation-summary {
  margin: 0.3rem 0 0.5rem;
  font-size: 1rem;
}

/* Label and value in two columns, so the eye runs down the values rather
   than hunting for them between labels of different widths. */
.invitation-detail {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 0.15rem 0.75rem;
  margin: 0;
}

.invitation-detail dt {
  color: var(--muted);
  font-size: 0.8rem;
}

.invitation-detail dd {
  margin: 0;
  min-width: 0;
  overflow-wrap: anywhere;
}

/* One guest a line, so a long list reads as a list rather than as a
   paragraph of names. */
.invitation-who {
  margin: 0;
  padding: 0;
  list-style: none;
}

.invitation-answers {
  display: flex;
  gap: 0.4rem;
  margin-top: 0.75rem;
}

.invitation-answers .chosen {
  background: var(--accent);
  color: var(--accent-text);
  border-color: var(--accent);
}

.invitation-standing {
  margin: 0.5rem 0 0;
  font-size: 0.8rem;
}

@media (max-width: 480px) {
  /* One column on a phone: two columns of a label and a value each become
     two words a line, which is unreadable long before it is compact. */
  .invitation-detail {
    grid-template-columns: 1fr;
  }

  .invitation-detail dt {
    margin-top: 0.35rem;
  }

  /* One guest a line, so a long list reads as a list rather than as a
   paragraph of names. */
  .invitation-who {
    margin: 0;
    padding: 0;
    list-style: none;
  }

  .invitation-answers {
    flex-wrap: wrap;
  }
}

/* On a touch screen the text in a field is never under 16px.

   Safari on iOS zooms the whole page when a field it is focusing has text
   smaller than that, and it zooms until the field fills the width -- which
   is why tapping the ask box pulled the page in to about the width of the
   box, and why it looked like the box's own doing. It is the type size, not
   the width: at 16px there is nothing to zoom for.

   Last in the file on purpose. A rule for the same field written later in
   the sheet wins at equal specificity, and the drawer's own 14px for its
   textarea sat four thousand lines below the first version of this -- so
   the first version changed nothing, and the page went on zooming. The
   selectors that are more specific than a bare tag are named here so that
   they are beaten too. Nothing changes on a mouse, where the 13px and 14px
   the dashboard is drawn at are the point. */
@media (pointer: coarse) {
  input,
  select,
  textarea,
  .select,
  .select-search,
  .agent-drawer-input textarea,
  .list-toolbar input[type='search'],
  .mailbox-actions select,
  .mailbox-pane-actions select,
  .mailbox-narrowing select,
  label:has(> input[type='radio']) > input {
    font-size: 16px;
  }
}

/* What the agent knows.

   Two columns on a wide window: the navigator, which is one folder of
   the hierarchy at a time, and the page it is showing. On a phone they
   show one at a time, the navigator first, because choosing what to read
   comes before reading it. The navigator scrolls inside its own panel so
   that a folder of six hundred pages does not make the whole page a
   kilometre long. */
.knowledge-frame {
  container-type: inline-size;
  container-name: knowledge;
  /* It takes the height left on the page rather than growing to its
     content. The rows below have overflow-y: auto, but a scroller only
     scrolls when something above it says how tall it may be, and nothing
     did: a folder of five hundred pages made this thirty six thousand
     pixels tall, so the rows never overflowed, never scrolled, and the
     overscroll-behavior that stops the page moving underneath them
     swallowed the wheel instead. Neither the list nor the page moved. */
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
}

/* The page this frame sits on becomes the column that bounds it. Only
   that page: every other one is a document that grows and is scrolled as
   a whole, which is what a block container already does. */
main.content:has(> .knowledge-frame) {
  display: flex;
  flex-direction: column;
}

/* The same frame the mail list and its reading pane are drawn in, because it
   is the same thing: a list down one side and what is open beside it. It was
   two rounded cards with a gap between them, which read as two unrelated
   panels on a page that is one thing, and meant a second set of rules for a
   layout the mailbox already had. */
.knowledge-split {
  display: grid;
  /* The page beside it has a real minimum: below it a paragraph turns into
     one word per line, which is what happened when the last column was left
     to take whatever was over. */
  grid-template-columns: minmax(280px, 380px) minmax(360px, 1fr);
  gap: 0;
  /* Fills the frame rather than standing on a minimum of its own. The
     minimum was all that was ever said about its height, so it grew to
     whatever the longer of its two columns needed. */
  flex: 1 1 auto;
  min-height: 0;
  border: 1px solid var(--border);
  border-radius: 8px;
  overflow: hidden;
  background: var(--surface);
}

/* The list down the left: a toolbar across the top of it and the rows
   scrolling underneath, so walking down a long folder does not carry the
   search box away with it. */
.knowledge-list {
  display: flex;
  flex-direction: column;
  min-width: 0;
  /* A grid item refuses to shrink below its content without this, the
     same trap the layout's own comment warns about for flex items, and
     the rows below would take their height from the whole folder again. */
  min-height: 0;
  border-right: 1px solid var(--border);
}

.knowledge-list-rows {
  /* Between the toolbar's own rule above and the frame's edge below, which
     is where the lines come from now. The rows used to be cut square through
     whatever one met the edge of a card, with nothing saying there was more.

     It takes the height left over, as the mail rows do, so the list scrolls
     inside the frame while the toolbar above it stays put, and a folder of
     six hundred pages does not make the whole page a kilometre long. */
  flex: 1 1 auto;
  overflow-y: auto;
  /* No padding of its own. It had some, and with the toolbar's padding above
     it and the row's own inside it, three of them stacked into a finger's
     width of nothing between the trail and the first row. The mail list has
     none here for the same reason. */
  padding: 0;
  /* The page behind does not start moving when this list reaches its end,
     which on a phone took the whole screen with it. */
  overscroll-behavior: contain;
}

/* The bar itself, as thin and as quiet as the one in the drawer.
   
   The rows run to the edge of the list, and a scrollbar takes its width out
   of the room they have: the highlight on a row stops where the bar starts,
   and against the default bar's own track that reads as the highlight
   falling short rather than as a scrollbar being there. A transparent track
   leaves the list's own ground beside the row instead of a second colour,
   and a thin bar takes less of the row to begin with. The rest of the gap
   is the scrollbar, and a classic scrollbar is outside what a row can
   paint. */
/* The wait, with room around it and in the middle of what it is waiting
   in. It was a bare line of text, so it took the alignment and the padding
   of whatever held it: in a list whose rows run to the edges, and which
   therefore has no padding of its own, it sat in the top corner. */
.loading {
  padding: var(--space-4);
  text-align: center;
}

.knowledge-list-rows {
  scrollbar-width: thin;
  scrollbar-color: var(--border) transparent;
}

.knowledge-list-rows::-webkit-scrollbar {
  width: 8px;
}

.knowledge-list-rows::-webkit-scrollbar-track {
  background: transparent;
}

.knowledge-list-rows::-webkit-scrollbar-thumb {
  border-radius: 4px;
  background: var(--border);
}

.knowledge-list-rows::-webkit-scrollbar-thumb:hover {
  background: var(--muted);
}

/* The name of the page being read, kept at the top of the pane while the
   rest of it scrolls under. Lifted out of the section below rather than
   pinned inside it: a sticky thing stays only within the box that holds
   it, and that box ended with the first section. */
.knowledge-page-head {
  position: sticky;
  /* Flush with the top of the pane, not with the top of its padding. A
     sticky offset is measured from the scrollport's padding box, so top: 0
     parked this sixteen pixels down and left a channel above it that the
     page scrolled through in full view. */
  top: calc(var(--space-4) * -1);
  z-index: 2;
  margin: calc(var(--space-4) * -1) calc(var(--space-4) * -1) var(--space-3);
  /* The same room above the name as below the row under it. The pane's
     own padding above and a smaller pad below left the name sitting
     nearer one line than the other. */
  padding: var(--space-4);
  background: var(--surface);
  border-bottom: 1px solid var(--border);
}

.knowledge-page-head h3 {
  margin: 0;
}

/* What the page is and what can be done to it, on one line under its
   name and pinned with it: they belong to the page rather than to the
   first thing written on it, and scrolling them away left no way to act
   on the page being read. */
.knowledge-page-head-meta {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  margin-top: var(--space-2);
}

.knowledge-pane {
  min-width: 0;
  overflow: auto;
  padding: var(--space-4);
}

/* What is read in the pane is a page, not a settings form.
   
   The pane is already inside the frame, and the frame already has a line
   around it and a corner on it. A card inside that is a box inside a box,
   and the rows inside the card are a third, which is what the pane was:
   five hundred bordered, rounded, filled things in one column. The mail
   reading pane beside it has none of them, because a thing being read does
   not need a container to say where it starts; a heading says that, and a
   hairline says where the last one ended. */
.knowledge-pane .settings-section.card {
  --card-padding-inline: 0;
  border: 0;
  border-radius: 0;
  background: none;
  padding-left: 0;
  padding-right: 0;
}

.knowledge-pane .settings-section.card + .settings-section.card {
  border-top: 1px solid var(--border);
}

/* On a phone a card is pulled out to the page's edges and so is a row in it,
   which is right on a page and 12 pixels too wide inside a pane that already
   has its own padding: the text was cut at the left once the pane scrolled. */
.knowledge-pane .settings-section.card,
.knowledge-pane .settings-row {
  margin-left: 0;
  margin-right: 0;
}

/* The same for what is inside them: a fact and a link are lines of a page. */
.knowledge-pane .settings-row,
.knowledge-pane .settings-empty {
  border: 0;
  border-radius: 0;
  background: none;
  padding-left: 0;
  padding-right: 0;
}

/* One column at a time once there is not room for two, which is the frame's
   own business rather than something to measure in script. */
@container knowledge (max-width: 860px) {
  .knowledge-split {
    grid-template-columns: minmax(0, 1fr);
  }
  .knowledge-split .knowledge-list {
    border-right: 0;
  }
  .knowledge-split.reading .knowledge-list {
    display: none;
  }
  .knowledge-split:not(.reading) .knowledge-pane {
    display: none;
  }
}


/* Nothing of its own: the box and the ways in share a row, which is what
   the mail list does and what the shared toolbar already arranges. The box
   took a row to itself for a while, because as a question and a phrase the
   ways in filled the toolbar and left nowhere for it. The labels are a word
   each now, so there is room and no override is needed.

   If a label ever grows back, the toolbar wraps rather than overflowing,
   and the fix is the wording, not a rule here. */

.knowledge-page {
  min-width: 0;
}

/* The hierarchy, one folder at a time.

   The header says which folder is open and is the way out of it; the
   frame below holds the list. A tree of no fixed depth cannot be a
   column per level -- the window runs out at three -- so it is one
   column walked into, and the slide is what says which way you went. */
.knowledge-navigator {
  min-width: 0;
}

/* Where you are, above the rows rather than among them. It scrolled away
   with them until now, which left a long folder with no way out of it
   until you had scrolled back to the top. */
.knowledge-trail {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  /* The toolbar's own padding, since it stands in the toolbar's place and
     carries the same rule underneath. It used to add a bottom pad on top of
     that, which was half the gap between the trail and the first row. */
  padding: var(--space-2);
  color: var(--muted);
  font-size: var(--text-label);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

/* It stands where the toolbar stands when it is the row in its place, so
   the column's first row is the same height whichever of the two is in it
   and the list below does not move as you walk in and out of folders. */
.knowledge-trail.list-toolbar {
  min-height: var(--space-7);
  /* Not wrapped. The chevron and the levels are small and fixed, and the
     name is the only thing that can be long: letting the row wrap put the
     name on a line of its own and left the first line holding two glyphs
     and a gap. It wraps inside its own box instead, which is what
     overflow-wrap on the name is for. */
  flex-wrap: nowrap;
  align-items: flex-start;
}

/* The two controls keep their place beside a name that has wrapped, rather
   than stretching down the side of it. */
.knowledge-trail.list-toolbar > .icon-action,
.knowledge-trail.list-toolbar > .tooltip-anchor > .icon-action,
.knowledge-trail-levels {
  flex: none;
  align-self: flex-start;
}


/* The back button is where the chevron on the row that led here was, so
   the two read as the same door from both sides. */
.knowledge-trail .icon-action {
  margin-left: calc(var(--space-2) * -1);
}

/* The levels above the one open. An ellipsis and nothing else, because the
   row has to cost the same whether the folder is one deep or four: the names
   in it run to sixty five characters, so four levels written out is two
   hundred, which no width has room for and a phone least of all. Every level
   is in the menu behind it. */
.knowledge-trail-levels {
  flex: none;
  /* The same box the chevron beside it has, so the two sit level. An
     ellipsis is a baseline glyph and sat low against an icon that is
     centred in its own square. */
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 28px;
  border: none;
  background: none;
  padding: 0 var(--space-1);
  color: var(--muted);
  font: inherit;
  line-height: 1;
  cursor: pointer;
}

.knowledge-trail-levels:hover {
  color: var(--text);
}

/* The folder you are in, with the rest of the row to itself. It is the one
   name here worth the width, so it wraps rather than being cut. */
.knowledge-trail-name {
  /* Level with the chevron beside it. The row is aligned to the top so
     that the two controls stay put when a long name wraps, and aligning
     the top of a twenty eight pixel control against an eighteen pixel
     line of text left the text five pixels high. Half the difference puts
     the first line back on the chevron's centre, and the lines after it
     still flow below. */
  padding-top: calc((28px - 18px) / 2);
  flex: 1 1 auto;
  min-width: 0;
  overflow-wrap: anywhere;
  color: var(--text);
}

/* Two lists live here while one is replacing the other. The one arriving
   is the one taken out of the flow, so the panel keeps the height of the
   list being left until the slide is over: the other way round, a list
   that had not answered yet collapsed the card to the height of the word
   "loading" and grew it back a tenth of a second later. */
.knowledge-navigator-frame {
  position: relative;
  overflow: hidden;
}

/* The list coming in is the one in the flow, so the frame is its height
   from the first frame of the slide; the list going out is lifted over
   it. It used to be the other way round, and a tall folder sliding in
   behind a short one was cut to the short one's height and then jumped,
   and a short one sliding in behind a tall one left the tall height
   standing empty under it. */
.knowledge-navigator-panel.entering,
.knowledge-navigator-panel.leaving {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
}

.knowledge-navigator-panel.leaving {
  /* It is already gone as far as the person is concerned; a press that
     landed on it while it slid opened something they had not aimed at. */
  pointer-events: none;
}

/* Animations rather than transitions: a list that has just been mounted
   has no previous position to transition from, and the two frames of
   nothing that a transition needs to be given one showed as a flicker. */
.knowledge-navigator-panel.entering.deeper {
  animation: knowledge-slide-in-right 200ms ease-out;
}

.knowledge-navigator-panel.leaving.deeper {
  animation: knowledge-slide-out-left 200ms ease-out both;
}

.knowledge-navigator-panel.entering.back {
  animation: knowledge-slide-in-left 200ms ease-out;
}

.knowledge-navigator-panel.leaving.back {
  animation: knowledge-slide-out-right 200ms ease-out both;
}

@keyframes knowledge-slide-in-right {
  from {
    transform: translateX(100%);
  }
}

@keyframes knowledge-slide-out-left {
  to {
    transform: translateX(-100%);
    opacity: 0;
  }
}

@keyframes knowledge-slide-in-left {
  from {
    transform: translateX(-100%);
  }
}

@keyframes knowledge-slide-out-right {
  to {
    transform: translateX(100%);
    opacity: 0;
  }
}

.knowledge-rows {
  list-style: none;
  margin: 0;
  padding: 0;
}

/* A row is a name, one line to tell it from the row above, and a count
   or a chevron on the right. Whole row lights up; nothing underlined. */
.knowledge-row {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  width: 100%;
  text-align: left;
  padding: var(--space-2) var(--space-3);
  border: none;
  /* Divided from the row under it rather than rounded and spaced away from
     it, which is how the mail list reads and why it can be dense without
     looking crowded. */
  border-bottom: 1px solid var(--border);
  border-radius: 0;
  background: none;
  color: var(--text);
  font: inherit;
  cursor: pointer;
  /* A row is pressed, not read: a second click on it selected its count. */
  user-select: none;
}

/* Nothing under the last one: the frame's own edge is there. */
.knowledge-row:last-child {
  border-bottom: 0;
}

.knowledge-row:hover:not(:disabled),
.knowledge-row.selected {
  background: var(--field);
}

.knowledge-row.selected .knowledge-row-name {
  font-weight: 600;
}

/* In the navigator the row is not one button but two: the name opens the
   page, the chevron walks into what is filed under it. They share the
   row's own lit-up background, so it still reads as one row. */
/* The row's padding belongs to its buttons, not to the row: with it on
   the row, a tap on the row's edge landed on nothing, and the ripple showed
   the button ending a finger short of where the row did. */
.knowledge-row:has(.knowledge-row-open) {
  padding: 0;
  gap: 0;
}

.knowledge-row-open {
  flex: 1;
  min-width: 0;
  display: flex;
  align-self: stretch;
  align-items: center;
  padding: var(--space-2) var(--space-3);
  border: none;
  border-radius: 0;
  background: none;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.knowledge-row-into {
  flex: none;
  display: flex;
  align-self: stretch;
  align-items: center;
  /* As tall as the row and as wide as a finger: the chevron's target is
     the whole right edge, not the glyph. */
  padding: 0 var(--space-3);
  border: none;
  border-radius: 0;
  background: none;
  color: var(--muted);
  cursor: pointer;
}

.knowledge-row-into:hover {
  color: var(--text);
}

.knowledge-row-text {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 1px;
}

.knowledge-row-name {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  overflow-wrap: anywhere;
}

.knowledge-row-hint {
  color: var(--muted);
  font-size: var(--text-label);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.knowledge-row-count {
  margin-left: auto;
  padding-right: var(--space-2);
  color: var(--muted);
  font-size: var(--text-label);
  font-variant-numeric: tabular-nums;
}

.knowledge-row > svg {
  flex: none;
  color: var(--muted);
}

.knowledge-list-heading {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin: var(--space-2) var(--space-3);
  color: var(--muted);
  font-size: var(--text-label);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

/* What other people shared with the person: under the folders at the top of
   the list, one group for each person, their picture and name over a row for
   each page they shared. The rows are the list's own; they are links here
   because a shared page is opened by address, with the owner in it. */
.knowledge-shared {
  border-top: 1px solid var(--border);
  padding-bottom: var(--space-2);
}

.knowledge-shared-person {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-3) var(--space-1);
  color: var(--muted);
  font-size: var(--text-label);
}

/* The same mark beside a page's own tags, where it is the owner's badge. */
.knowledge-page-head-meta .knowledge-shared-person {
  padding: 0;
}

.knowledge-shared a.knowledge-row {
  text-decoration: none;
}

/* No result of the search of what is shared: inset like the rows, not a box
   flush to both edges of the list. */
.knowledge-shared .settings-empty {
  margin: var(--space-2) var(--space-3);
  min-height: 80px;
}

/* The line a person whose agent is off reads where the list of their own
   pages would start. */
.knowledge-agent-off {
  margin: 0;
  padding: var(--space-3);
  border-bottom: 1px solid var(--border);
}

/* What the other side will see, in the dialog that shares a page: the page as
   they read it, in a box of its own that scrolls so the choice above it and
   the button under it stay in view. */
.share-preview {
  margin-top: var(--space-3);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: var(--space-3);
  max-height: 40vh;
  overflow-y: auto;
}

.share-preview h4 {
  margin: 0 0 var(--space-2);
}

/* The pages a share reaches, one path a line. */
.share-reach {
  margin: 0 0 var(--space-2);
  padding-left: var(--space-4);
  overflow-wrap: anywhere;
}

.share-preview .settings-section.card {
  border: 0;
  padding: 0;
  margin-top: var(--space-3);
  /* Not pulled out to the box's edge, as a card is on a phone: the body is
     inset like the heading above it. */
  margin-left: 0;
  margin-right: 0;
}

.share-preview .settings-section-head {
  margin-bottom: var(--space-2);
}

.share-preview .settings-row {
  margin-left: 0;
  margin-right: 0;
  padding-left: 0;
  padding-right: 0;
  border: 0;
  background: none;
}

/* A fact the agent folded into another. Greyed rather than hidden: it is
   still there, and the number beside it is where its words went. */
.knowledge-folded {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-1) var(--space-3);
  color: var(--muted);
  font-size: var(--text-label);
}

.knowledge-folded-into {
  flex: none;
  font-variant-numeric: tabular-nums;
}

/* One column at a time on a phone. The way back is a real navigation --
   it changes the URL -- so the browser's Back does the same thing. */
/* A path is one word to the browser -- projects/210236-a-long-project-slug
   eastvale-single-sku-unstack-to-convey -- and a tag does not wrap,
   so a long one ran out of the card. It breaks wherever it has to. */
.knowledge-path {
  white-space: normal;
  overflow-wrap: anywhere;
  max-width: 100%;
}

.knowledge-page h3,
.page-heading,
.breadcrumb .crumb {
  overflow-wrap: anywhere;
}

/* A page is prose the nightly run wrote, so it is read rather than
   scanned: the measure is capped where the panel is not. */
.knowledge-summary {
  max-width: var(--measure);
  overflow-wrap: break-word;
}

/* The words a fact came from, shown under it. Quiet, because the fact is
   what is being read and this is why it can be believed. */
.knowledge-quote {
  display: block;
  color: var(--muted);
  font-style: italic;
  margin-bottom: 2px;
}

/* The record a fact's evidence names, opening it: a small mark of the kind
   and the title, under the fact's quote. */
.knowledge-record-evidence {
  display: inline-flex;
  margin-left: var(--space-3);
  align-items: center;
  gap: var(--space-1);
  margin-right: var(--space-3);
  max-width: 100%;
  font-size: var(--text-label);
}

.knowledge-record-evidence a {
  overflow-wrap: anywhere;
}

/* The records a page is linked to, grouped by kind: the group's heading is a
   quiet line with the kind's mark and how many, over the rows. */
.linked-records-group + .linked-records-group {
  margin-top: var(--space-3);
}

.linked-records-kind {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin: 0 0 var(--space-1);
  font-size: var(--text-label);
  font-weight: 600;
  color: var(--muted);
  text-transform: none;
}

/* A record's row keeps its one or two icons beside the title on a phone, as a
   row of icons does everywhere else, rather than wrapping them to a line of
   their own under it as a row with a dropdown must. */
.linked-records-group .settings-row-body {
  min-width: 0;
}

.linked-records-group .settings-row-actions {
  width: auto;
}

.linked-record-title {
  font-weight: 600;
}

/* In memory, on a record: the pages of the agent's memory it is linked to,
   as a wrapped line of links, with a guess marked. */
.in-memory {
  margin-top: var(--space-4);
  padding-top: var(--space-3);
  border-top: 1px solid var(--border);
}

.in-memory h4 {
  margin: 0 0 var(--space-2);
  font-size: var(--text-label);
  font-weight: 600;
  color: var(--muted);
}

.in-memory-pages {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-3);
  margin: 0;
  padding: 0;
  list-style: none;
}

.in-memory-pages li {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  max-width: 100%;
}

/* --- people and descriptions ----------------------------------------------
   A face is a round picture cut from a photo; the viewer's details gain a
   section for the people in the photo and one for its description. */
.face-picture {
  display: inline-flex;
  flex: 0 0 auto;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  border-radius: 50%;
  background: var(--field);
  color: var(--muted);
  font-weight: 600;
}

.face-picture img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.face-picture.is-small {
  width: 36px;
  height: 36px;
}

.face-picture.is-medium {
  width: 48px;
  height: 48px;
}

.face-picture.is-large {
  width: 72px;
  height: 72px;
  font-size: 28px;
}

.photo-show-hidden {
  margin: 0;
}

.photo-person-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(132px, 1fr));
  gap: var(--space-4) var(--space-3);
  margin: 0;
  padding: 0;
  list-style: none;
}

/* The tile fills its grid cell, so that the tiles of a row are one height
   whatever is under each picture. */
.photo-person-grid > li {
  display: flex;
  min-width: 0;
}

.photo-person-cell {
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
  align-items: center;
  /* The gap inside the link, so that the picture, the line and the count are
     as far apart whether they are one link or a link and two lines beside it. */
  gap: 2px;
  min-width: 0;
  padding: var(--space-2);
  border-radius: var(--radius-2);
  text-align: center;
}

.photo-person-cell:hover {
  background: var(--field);
}

/* The picture, the name and the count are one link; what is beside it (the
   tag, the way to name) is not part of it. */
.photo-person-link {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  min-width: 0;
  /* The whole width of the tile, so that the picture's percentage width is
     the tile's and not that of the longest word under it. */
  width: 100%;
  color: var(--text);
  text-decoration: none;
}

.photo-person-link:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: var(--radius-2);
}

/* A person hidden from the list, shown because Show hidden is on: the picture
   and the name dimmed, and the tag says why. */
.photo-person-cell.is-hidden .face-picture,
.photo-person-cell.is-hidden .photo-person-name,
.photo-person-cell.is-hidden .photo-person-details {
  opacity: 0.5;
}

.photo-person-name-button {
  padding: 4px 12px;
  font-size: var(--text-body);
}

/* The line under the picture is as tall as the Name button, which takes the
   place of the name on a person nobody has named, so that every tile is one
   height and naming a person does not move the rows under them. */
.photo-person-cell .photo-person-title {
  min-height: 32px;
  align-items: center;
}

/* Where every button is made 40px tall for a thumb, the line is too. */
@media (max-width: 760px) {
  .photo-person-cell .photo-person-title {
    min-height: 40px;
  }
}

.photo-person-cell .face-picture.is-large {
  width: min(100%, 112px);
  height: auto;
  aspect-ratio: 1;
  margin-bottom: var(--space-1);
  font-size: 40px;
}

/* The name and, for the person's own group, "You" after it: the name gives way
   to an ellipsis, the "You" does not. */
.photo-person-title {
  display: flex;
  align-items: baseline;
  justify-content: center;
  gap: var(--space-1);
  min-width: 0;
  max-width: 100%;
}

.photo-you {
  flex: 0 0 auto;
  font-size: var(--text-label);
  font-weight: 400;
  white-space: nowrap;
}

.photo-person-name {
  min-width: 0;
  max-width: 100%;
  overflow: hidden;
  font-weight: 600;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.photo-person-name.muted {
  font-weight: 400;
}

.photo-person-details {
  font-size: var(--text-label);
}

/* A person in the circles has the circle picture on the corner of the face
   and "In your circles" under the count. The picture is drawn over the face
   and takes no room, and when anybody in the list is in the circles every tile
   keeps two lines under its name, so the tiles stay one height. */
.photo-person-picture {
  position: relative;
  display: flex;
  width: min(100%, 112px);
  margin-bottom: var(--space-1);
}

.photo-person-cell .photo-person-picture .face-picture.is-large {
  width: 100%;
  margin-bottom: 0;
}

.photo-person-circle-badge {
  position: absolute;
  right: -2px;
  bottom: -2px;
  box-shadow: 0 0 0 2px var(--background);
}

.photo-person-details > span {
  display: block;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.photo-person-grid.has-circle-people .photo-person-details {
  min-height: calc(2 * 1.4em);
  line-height: 1.4;
}

/* The address book page's picker for which book is shown: the person's own,
   or a circle's. */
.book-toolbar {
  display: flex;
  align-items: flex-end;
  gap: var(--space-3);
  margin-bottom: var(--space-4);
}

/* The suggestions at the top of Contacts: one line for each contact, the
   person's picture, what is suggested and the two answers. On a phone the
   answers drop under the words. */
.account-suggestions {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  margin-bottom: var(--space-4);
}

.account-suggestion {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2) var(--space-3);
}

.account-suggestion-text {
  flex: 1 1 14rem;
  min-width: 0;
  overflow-wrap: anywhere;
}

.account-suggestion-actions {
  display: flex;
  flex: 0 0 auto;
  gap: var(--space-2);
}

/* In a contact's dialog: who the contact is in the circles, with the two ways
   to change it, or the one way to say it. */
.account-link {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2) var(--space-3);
}

.account-link-text {
  flex: 1 1 8rem;
  min-width: 0;
  overflow-wrap: anywhere;
}

/* Change and Remove stay together, on the line or under it. */
.account-link-actions {
  display: flex;
  gap: var(--space-2);
}

.contact-picker-row .avatar {
  flex: 0 0 auto;
}

.photo-person-head {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin-bottom: var(--space-4);
}

.photo-person-head-body {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-1) var(--space-3);
  min-width: 0;
}

.photo-person-head-body strong {
  font-size: var(--text-title);
  overflow-wrap: anywhere;
}

.photo-person-head-body strong.muted {
  font-weight: 400;
}

.photo-person-choices {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(92px, 1fr));
  gap: var(--space-2);
  max-height: 16rem;
  margin-top: var(--space-2);
  overflow-y: auto;
}

.photo-person-choice {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-1);
  min-width: 0;
  padding: var(--space-2);
  border: 1px solid transparent;
  background: transparent;
  text-align: center;
}

.photo-person-choice.is-chosen {
  border-color: var(--accent);
  background: var(--field);
}

.photo-person-choice-name {
  max-width: 100%;
  overflow: hidden;
  font-size: var(--text-label);
  text-overflow: ellipsis;
  white-space: nowrap;
}

.photo-viewer-section {
  margin-top: var(--space-4);
  padding-top: var(--space-3);
  border-top: 1px solid var(--border);
}

/* A section of the details is labeled as the rows above it are: muted and
   regular, at the label size. */
.photo-viewer-section h3 {
  margin: 0;
  color: var(--muted);
  font-size: var(--text-label);
  font-weight: 400;
}

.photo-viewer-section-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  margin-bottom: var(--space-2);
}

.photo-viewer-section > h3 {
  margin-bottom: var(--space-2);
}

.photo-viewer-people {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  margin: 0;
  padding: 0;
  list-style: none;
}

.photo-viewer-person {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

.photo-viewer-person-link {
  display: flex;
  flex: 1 1 auto;
  align-items: center;
  gap: var(--space-2);
  min-width: 0;
  color: var(--text);
  text-decoration: none;
}

/* An unnamed person is a button that opens the naming dialog: it looks like
   the link a named person is, with no button chrome of its own. */
.photo-viewer-person-name-button {
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.photo-viewer-person-link:hover .photo-viewer-person-name {
  text-decoration: underline;
}

.photo-viewer-person-name {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

@media (hover: hover) {
  /* Where there is a pointer, "Not them" waits for the pointer; on a touch
     screen there is nothing to hover with, so it is always shown. */
  .photo-viewer-person-not {
    opacity: 0;
  }

  .photo-viewer-person:hover .photo-viewer-person-not,
  .photo-viewer-person:focus-within .photo-viewer-person-not {
    opacity: 1;
  }
}

.photo-description,
.photo-description-text {
  margin: 0 0 var(--space-2);
  overflow-wrap: anywhere;
}

.photo-description-label {
  margin: var(--space-2) 0 2px;
  font-size: var(--text-label);
}

.photo-description-text {
  white-space: pre-wrap;
}

.photo-description-source {
  margin: 0;
  font-size: var(--text-label);
}

.photo-describing {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin: var(--space-2) 0 0;
}

.photo-describing .photo-spinner {
  flex: 0 0 auto;
  width: 14px;
  height: 14px;
}

.photo-viewer-section p.muted {
  margin: 0 0 var(--space-2);
}

/* Describe and Ask are the two things to do with the picture, and look it:
   one width, one weight, the whole of the panel. */
.photo-viewer-action {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  width: 100%;
  margin-top: var(--space-2);
}

.photo-viewer-action svg {
  flex: 0 0 auto;
}

.in-memory-page {
  overflow-wrap: anywhere;
}

/* The cards a page can be linked to, one to choose; a book of hundreds
   scrolls inside the dialog rather than lengthening it. */
.contact-picker {
  display: flex;
  flex-direction: column;
  max-height: min(55vh, 22rem);
  /* In a window too short for the list at that height the list is what gives
     way, down to a few rows, and the rows are what scroll. */
  min-height: 7rem;
  flex: 0 1 auto;
  overflow-y: auto;
  margin-top: var(--space-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-2);
  background:
    linear-gradient(var(--background) 30%, transparent) top / 100% 28px no-repeat local,
    linear-gradient(transparent, var(--background) 70%) bottom / 100% 28px no-repeat local,
    radial-gradient(farthest-side at 50% 0, rgb(0 0 0 / 22%), transparent) top / 100% 8px no-repeat scroll,
    radial-gradient(farthest-side at 50% 100%, rgb(0 0 0 / 22%), transparent) bottom / 100% 8px no-repeat scroll,
    var(--background);
}

/* A row of the list, the way a row of a list is drawn here: the name at the
   text size and color, what else there is to say about it under it in muted
   text, a line between. */
.contact-picker-row {
  display: flex;
  /* Rows keep the height their words need; a list that is taller than its box
     scrolls, it does not squeeze the rows into each other. */
  flex: 0 0 auto;
  align-items: center;
  gap: var(--space-3);
  margin: 0;
  padding: var(--space-2) var(--space-3);
  min-height: 48px;
  border-top: 1px solid var(--border);
  color: var(--text);
  font-size: var(--text-body);
  overflow-wrap: anywhere;
  cursor: pointer;
}

.contact-picker-row:first-child {
  border-top: none;
}

.contact-picker-row:hover {
  background: var(--sidebar-hover);
}

.contact-picker-row.is-chosen {
  background: var(--field);
}

.contact-picker label.contact-picker-row {
  margin: 0;
}

/* A tick or a radio is as big as it draws itself, whatever height a phone gives
   the fields. */
.contact-picker-row input,
label.checkbox.is-plain input {
  flex: 0 0 auto;
  min-height: 0;
  margin: 0;
}

/* The dialog that holds the list is never taller than the window: the list
   shrinks first, and what is still too tall scrolls inside the dialog with
   Cancel and Save kept in view. */
.dialog:has(.contact-picker) {
  display: flex;
  flex-direction: column;
  max-height: calc(100dvh - var(--space-4) * 2);
  overflow-y: auto;
}

.dialog:has(.contact-picker) > * {
  flex: 0 0 auto;
}

.dialog:has(.contact-picker) > .contact-picker {
  flex: 0 1 auto;
}

/* The line under the list is under it. A hint under a field pulls itself up
   to sit close to the field; under a box that scrolls it pulled up into the
   last row. */
.contact-picker + .field-hint {
  margin: var(--space-2) 0 var(--space-3);
}

.contact-picker-text {
  margin: 0;
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.contact-picker-text > span {
  margin: 0;
  color: var(--text);
  font-size: var(--text-body);
}

.contact-picker-text > .contact-picker-detail {
  color: var(--muted);
  font-size: var(--text-label);
}

.contact-picker-empty {
  margin: 0;
  padding: var(--space-3);
}

/* The dialog that holds the list is never taller than the window: the list
   shrinks first, and what is still too tall scrolls inside the dialog with
   Cancel and Save kept in view. */
.dialog:has(.contact-picker) {
  display: flex;
  flex-direction: column;
  max-height: calc(100dvh - var(--space-4) * 2);
  overflow-y: auto;
}

.dialog:has(.contact-picker) > * {
  flex: 0 0 auto;
}

.dialog:has(.contact-picker) > .contact-picker {
  flex: 0 1 auto;
}

/* The line under the list is under it. A hint under a field pulls itself up
   to sit close to the field; under a box that scrolls it pulled up into the
   last row. */
.contact-picker + .field-hint {
  margin: var(--space-2) 0 var(--space-3);
}

/* A picture or a file a fact was read out of, under the fact. Small
   enough that a page of facts is still a page of facts -- the picture
   opens into the lightbox to be looked at closely -- and narrow enough on
   a phone that it never widens the card. */
.knowledge-attachment {
  display: block;
  margin-top: var(--space-1);
}

.knowledge-attachment .muted {
  display: block;
  font-size: var(--text-label);
}

.knowledge-attachment-image {
  display: block;
  max-width: min(320px, 100%);
  max-height: 200px;
  width: auto;
  height: auto;
  border: 1px solid var(--border);
  border-radius: var(--radius-2);
  background: var(--field);
}

/* What a question would carry, inside the dialog that asks it: a page, and
   under it the facts recall would have put in front of the model. The facts
   are read rather than aimed at -- they are the answer, not a way anywhere --
   so they are prose indented under the page's name and not rows of their own,
   which on a phone would be a list of targets nobody wants to hit. */
.recall-pages {
  list-style: none;
  margin: var(--space-3) 0 0;
  padding: 0;
}

.recall-pages > li + li {
  margin-top: var(--space-3);
}

/* The page's own name, which is the heading of the facts under it: a link,
   but read before it is aimed at, so it keeps the text colour rather than
   the muted one every other text button has. */
.recall-pages > li > button.link {
  color: var(--text);
  overflow-wrap: anywhere;
}

/* The fact's number is its own marker -- it is how the page cites it, and
   what the agent says back -- so there is no disc beside it. */
.recall-facts {
  list-style: none;
  margin: var(--space-1) 0 0;
  padding-left: var(--space-3);
  color: var(--muted);
  font-size: var(--text-label);
}

.recall-facts > li + li {
  margin-top: 2px;
}

/* What the sources read, inside the dialog that searches it.

   A passage is somebody's own words -- a commit message, a chat post, a file
   -- so it keeps the text colour and its own line breaks, and everything
   around it is muted: the document's title, who wrote it, which source read
   it. Nothing here is a fixed width or a fixed height. The dialog is already
   100% of a phone's width up to 420px and scrolls with the scrim, and a
   passage of prose and a passage of code have to live in the same box. */
.document-section {
  margin: var(--space-3) 0 0;
  font-size: var(--text-label);
}

.document-definitions {
  list-style: none;
  margin: var(--space-1) 0 0;
  padding: 0;
  font-size: var(--text-label);
  overflow-wrap: anywhere;
}

.document-definitions > li + li {
  margin-top: 2px;
}

.document-hits {
  list-style: none;
  margin: var(--space-3) 0 0;
  padding: 0;
}

/* A rule between documents rather than a card around each: inside a dialog
   a card within a card is a box nobody reads the edges of. */
.document-hits > li + li {
  margin-top: var(--space-3);
  padding-top: var(--space-3);
  border-top: 1px solid var(--border);
}

.document-hit-heading {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-2);
}

/* The title takes the line and the one action follows it, so that a long
   path wraps as a path instead of being squeezed to one word a line by a
   button holding the end of the row. */
.document-hit-name {
  flex: 1 1 auto;
  min-width: 0;
  font-weight: 600;
  overflow-wrap: anywhere;
}

.document-hit-detail {
  margin: 2px 0 0;
  font-size: var(--text-label);
}

/* The passage itself, and the document in the reader: both are text as it
   was written, so the line breaks in it are kept and a long unbroken token
   -- a URL, an identifier -- breaks rather than widening the dialog. */
.document-hit-text,
.document-read-text {
  margin: var(--space-2) 0 0;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

/* The whole graph, drawn.

   A page rather than a card. A drawing that is panned and dragged wants the
   window, and it must not be inside anything that scrolls: a drag that the
   page also reads is a drag that scrolls the page out from under the finger.
   So the box takes exactly the room left under the heading — measured, in
   the component, against the scrolling area's own padding — and the controls
   float over the canvas instead of sitting above it, where they would take
   back the height they are there to save. */
.graph-explore {
  position: relative;
  width: 100%;
  overflow: hidden;
  border: 1px solid var(--border);
  border-radius: var(--radius-3);
  background: var(--surface);
  /* Nothing here is text to select, and a drag that starts on a name should
     move the drawing rather than highlight the name. */
  user-select: none;
}

/* In a page's Connections card: the height of a card, the same drawing. */
.graph-explore.embedded {
  height: 420px;
}

/* The browser's fullscreen: the frame is the screen, whatever height the
   page had measured for it, and the border is gone. */
.graph-explore:fullscreen {
  height: 100vh !important;
  border: 0;
  border-radius: 0;
}

.graph-explore-canvas {
  display: block;
  width: 100%;
  height: 100%;
  /* Every gesture on the canvas is one this page handles: one finger pans
     and two zoom, so the browser must not also scroll or zoom. */
  touch-action: none;
  cursor: grab;
}

/* The colours a kind is drawn in.

   Here rather than in tokens.css: the rest of the dashboard is deliberately
   almost colourless, and this is one page's palette rather than a set of
   chrome tokens anything else should reach for. Nine hues that stay apart at
   the size of a circle, lifted for dark where the light ones go muddy. */
:root {
  --graph-self: #5468d4;
  --graph-person: #2f80c8;
  --graph-organization: #16867a;
  --graph-project: #c1543f;
  --graph-topic: #8a5cc2;
  --graph-place: #3d8f4f;
  --graph-thing: #b06f2a;
  --graph-period: #7a8b2b;
  --graph-folder: #8e949c;
  /* A kind the server grew after this was written. It is drawn, quietly,
     rather than left invisible. */
  --graph-other: #9aa0a6;
  /* The person's own records, which are not pages: one colour for all of
     them, since their shape and mark already say which kind. */
  --graph-record: #6b7280;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme='light']) {
    --graph-self: #8b9bf0;
    --graph-person: #6db0e8;
    --graph-organization: #4fc0b0;
    --graph-project: #e5836e;
    --graph-topic: #b58ce0;
    --graph-place: #72c98a;
    --graph-thing: #dda05e;
    --graph-period: #b4c464;
    --graph-folder: #9ba1a9;
    --graph-other: #a0a0a8;
    --graph-record: #aab0ba;
  }
}

:root[data-theme='dark'] {
  --graph-self: #8b9bf0;
  --graph-person: #6db0e8;
  --graph-organization: #4fc0b0;
  --graph-project: #e5836e;
  --graph-topic: #b58ce0;
  --graph-place: #72c98a;
  --graph-thing: #dda05e;
  --graph-period: #b4c464;
  --graph-folder: #9ba1a9;
  --graph-other: #a0a0a8;
  --graph-record: #aab0ba;
}

.graph-explore-kind-record {
  --graph-kind: var(--graph-record);
}

.graph-explore-kind-self {
  --graph-kind: var(--graph-self);
}
.graph-explore-kind-person {
  --graph-kind: var(--graph-person);
}
.graph-explore-kind-organization {
  --graph-kind: var(--graph-organization);
}
.graph-explore-kind-project {
  --graph-kind: var(--graph-project);
}
.graph-explore-kind-topic {
  --graph-kind: var(--graph-topic);
}
.graph-explore-kind-place {
  --graph-kind: var(--graph-place);
}
.graph-explore-kind-thing {
  --graph-kind: var(--graph-thing);
}
.graph-explore-kind-period {
  --graph-kind: var(--graph-period);
}
.graph-explore-kind-folder {
  --graph-kind: var(--graph-folder);
}

.graph-explore-node {
  cursor: pointer;
}

/* The child combinator, not a descendant: the badge in the corner is a
   circle too, and it is not the page. */
.graph-explore-node > circle {
  fill: var(--graph-kind, var(--graph-other));
  stroke: var(--surface);
  stroke-width: 1.5px;
}

/* A ring on a page whose neighbourhood has been fetched, so that the edge of
   what is drawn is visible: everything without one may have more behind it. */
/* A record of the person's own is a square with its kind's mark, drawn in
   outline so that it is never mistaken for a page's filled circle. */
.graph-explore-record > rect {
  fill: var(--surface);
  stroke: var(--graph-kind, var(--graph-other));
  stroke-width: 2px;
}

.graph-explore-record.selected > rect {
  stroke: var(--accent);
  stroke-width: 2.5px;
}

.graph-explore-record-icon {
  color: var(--graph-kind, var(--graph-other));
  pointer-events: none;
}

.graph-explore-node.expanded > circle {
  stroke: var(--text);
  stroke-opacity: 0.35;
  stroke-width: 2px;
}

.graph-explore-node.selected > circle {
  stroke: var(--accent);
  stroke-opacity: 1;
  stroke-width: 2.5px;
}

/* More behind it than is drawn: pages filed under it, or a page nobody has
   opened yet. */
.graph-explore-more circle {
  fill: var(--surface);
  stroke: var(--border-strong);
  stroke-width: 1px;
}

.graph-explore-more path {
  fill: none;
  stroke: var(--text);
  stroke-width: 1.4px;
  stroke-linecap: round;
}

/* The names are inside the group that carries the zoom, so their size is
   divided by it as it is drawn — which is what keeps a name twelve real
   pixels tall however far in or out the drawing is. Below half scale they
   are closer together than they are tall and are not drawn at all. */
.graph-explore-name {
  fill: var(--text);
  font-size: var(--graph-explore-label, 12px);
  paint-order: stroke;
  stroke: var(--surface);
  stroke-width: 3px;
  stroke-linejoin: round;
  pointer-events: none;
  display: none;
}

.graph-explore-canvas.names .graph-explore-name {
  display: block;
}

/* A name that would sit on one already shown, until its page is hovered. */
.graph-explore-name.crowded {
  visibility: hidden;
}

.graph-explore-node:hover .graph-explore-name.crowded {
  visibility: visible;
}

.graph-explore-edge line {
  fill: none;
  stroke: var(--border-strong);
}

/* Where a page is filed rather than something somebody stated about it:
   thinner, dashed, and no arrow. The hierarchy is already in the path. */
.graph-explore-edge.under line {
  stroke: var(--border);
  stroke-dasharray: 3 4;
}

/* A link the nightly walk guessed. Dashed like the line above but with
   longer dashes, so the two do not read as the same thing, and it keeps
   its arrow: it is still a relation, only an unconfirmed one. */
.graph-explore-edge.proposed line {
  stroke-dasharray: 7 4;
}

.graph-explore-arrow {
  fill: var(--border-strong);
  stroke: none;
}

/* The relation, on the line. Only once the line is long enough on screen to
   hold the word; until then it is on the line's tooltip. */
.graph-explore-relation {
  fill: var(--muted);
  font-size: calc(var(--graph-explore-label, 12px) * 0.85);
  paint-order: stroke;
  stroke: var(--surface);
  stroke-width: 4px;
  stroke-linejoin: round;
  pointer-events: none;
  display: none;
}

.graph-explore-canvas.relations .graph-explore-relation {
  display: block;
}

.graph-explore-lookup {
  position: absolute;
  top: var(--space-3);
  left: var(--space-3);
  width: min(280px, calc(100% - var(--space-6)));
}

.graph-explore-lookup input {
  width: 100%;
  background: var(--surface);
}

.graph-explore-found {
  list-style: none;
  margin: var(--space-1) 0 0;
  padding: var(--space-1);
  max-height: 320px;
  overflow: auto;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-2);
  box-shadow: var(--shadow-floating);
}

.graph-explore-found li {
  padding: 0;
}

.graph-explore-found li.muted {
  padding: var(--space-2);
  font-size: var(--text-label);
}

.graph-explore-found button {
  display: flex;
  flex-direction: column;
  gap: 1px;
  width: 100%;
  text-align: left;
  padding: var(--space-2);
  border: none;
  border-radius: var(--radius-1);
  background: none;
  color: var(--text);
  font: inherit;
  cursor: pointer;
}

.graph-explore-found button:hover {
  background: var(--field);
}

.graph-explore-found button .muted {
  font-size: var(--text-label);
  overflow-wrap: anywhere;
}

.graph-explore-zoom {
  position: absolute;
  right: var(--space-3);
  top: var(--space-3);
  display: flex;
  align-items: center;
  gap: var(--space-1);
}

/* Over a drawing rather than on a toolbar, so these carry their own ground:
   a transparent button on a canvas full of circles is not a button. The
   segmented pair brings its own outline; an icon button is given one. */
.graph-explore-zoom .segmented {
  background: var(--surface);
  height: 36px;
}

.graph-explore-zoom .segmented button {
  height: 100%;
  padding: 0 12px;
}

.graph-explore-zoom .icon-button {
  background: var(--surface);
  border: 1px solid var(--border);
  font: inherit;
  font-size: 17px;
  line-height: 1;
  /* The same height as the Fit and Fullscreen pair beside them. */
  width: 36px;
  height: 36px;
  padding: 0;
}

/* The agent's button floats over the bottom right of every page and covered
   Fit and Fullscreen there, on a phone as on a desktop; the top right of the
   drawing is free, so the controls stay there at every width.

   At 760px and below every button is at least 40px tall, which made the 36px
   icon buttons taller than wide. The whole group takes that height, and the
   icon buttons the same width, so they stay square. */
@media (max-width: 760px) {
  .graph-explore-zoom .segmented {
    height: 40px;
  }

  .graph-explore-zoom .icon-button {
    width: 40px;
    height: 40px;
  }
}

.graph-explore-legend {
  position: absolute;
  left: var(--space-3);
  bottom: var(--space-3);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-1) var(--space-3);
  max-width: min(560px, calc(100% - 180px));
  /* A legend is read, not pressed, and a drag that starts on it should pan
     the drawing behind it. */
  pointer-events: none;
}

.graph-explore-swatch {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  color: var(--muted);
  font-size: 13px;
}

.graph-explore-swatch i {
  width: 9px;
  height: 9px;
  border-radius: var(--radius-round);
  background: var(--graph-kind, var(--graph-other));
}

.graph-explore-swatch i.square {
  border-radius: 2px;
  background: var(--surface);
  border: 2px solid var(--graph-kind, var(--graph-other));
  width: 11px;
  height: 11px;
  box-sizing: border-box;
}

.graph-explore-hint {
  flex-basis: 100%;
  font-size: 13px;
}

.graph-explore-nothing {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-6);
  pointer-events: none;
}

/* What one page says, beside the drawing rather than instead of it: reading
   a page is a click away, and choosing one on the canvas should not take the
   canvas away. */

.graph-explore-facts {
  margin-top: var(--space-3);
  max-height: 40vh;
  overflow: auto;
}

/* On a phone the dialog is the screen, and the facts are what the screen
   is for: the list takes every line the opening and the buttons leave,
   and scrolls inside, rather than stopping at a third of the height with
   the rest of the screen empty under the buttons. */
@media (max-width: 640px) {
  .dialog:has(.graph-explore-facts) {
    display: flex;
    flex-direction: column;
  }
  .dialog:has(.graph-explore-facts) .dialog-actions {
    margin-top: auto;
  }
  .graph-explore-facts {
    flex: 1 1 auto;
    min-height: 0;
    max-height: none;
  }
}

.graph-explore-fact {
  padding: var(--space-1) 0;
  border-top: 1px solid var(--border);
  font-size: var(--text-label);
  line-height: 1.4;
}

.graph-explore-opening {
  font-size: var(--text-body);
  overflow-wrap: break-word;
}

.graph-explore-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  margin-top: var(--space-3);
}

/* On a phone the panel is a sheet across the foot of the screen. A 300px
   card floating over a 390px window is the window. */

@media (max-width: 600px) {
  /* Full width, no side frame, the same way a card gives up its own at this
     size: every pixel of a drawing is the drawing. */
  .graph-explore {
    margin-left: calc(var(--space-3) * -1);
    margin-right: calc(var(--space-3) * -1);
    width: auto;
    border-left: none;
    border-right: none;
    border-radius: 0;
  }

  /* The sheet covers the foot of the canvas, so the legend goes up out of
     its way and the sentence about dragging goes: the gestures are the ones
     the phone already teaches.
     
     Up out of the way of the controls as well, which is what this said and
     did not do. Narrowing it was not enough: at this width the legend wraps
     onto two lines and still reached them. Measured at a 500 pixel viewport
     it ended at 372 and the zoom buttons began at 371, so the buttons were
     drawn over the words. Above them it has the width back. */
  .graph-explore-legend {
    max-width: 100%;
    bottom: calc(var(--space-3) + 2.75rem);
  }

  .graph-explore-hint {
    display: none;
  }
}

/* The hours a dream may start: two clocks in the row that explains them,
   each with a word before it, on one line while there is room. */
.hours {
  display: inline-flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
}
.hours label {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  margin: 0;
}
.hours label > span {
  color: var(--muted);
  font-size: 0.85rem;
}

/* The contact that is the person: a filled mark in the accent, so the state
   is visible without the tooltip. */
.row-actions button.marked {
  color: var(--accent);
  border-color: var(--accent);
}

/* A search box as a section's action keeps to its own width and the head
   wraps under it on a phone, rather than the box taking the row and the
   title breaking into one word per line beside it. */
.settings-section-head {
  flex-wrap: wrap;
}
.settings-section-head > input[type='search'] {
  flex: 0 1 16rem;
  min-width: 10rem;
}

/* The row's own figure on a narrow window.
   The floor under anything a thumb has to hit makes the buttons 40, and the
   border the set is drawn with puts two more around them; the box has the
   same floor and no such border, so the two came out 40 against 42. Telling
   the row that its controls are 42 raises the box to meet them, rather than
   shrinking a target somebody sized on purpose. Last in the file, after the
   rule that sets the floor, so that it is this figure the box is held to. */
@media (max-width: 760px) {
  .list-toolbar {
    --toolbar-control: 42px;
  }
}

/* Room at the foot of the page for the floating button, where the window
   is narrow enough that it sits over the content rather than beside it.
   Only where it is needed: on a wide window the button is clear of the
   column it would otherwise cover. Up to the 900px breakpoint, since at
   820 the button still sat on the end of a panel's last line.

   That room is only reached by scrolling to the end, and a page a little
   taller than the window rests with its last line under the button: the
   next-page arrow of a list's pager, at the right end of the foot, was the
   one control covered. So a pager in the page keeps the button's corner
   clear as well, its width and margin less the page's own padding; one in
   a dialog sits above the button and needs none. */
@media (max-width: 900px) {
  main.content,
  .table-surface {
    padding-bottom: 84px;
  }

  main.content .table-pagination {
    padding-right: 52px;
  }

  /* Recording passage actions keep the same corner clear as the pager. */
  .recording-page .settings-row-actions {
    padding-right: 52px;
  }

  main.content .dialog .table-pagination {
    padding-right: 0;
  }
}

/* Keep a pending message readable while its fields and toolbar cannot change. */
.compose-fields {
  min-width: 0;
  margin: 0;
  padding: 0;
  border: 0;
}

/* --- approving a program ---------------------------------------------------
   The card a program's authorization request is read in. Same chrome as
   signing in and as /cli, because it is the same kind of moment: one
   question, arrived at from somewhere else.

   The facts are a description list rather than prose. The reader is being
   asked to make a security decision about something they may not recognize,
   and three labelled values they can scan beat a sentence they have to
   parse. */

.authorize-intro {
  margin: 0;
  text-wrap: pretty;
}

.authorize-facts {
  display: grid;
  /* The label column takes what it needs and no more, so the values line up
     against it; on a phone they stack instead, below. */
  /* The label column is capped so a long label wraps onto a second line
     rather than taking the width the value needs. Uncapped, "Sends the
     approval to" left the host so little room that it broke mid-word. */
  grid-template-columns: fit-content(7.5rem) minmax(0, 1fr);
  gap: var(--space-2) var(--space-4);
  margin: var(--space-4) 0 0;
  padding: var(--space-3) var(--space-4);
  background: var(--field);
  /* Outlined as well as filled. In light the fill alone reads as a panel,
     but in dark --field is a step darker than the card rather than lighter,
     and the panel all but vanished into it. tokens.css says as much: what
     sits on a surface is told apart by its outline. */
  border: 1px solid var(--border);
  border-radius: var(--radius-2);
}

.authorize-facts dt {
  color: var(--muted);
  font-size: var(--text-label);
  /* Against the first line of its value, which is two lines tall when the
     program's name carries the note under it. */
  align-self: start;
  padding-top: 1px;
}

.authorize-facts dd {
  margin: 0;
  min-width: 0;
}

/* The claimed name, which is the thing most likely to be long and least
   likely to be trustworthy. Broken anywhere rather than allowed to widen the
   card: a registration is a stranger's text and may be one long word. */
.authorize-name {
  display: block;
  font-weight: 500;
  overflow-wrap: anywhere;
}

/* Under the account the approval would act as, so the way to change it sits
   beside the fact it changes. A quiet button rather than a link: it signs
   out, which is an action. Tall enough for a finger on a phone. */
.authorize-switch {
  display: block;
  margin-top: var(--space-1);
  min-height: 32px;
  font-size: var(--text-label);
}

@media (pointer: coarse) {
  .authorize-switch {
    min-height: 44px;
  }
}

.authorize-claimed {
  display: block;
  margin-top: 2px;
  font-size: var(--text-label);
}

/* The address the approval would go to. The one fact on this card a reader
   can actually check, so it is set as what it is rather than as prose. */
.authorize-host {
  display: inline-block;
  max-width: 100%;
  padding: 2px 6px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-1);
  font-size: var(--text-label);
  /* Broken at the dots the page marks, and only mid-label when one label is
     longer than the card. "anywhere" also let the browser choose to break a
     short host that would have fitted. */
  overflow-wrap: break-word;
}

/* The code a phone scans: square, on the white its image carries, and large
   enough to read from arm's length on a laptop. */
.connect-qr {
  display: block;
  width: min(100%, 240px);
  aspect-ratio: 1;
  margin: var(--space-3) auto;
  border-radius: var(--radius-1);
}

.connect-here {
  margin-top: var(--space-2);
}

.devices-group + .devices-group {
  margin-top: var(--space-6);
}
.devices-group > h2 + .muted {
  margin-bottom: var(--space-3);
}

.connect-back {
  display: inline-block;
  margin-top: var(--space-3);
  color: var(--muted);
  font-size: var(--text-label);
}

.authorize-grant {
  margin: var(--space-3) 0 0;
  font-size: var(--text-label);
  line-height: 1.55;
  text-wrap: pretty;
}

/* Allow and Refuse side by side, Allow first because it is what the reader
   came to do. Refuse is not styled as a danger: refusing is the safe answer
   and should not look like the frightening one. */
.authorize-actions {
  display: flex;
  gap: var(--space-2);
  margin-top: var(--space-4);
}

.authorize-actions .auth-button {
  margin-top: 0;
}

/* Refuse takes the space it needs; Allow takes the rest. */
.authorize-actions button[type='button'] {
  flex: 0 0 auto;
  width: auto;
  padding: 0 var(--space-4);
}

.authorize-actions button[type='submit'] {
  flex: 1;
}

@media (max-width: 480px) {
  /* Stacked: the label above its value, so a long host name has the whole
     card width rather than what is left beside a label. */
  /* Stacked, each label close over its own value and a clear step before
     the next pair. With an even gap throughout, a label sat about as far
     from its value as from the pair above, and "Acts as" read as belonging
     to the host. */
  .authorize-facts {
    grid-template-columns: minmax(0, 1fr);
    gap: 0;
  }

  .authorize-facts dt {
    padding-top: var(--space-4);
  }

  .authorize-facts dt:first-of-type {
    padding-top: 0;
  }

  .authorize-facts dd {
    margin-top: 2px;
  }

  /* One under the other, Allow still first. Two buttons side by side on a
     narrow card leaves each too small to press confidently. */
  .authorize-actions {
    flex-direction: column;
  }

  .authorize-actions button[type='button'] {
    width: 100%;
  }
}

/* Where a program asked to be sent, under the sentence saying it cannot be
   approved: the one detail a reader can check against what they expected. */
.authorize-asked {
  margin: var(--space-3) 0 0;
  font-size: var(--text-label);
  overflow-wrap: anywhere;
}

/* Ideas: offers of work the agent can do. A row is the button that starts
   the idea, with the mark that dismisses it in its corner; under it, for one
   found in the person's own data, why and from what. Two columns where
   there is room, so thirty ideas are not one long scroll. */
.idea-list {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-4) var(--space-6);
}

@media (min-width: 900px) {
  .idea-list {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

.idea-row {
  position: relative;
  padding-right: 28px;
}

.idea-start {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  width: 100%;
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.idea-start:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 4px;
  border-radius: var(--radius-1);
}

@media (hover: hover) {
  .idea-start:hover .idea-headline {
    text-decoration: underline;
    text-underline-offset: 3px;
  }
}

.idea-emoji {
  flex: none;
  width: 28px;
  font-size: 22px;
  line-height: 1.25;
  text-align: center;
}

.idea-text {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.idea-headline {
  font-weight: 600;
  line-height: 1.35;
}

.idea-body {
  line-height: 1.45;
}

.idea-row .idea-dismiss {
  position: absolute;
  top: -2px;
  right: 0;
}

.idea-foot {
  margin: var(--space-2) 0 0 calc(28px + var(--space-3));
  font-size: var(--text-label);
}

.idea-reason {
  margin: 0 0 var(--space-1);
}

/* What prompted an idea: plain links, one to a line and cut to it, each
   with the mark of what it is. */
.idea-sources {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 2px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.idea-source {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  min-width: 0;
  max-width: 100%;
  padding: 0;
  border: 0;
  background: none;
  color: var(--muted);
  font: inherit;
  text-align: left;
  text-decoration: none;
  cursor: pointer;
}

.idea-source svg {
  flex: none;
}

.idea-source span {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

@media (hover: hover) {
  .idea-source:hover {
    color: var(--text);
  }

  .idea-source:hover span {
    text-decoration: underline;
    text-underline-offset: 2px;
  }
}

.idea-suggestions {
  margin-top: var(--space-4);
}

/* An idea in the history table: its words on one line, opening the
   conversation that carried it out. */
.idea-history-title {
  display: block;
  max-width: min(50vw, 36rem);
  overflow: hidden;
  padding: 0;
  border: 0;
  background: none;
  color: var(--text);
  font: inherit;
  text-align: left;
  text-overflow: ellipsis;
  white-space: nowrap;
}

button.idea-history-title {
  cursor: pointer;
}

@media (hover: hover) {
  button.idea-history-title:hover {
    text-decoration: underline;
    text-underline-offset: 3px;
  }
}

/* One column in the drawer, however wide the window: the two-column rule
   answers to the window, and the drawer is narrow in any of them. */
.idea-suggestions .idea-list {
  grid-template-columns: minmax(0, 1fr);
}

.idea-suggestions-title {
  margin-bottom: var(--space-3);
}

/* Goals: what the agent keeps working on, each a row the person ticks when
   it is done, and the areas a new one can be about. */
.goal-list {
  display: grid;
  gap: var(--space-3);
}

.goal-row {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
}

.goal-check {
  flex: none;
  margin-top: 3px;
}

.goal-mark {
  flex: none;
  width: 18px;
  text-align: center;
}

.goal-text {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.goal-title {
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  font-weight: 600;
  text-align: left;
  text-decoration: none;
  cursor: pointer;
}

@media (hover: hover) {
  .goal-title:hover {
    text-decoration: underline;
    text-underline-offset: 3px;
  }
}

.goal-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-1) var(--space-2);
  font-size: var(--text-label);
}

.goal-title-text {
  font-weight: 600;
}

/* Starting a goal, and answering one: a title, then what it is for. */
.goal-start,
.goal-tell {
  display: grid;
  gap: var(--space-2);
  max-width: 720px;
}

/* One goal: where it stands, its status in its own words, what it is for,
   the box to answer it and what to do with it. */
.goal-page {
  display: grid;
  gap: var(--space-3);
  min-width: 0;
  max-width: 720px;
}

.goal-page-back {
  margin-bottom: var(--space-3);
}

.goal-status {
  margin: 0;
  font-weight: 600;
}

.goal-description {
  margin: 0;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

.goal-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}

/* What happened on a goal, newest first: a dot for each entry, joined by a
   line drawn from each dot down to the next, so the two always line up. */
.goal-activity {
  display: grid;
  margin: 0;
  padding: 0;
  list-style: none;
}

.goal-activity-item {
  position: relative;
  display: grid;
  gap: 2px;
  min-width: 0;
  padding: 0 0 var(--space-4) 26px;
}

.goal-activity-item:last-child {
  padding-bottom: 0;
}

.goal-activity-item::before {
  content: '';
  position: absolute;
  left: 0;
  top: 4px;
  box-sizing: border-box;
  width: 12px;
  height: 12px;
  border-radius: 50%;
  border: 2px solid var(--border-strong);
  background: var(--surface);
}

.goal-activity-item::after {
  content: '';
  position: absolute;
  left: 5px;
  top: 18px;
  bottom: 2px;
  width: 2px;
  background: var(--border);
}

.goal-activity-item:last-child::after {
  display: none;
}

.goal-activity-waiting::before {
  border-color: var(--warn);
}

.goal-activity-met::before {
  border-color: var(--good);
}

.goal-activity-headline {
  font-weight: 600;
}

.goal-activity-detail {
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

.goal-activity-when {
  font-size: var(--text-label);
}

.goal-areas {
  display: grid;
  gap: var(--space-2);
  grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
}

.goal-area {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  justify-content: flex-start;
}

/* The reminders list beside the calendar: which list, then the list, whose
   first line is where a new one is written and each line after it a
   reminder with the box that ticks it off. */
.reminders {
  display: grid;
  gap: var(--space-4);
  max-width: 720px;
}

/* The line that adds one looks like the lines it adds: a mark where the box
   would be, and the words where the title would be, in a field with no box
   of its own, so it reads as the next reminder waiting to be written. */
.reminder-add {
  align-items: flex-start;
}

/* The mark takes the room the tick box takes, margins and all, so what is
   typed starts where the titles under it start. */
.reminder-add-mark {
  flex: none;
  box-sizing: content-box;
  width: 13px;
  height: 13px;
  margin: 0 6px 0 4px;
  color: var(--muted);
}

.reminder-add form {
  display: grid;
  flex: 1;
  gap: var(--space-2);
  min-width: 0;
  margin: 0;
}

/* One line tall, as a title is, rather than a field's height; the text stays
   at 16px, below which a phone zooms the page in on focus. */
.reminder-add input[type='text'],
.reminder-add input[type='text']:hover,
.reminder-add input[type='text']:focus-visible {
  height: auto;
  min-height: 0;
  padding: 0;
  border: none;
  background: transparent;
  line-height: 22px;
}

.reminder-add-details {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

.reminders-none {
  margin: 0;
}

/* The due day, as one field with its name inside it: an empty date field is
   a blank box on a phone, and a blank box beside "Add" says nothing. */
.reminders-due {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex: none;
  width: 13rem;
  margin: 0;
  padding-left: 10px;
  border: 1px solid var(--border);
  border-radius: var(--radius-2);
  background: var(--surface);
}

.reminders-due:hover {
  border-color: var(--border-strong);
}

.reminders-due:focus-within {
  border-color: var(--text);
}

.reminders-due > span {
  margin: 0;
  color: var(--muted);
  font-size: var(--text-label);
  white-space: nowrap;
}

.reminders-due > input[type='date'],
.reminders-due > input[type='date']:hover,
.reminders-due > input[type='date']:focus-visible {
  flex: 1;
  min-width: 0;
  padding-left: 0;
  border: none;
  background: transparent;
}

/* To do and Done: the one choice of which list is shown, a little larger than
   a toolbar's segments since it is what the page is about. */
.reminders-lists {
  justify-self: start;
}

.reminders-lists button {
  padding: 5px 14px;
  font-size: 13px;
}

.reminders-lists .count {
  margin-left: var(--space-1);
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

.reminder-list {
  display: grid;
  margin: 0;
  padding: 0;
  list-style: none;
  border-top: 1px solid var(--border);
}

.reminder-row {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  padding: var(--space-3) 0;
  border-bottom: 1px solid var(--border);
}

/* The box sits in a slot one title line tall and is centered in it, so it
   lines up with the first line of the title however many lines follow, and
   is not stretched to a phone's touch height. The + on the adding line
   takes the same slot. */
.reminder-check {
  display: flex;
  flex: none;
  align-items: center;
  height: 22px;
}

.reminder-row .reminder-check input[type='checkbox'] {
  min-height: 0;
  margin: 0 6px 0 4px;
}

.reminder-text {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.reminder-title {
  line-height: 22px;
  overflow-wrap: anywhere;
}

.reminder-row.done .reminder-title {
  color: var(--muted);
  text-decoration: line-through;
}

.reminder-due {
  color: var(--muted);
  font-size: var(--text-label);
}

.reminder-due.overdue {
  color: var(--bad);
}

.reminder-notes {
  font-size: var(--text-label);
  overflow-wrap: anywhere;
}

@media (max-width: 600px) {
  .reminders-due {
    flex: 1;
    width: auto;
  }
}

/* The notes folder: the list of notes beside the one being edited, drawn in
   the mailbox's frame so the page keeps its height and its container. */
.notes {
  display: grid;
  grid-template-columns: 320px minmax(0, 1fr);
  flex: 1 1 auto;
  min-height: 0;
  border: 1px solid var(--border);
  border-radius: var(--radius-2);
  overflow: hidden;
  background: var(--surface);
}

.notes-list {
  display: flex;
  flex-direction: column;
  min-width: 0;
  min-height: 0;
  border-right: 1px solid var(--border);
}

.notes-list-head {
  display: flex;
  padding: var(--space-3);
  border-bottom: 1px solid var(--border);
}

.notes-list .settings-empty {
  margin: var(--space-3);
}

.notes-rows {
  flex: 1 1 auto;
  margin: 0;
  padding: 0;
  overflow-y: auto;
  list-style: none;
}

.notes-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 2px var(--space-2);
  padding: var(--space-2) var(--space-3);
  border-bottom: 1px solid var(--border);
  color: inherit;
  text-decoration: none;
}

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

.notes-row.active {
  background: var(--sidebar-hover);
}

.notes-row-title,
.notes-row-preview {
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.notes-row-title {
  font-weight: 600;
}

.notes-row-when {
  color: var(--muted);
  font-size: var(--text-label);
  white-space: nowrap;
}

.notes-row-preview {
  grid-column: 1 / -1;
  color: var(--muted);
  font-size: 13px;
}

.notes-pane {
  display: flex;
  flex-direction: column;
  min-width: 0;
  min-height: 0;
  padding: var(--space-3) var(--space-4);
  overflow-y: auto;
}

.notes-pane-placeholder {
  margin: auto;
}

.notes-editor {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
}

.notes-editor-actions {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin-bottom: var(--space-3);
}

.notes-editor-when {
  flex: 1 1 auto;
  min-width: 0;
  font-size: var(--text-label);
}

.notes-editor-uneditable {
  margin: 0 0 var(--space-3);
  font-size: var(--text-label);
}

.notes-editor .richtext {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  margin-bottom: 0;
}

.notes-editor .richtext-editor {
  flex: 1 1 auto;
}

/* The back button is a step only when the note has taken the list's place. */
.notes-back {
  display: none;
}

/* One pane at a time on a phone: the list, or the note with a way back. */
@container mailbox (max-width: 600px) {
  .notes {
    grid-template-columns: minmax(0, 1fr);
  }

  .notes-list {
    border-right: 0;
  }

  .notes.reading .notes-list,
  .notes:not(.reading) .notes-pane {
    display: none;
  }

  .notes-back {
    display: inline-flex;
  }
}

@media (max-width: 600px) {
  .notes {
    margin-left: calc(var(--space-3) * -1);
    margin-right: calc(var(--space-3) * -1);
    border: none;
    border-radius: 0;
  }

  .notes-pane {
    padding: var(--space-3) var(--space-2);
  }
}

/* A page's overview is several sections of prose the night wrote, read
   like the opening above it, so its measure is capped the same way. */
.knowledge-overview {
  max-width: var(--measure);
  overflow-wrap: break-word;
}

/* A reflection's citations: the pages and facts it rests on, each a link
   to its page, wrapping onto as many lines as it needs. */
.knowledge-citations {
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem 0.5rem;
  align-items: baseline;
  overflow-wrap: anywhere;
}

/* Alerts on the agent's Overview: what was said reads as a sentence, not a
   name, so it wraps at the measure; each message it was about is a line of
   its own under the time it was said. */
.alert-text {
  font-weight: normal;
  max-width: var(--measure);
  overflow-wrap: break-word;
}

.alert-covered {
  display: block;
  overflow-wrap: anywhere;
}

/* The night's two clocks and the day's most on one line, folding under
   each other on a phone. */
.alert-settings input[type='time'] {
  min-width: 7rem;
}

/* A plan's allowance under its provider: one bar per window, then when the
   plan last said so. */
.plan-usage {
  display: flex;
  flex-direction: column;
  margin-top: 6px;
}
.plan-usage .agent-budget {
  margin: 4px 0;
}
.plan-usage-observed {
  font-size: var(--text-label);
}

/* --- the agent's Finance tab ---------------------------------------------- */

/* The line that says where the other half of finance is: the setup on the
   agent page, the data on the Finance page. Above the panels, spaced as a
   panel is from the next. */
.finance-pointer {
  margin: 0 0 var(--space-4);
}

/* The pointer's own margin is the space below; the actions row adds none. */
.finance-pointer .page-actions {
  margin-bottom: 0;
}

/* The choice of whose finance the Finance page shows, above its tabs or
   above somebody's shared accounts: like the Health page's. */
.finance-toolbar {
  display: flex;
  align-items: flex-end;
  gap: var(--space-3);
  margin-bottom: var(--space-4);
}

/* An amount column in the shared table reads down by its decimal places,
   as the plain numbers tables do. */
.table-surface .numeric {
  text-align: right;
  font-variant-numeric: tabular-nums;
}

/* Amounts and dates in the finance tables stay on one line; the table
   scrolls sideways inside its panel on a phone rather than wrapping a
   balance over two lines. */
.finance-table td,
.finance-table th {
  white-space: nowrap;
}

.finance-table td.wrap {
  white-space: normal;
  min-width: 12rem;
}

/* The statement import address is long and has no spaces; on a phone it
   breaks anywhere rather than pushing the page sideways. */
.statement-address {
  overflow-wrap: anywhere;
  word-break: break-all;
}

.finance-table tfoot th {
  color: var(--text);
  font-weight: 600;
  border-bottom: none;
}

/* The filters above a finance table: dates, an account and words, folding
   two to a line and then one on a phone. */
.finance-filters {
  margin-bottom: var(--space-3);
  /* Top-aligned, so the words over each field sit on one line: a dropdown
     is a little taller than a date, and aligned by their bottoms its words
     rode higher than the date's. */
  align-items: flex-start;
}

/* The ticks under the filters: side by side while they fit, each box kept
   with its words, wrapping between ticks rather than inside one. */
.finance-filter-ticks {
  display: flex;
  flex-wrap: wrap;
  gap: 0 var(--space-4);
}

.finance-filter-ticks > label.checkbox {
  margin-bottom: 0;
  white-space: nowrap;
}

.finance-filters > label {
  min-width: 150px;
}

.finance-description {
  display: inline-block;
  max-width: 28rem;
  overflow: hidden;
  text-overflow: ellipsis;
  vertical-align: middle;
}

/* The spending category chosen in the row, and who chose it when it was
   not the person: a spending rule, the provider category, the model. */
.finance-category-cell {
  display: inline-flex;
  flex-direction: column;
  gap: 2px;
  min-width: 11rem;
}

/* A second line under a cell's figure: a holding's quantity and the price
   of one under its value. */
.finance-cell-detail {
  display: block;
  font-size: var(--text-label);
}

/* A bar per spending category against its budget, and one per savings
   target: the name and its pace, the numbers at the end of the line, the
   bar under them, what is projected under that. */
.finance-budget-bars {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}

.finance-budget-row,
.finance-target {
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-width: 0;
}

.finance-budget-row-head {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--space-2);
}

/* The head is a link to the budget's transactions; its name is underlined
   the way the spending table's group names are. */
.finance-budget-row-link {
  color: inherit;
  text-decoration: none;
}

.finance-budget-row-link strong {
  text-decoration: underline;
  text-decoration-color: var(--border-strong);
  text-underline-offset: 3px;
}

.finance-budget-row-link:hover strong {
  text-decoration-color: currentColor;
}

.finance-budget-row-said {
  margin-left: auto;
  font-variant-numeric: tabular-nums;
}

.finance-budget-row-detail {
  font-size: var(--text-label);
}

/* Where the month is heading, and beside it the button that opens how
   that is worked out: a small icon, so the line keeps the height of the
   text. */
.finance-forecast-line {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0 var(--space-1);
}

.finance-forecast-explain.icon-action {
  width: 22px;
  height: 22px;
}

.finance-forecast-explanation {
  padding: var(--space-2) var(--space-3);
  border-left: 2px solid var(--border);
  color: var(--muted);
  font-size: var(--text-label);
}

.finance-forecast-explanation p {
  margin: 0 0 var(--space-2);
}

.finance-forecast-explanation > :last-child {
  margin-bottom: 0;
}

.finance-forecast-charges {
  max-width: 24rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

.finance-forecast-charges li {
  display: flex;
  justify-content: space-between;
  gap: var(--space-3);
}

.finance-forecast-charges .numeric {
  font-variant-numeric: tabular-nums;
}

/* Budgets on spending and budgets on income, each under a small heading
   once both are there: the income ones read the other way round. */
.finance-budget-group + .finance-budget-group {
  margin-top: var(--space-4);
}

.finance-group-heading {
  margin: 0 0 var(--space-2);
  color: var(--muted);
  font-size: var(--text-label);
  font-weight: 600;
}

/* The month's income, spending and saving, budgeted against actual,
   under the saving's bar. */
.finance-saving-table {
  margin-top: var(--space-3);
}

.finance-saving-table tbody th,
.finance-saving-table tfoot th[scope='row'] {
  text-align: left;
}

/* Four columns of amounts fit a phone when the cells are closer: the
   table stays a table, and the last column no longer scrolls a few pixels
   out of the card. */
@media (max-width: 600px) {
  .finance-saving-table th,
  .finance-saving-table td {
    padding-left: 4px;
    padding-right: 4px;
  }

  .finance-saving-table th:first-child,
  .finance-saving-table td:first-child {
    padding-left: 0;
  }

  .finance-saving-table th:last-child,
  .finance-saving-table td:last-child {
    padding-right: 0;
  }
}

.finance-evidence {
  display: block;
}

.finance-asset-choices {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin: 0;
  padding: 0;
  border: none;
}

.finance-asset-choices legend {
  margin-bottom: var(--space-2);
  color: var(--muted);
  font-size: var(--text-label);
}

/* One institution's finance accounts, or one account's assets, in a
   savings target's choices: the name above, the checkboxes under it. */
.finance-choice-group {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.finance-choice-group-name {
  font-size: var(--text-label);
  font-weight: 600;
}

.finance-converted strong {
  font-size: var(--text-title);
  font-variant-numeric: tabular-nums;
}

/* The finance charts' lines and the zero line a scale below it crosses,
   in the usage chart's colours. */
.series-chart-zero {
  stroke: var(--muted);
  stroke-width: 1;
  shape-rendering: crispEdges;
}

.series-chart-line {
  fill: none;
  stroke-width: 2;
  stroke-linejoin: round;
  stroke-linecap: round;
}

.series-chart-line.output,
.series-chart-point.output {
  stroke: var(--usage-output);
}

.series-chart-line.input,
.series-chart-point.input {
  stroke: var(--usage-input);
}

.series-chart-line.cached,
.series-chart-point.cached {
  stroke: color-mix(in srgb, var(--muted) 70%, var(--surface));
}

.series-chart-point {
  fill: var(--surface);
  stroke-width: 2;
}

/* The stages of a night's sleep, from the deepest up, as the Health app draws
   them: deep a dark indigo, core a mid blue, REM a light cyan blue and awake
   orange. Each is at least 3:1 against the card in its theme, and the neighbors
   differ in lightness or hue. And the band behind a line of averages that
   reaches from the period's lowest reading to its highest. */
:root {
  --sleep-deep: #2a1f77;
  --sleep-core: #3a5ccf;
  --sleep-rem: #0e9fc6;
  --sleep-awake: #e0760b;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme='light']) {
    --sleep-deep: #6558ec;
    --sleep-core: #6aaeff;
    --sleep-rem: #6fe3f5;
    --sleep-awake: #fb923c;
  }
}

:root[data-theme='dark'] {
  --sleep-deep: #6558ec;
  --sleep-core: #6aaeff;
  --sleep-rem: #6fe3f5;
  --sleep-awake: #fb923c;
}

.usage-chart-column.deep,
.usage-chart-swatch.deep {
  fill: var(--sleep-deep);
  background: var(--sleep-deep);
}

.usage-chart-column.core,
.usage-chart-swatch.core {
  fill: var(--sleep-core);
  background: var(--sleep-core);
}

.usage-chart-column.rem,
.usage-chart-swatch.rem {
  fill: var(--sleep-rem);
  background: var(--sleep-rem);
}

.usage-chart-column.awake,
.usage-chart-swatch.awake {
  fill: var(--sleep-awake);
  background: var(--sleep-awake);
}

.series-chart-band {
  stroke: none;
}

.series-chart-band.band,
.usage-chart-swatch.band {
  fill: color-mix(in srgb, var(--leaf) 22%, transparent);
  background: color-mix(in srgb, var(--leaf) 22%, transparent);
}

/* A chart whose keys can be chosen: each slot is a clear button over the
   columns, the chosen slot sits on a band, and its label is the one in
   the text's own colour. */
.series-chart-slot {
  fill: transparent;
  cursor: pointer;
  outline: none;
}

.series-chart-slot:focus-visible {
  stroke: var(--text);
  stroke-width: 2;
}

.series-chart-selected-band {
  fill: color-mix(in srgb, var(--text) 8%, transparent);
}

.series-chart-axis-selected {
  fill: var(--text);
  font-weight: 650;
}

/* With a key chosen, the others step back less than they do for a
   pointer passing over them: they are still the chart being read, and at
   the pointer's fade the pale spending columns of a light page looked
   switched off. The chosen one keeps its band and its label. */
.series-chart-unselected .usage-chart-column {
  opacity: 0.7;
}

/* Where a month's money went: a ring, whose legend is the table under it.
   The slices' colours are mixed from the page's own, so a
   dark page gets them lifted with the rest; neighbours alternate between
   the leaf, the amber and the text colour so no two side by side match. */
.spending-ring,
.finance-summary-table,
.finance-assets-table,
.finance-credit-table,
.finance-tax-estimate {
  --ring-tone-0: var(--leaf);
  --ring-tone-1: var(--warn);
  --ring-tone-2: color-mix(in srgb, var(--text) 62%, var(--surface));
  --ring-tone-3: color-mix(in srgb, var(--leaf) 50%, var(--surface));
  --ring-tone-4: color-mix(in srgb, var(--warn) 50%, var(--surface));
  --ring-tone-5: color-mix(in srgb, var(--text) 35%, var(--surface));
  --ring-tone-6: color-mix(in srgb, var(--leaf) 60%, var(--text));
  --ring-tone-7: color-mix(in srgb, var(--warn) 60%, var(--text));
  --ring-other: color-mix(in srgb, var(--muted) 40%, var(--surface));
}

.spending-ring {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-4) var(--space-6);
  margin-bottom: var(--space-4);
}

.spending-ring-drawing {
  flex: 0 0 auto;
  width: 100%;
  max-width: 200px;
  height: auto;
}

.spending-ring-slice {
  stroke: var(--surface);
  stroke-width: 2;
  stroke-linejoin: round;
}

.spending-ring-total {
  fill: var(--text);
  font-size: 17px;
  font-weight: 650;
  font-variant-numeric: tabular-nums;
}

.spending-ring-caption {
  fill: var(--muted);
  font-size: 12px;
}

/* The swatch that names a slice, in the table under the ring. */
.spending-ring-swatch {
  display: inline-block;
  width: 10px;
  height: 10px;
  margin-right: var(--space-2);
  border-radius: 3px;
  vertical-align: middle;
}

/* The slice of the row under the pointer stands out by the others
   stepping back. */
.spending-ring-slice {
  transition: opacity 120ms ease;
}

.spending-ring-slice.dimmed {
  opacity: 0.3;
}

/* A group's share of the month, small beside its amount. */
.finance-share {
  display: inline-block;
  min-width: 2.75rem;
  margin-left: var(--space-1);
  font-size: var(--text-label);
}

.spending-ring-slice.tone-0 {
  fill: var(--ring-tone-0);
}

.spending-ring-slice.tone-1 {
  fill: var(--ring-tone-1);
}

.spending-ring-slice.tone-2 {
  fill: var(--ring-tone-2);
}

.spending-ring-slice.tone-3 {
  fill: var(--ring-tone-3);
}

.spending-ring-slice.tone-4 {
  fill: var(--ring-tone-4);
}

.spending-ring-slice.tone-5 {
  fill: var(--ring-tone-5);
}

.spending-ring-slice.tone-6 {
  fill: var(--ring-tone-6);
}

.spending-ring-slice.tone-7 {
  fill: var(--ring-tone-7);
}

.spending-ring-slice.other {
  fill: var(--ring-other);
}

.spending-ring-swatch.tone-0 {
  background: var(--ring-tone-0);
}

.spending-ring-swatch.tone-1 {
  background: var(--ring-tone-1);
}

.spending-ring-swatch.tone-2 {
  background: var(--ring-tone-2);
}

.spending-ring-swatch.tone-3 {
  background: var(--ring-tone-3);
}

.spending-ring-swatch.tone-4 {
  background: var(--ring-tone-4);
}

.spending-ring-swatch.tone-5 {
  background: var(--ring-tone-5);
}

.spending-ring-swatch.tone-6 {
  background: var(--ring-tone-6);
}

.spending-ring-swatch.tone-7 {
  background: var(--ring-tone-7);
}

.spending-ring-swatch.other {
  background: var(--ring-other);
}

/* The tax ring's tax still owing at filing. */
.spending-ring-slice.owing {
  fill: var(--bad);
}

.spending-ring-swatch.owing {
  background: var(--bad);
}

/* Credit usage, on Accounts: the share in the ring's middle and in each
   card's row takes the page's good, warn and bad, by the common
   thresholds (under 30%, to 50%, above). */
.spending-ring-total.good {
  fill: var(--good);
}

.spending-ring-total.warn {
  fill: var(--warn);
}

.spending-ring-total.bad {
  fill: var(--bad);
}

.finance-credit-share {
  font-weight: 650;
  font-variant-numeric: tabular-nums;
}

.finance-credit-share.good {
  color: var(--good);
}

.finance-credit-share.warn {
  color: var(--warn);
}

.finance-credit-share.bad {
  color: var(--bad);
}

/* The issuer under the card's name, so the name keeps the column's width. */
.finance-credit-institution {
  display: block;
  font-size: var(--text-label);
}

/* Marks a limit worked out from the balances; the note under the table
   says so. */
.finance-credit-mark {
  margin-left: 2px;
}


/* The line under the months chart: the chosen month's income, spending and
   what was left, folding onto more lines on a phone. */
.finance-month-flow {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1) var(--space-4);
  margin: 0;
  font-variant-numeric: tabular-nums;
}

.finance-month-flow strong {
  color: var(--text);
  font-weight: 600;
}

/* A group in the spending table that opens its transactions: the row's
   name, as a link, and as long as it needs to be. */
.finance-group-link {
  color: inherit;
  text-decoration: underline;
  text-decoration-color: var(--border-strong);
  text-underline-offset: 3px;
}

.finance-group-link:hover {
  text-decoration-color: currentColor;
}

/* A group's name gives way to its amount: one line, cut with an ellipsis
   (the whole name is its title), so a long merchant name never pushes the
   amount out of sight on a phone. */
.finance-group-name {
  display: inline-block;
  max-width: 28rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  vertical-align: bottom;
}

/* Month or Year, and the month or the year in a menu between two arrows:
   one row above the Spending panels, at the right on a wide window and the
   whole width on a phone. The same row in both modes, outside every card,
   so the control never moves as the panels under it change. */
.finance-period-bar {
  display: flex;
  justify-content: flex-end;
  margin-bottom: var(--space-3);
}

.finance-period {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-width: 0;
}

.finance-period .segmented {
  flex: 0 0 auto;
}

/* As tall as the menu beside it, so the row reads as one line of controls. */
.finance-period .segmented button {
  padding: 7px 12px;
  font-size: var(--text-body);
}

.finance-period-step {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  min-width: 0;
}

/* Wide enough for the longest month in any of the languages, and the same
   for a year, so neither stepping nor switching Month and Year moves the
   arrows. */
.finance-period-select.select {
  width: 12rem;
  min-width: 0;
}

@media (max-width: 600px) {
  .finance-period-bar {
    justify-content: stretch;
  }

  .finance-period {
    flex: 1 1 auto;
  }

  .finance-period-step {
    flex: 1 1 auto;
  }

  .finance-period-select.select {
    flex: 1 1 auto;
    width: auto;
  }
}

/* On a phone the transactions' filters fold into one line that says which
   hold, opened to change them. */
.finance-filter-disclosure {
  margin-bottom: var(--space-3);
}

.finance-filter-disclosure > summary {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 2px var(--space-2);
  padding: var(--space-2) 0;
  cursor: pointer;
  list-style: none;
}

.finance-filter-disclosure > summary::-webkit-details-marker {
  display: none;
}

/* The disclosure's own arrow, which a flex summary loses: pointing right
   while closed, down while open. */
.finance-filter-disclosure > summary::before {
  content: '';
  align-self: center;
  width: 6px;
  height: 6px;
  margin-right: 2px;
  border-right: 1.5px solid var(--muted);
  border-bottom: 1.5px solid var(--muted);
  transform: rotate(-45deg);
  transition: transform 120ms ease;
}

.finance-filter-disclosure[open] > summary::before {
  transform: rotate(45deg);
}

.finance-filter-disclosure > summary > .muted {
  min-width: 0;
  overflow-wrap: anywhere;
  font-size: var(--text-label);
}

.finance-filter-disclosure[open] > summary {
  margin-bottom: var(--space-2);
}

.finance-transaction-count {
  margin: 0 0 var(--space-2);
  font-size: var(--text-label);
}

/* A transaction is a line, not a card: a compact spending category control
   and little padding around it. The row opens the transaction's details,
   so it shows where the keyboard is. */
.finance-transactions-table .table-surface td {
  padding-top: 5px;
  padding-bottom: 5px;
}

.finance-transactions-table .select {
  padding-top: 4px;
  padding-bottom: 4px;
}

.finance-transactions-table tbody tr:focus-visible {
  outline: 2px solid var(--text);
  outline-offset: -2px;
}

/* What can be done to the chosen transactions, in the row above the table.
   It wraps on a phone rather than pushing the page sideways: the table
   scrolls, the page does not. */
.finance-selection-toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  flex: 1 1 auto;
  min-width: 0;
}

.finance-selection-toolbar label.checkbox {
  margin-top: 0;
}

.finance-selection-toolbar .select {
  min-width: 12rem;
}

/* Every rule about to be saved is listed, up to fifty, so the list scrolls
   inside the dialog rather than hiding the ones past the first few: what is
   saved is what the person could read. */
.finance-rule-proposals {
  margin: 0 0 var(--space-3);
  padding-left: var(--space-5);
  max-height: 40vh;
  overflow-y: auto;
}

.finance-rule-proposals li + li {
  margin-top: var(--space-1);
}

.finance-rule-proposals .finance-rule-proposal-detail {
  display: block;
}

/* The assets a group a kind: the kind's line opens it, and its assets sit
   indented under it, a finance account's holdings under a line naming the
   account. Every row is one line, a long name cut with an ellipsis (its
   title has the whole of it) so the value beside it stays in sight. */
.finance-asset-group {
  cursor: pointer;
  background: color-mix(in srgb, var(--text) 3%, transparent);
}

.finance-asset-group > td {
  vertical-align: middle;
}

.finance-group-toggle {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: 0;
  border: none;
  background: none;
  color: inherit;
  font: inherit;
  font-weight: 600;
  cursor: pointer;
}

.finance-group-toggle:hover {
  background: none;
}

.finance-group-toggle .spending-ring-swatch {
  margin-right: 0;
}

.finance-group-count {
  font-weight: 400;
  font-size: var(--text-label);
}

/* Pointing right while closed, down while open. */
.finance-group-chevron {
  width: 6px;
  height: 6px;
  margin: 0 2px;
  border-right: 1.5px solid var(--muted);
  border-bottom: 1.5px solid var(--muted);
  transform: rotate(-45deg);
  transition: transform 120ms ease;
}

.finance-group-chevron.open {
  transform: rotate(45deg);
}

.finance-asset-row > td:first-child,
.finance-asset-account > td {
  padding-left: 2.25rem;
}

.finance-asset-account > td {
  padding-top: 10px;
  padding-bottom: 4px;
  font-size: var(--text-label);
}

.finance-filters > .finance-asset-search {
  flex: 0 1 24rem;
}

/* The ring and owned, owed and net worth beside it, read together; the
   totals stand alone when there is no ring. */
.finance-worth-summary {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-4) var(--space-6);
  margin-bottom: var(--space-4);
}

.finance-worth-summary > .spending-ring {
  margin-bottom: 0;
}

/* Owned, owed and net worth beside the ring, read together. */
.finance-worth-line {
  display: grid;
  grid-template-columns: max-content auto;
  gap: 4px var(--space-4);
  margin: 0;
  font-variant-numeric: tabular-nums;
}

.finance-worth-line dt {
  color: var(--muted);
}

.finance-worth-line dd {
  margin: 0;
  text-align: right;
}

/* The tax ring's figures: the slices and pay, then each tax and the
   year's, each list closed by its total over a rule. */
.finance-worth-line .finance-tax-estimate-total {
  padding-top: 4px;
  border-top: 1px solid var(--border);
}

.finance-tax-refund {
  color: var(--good);
}

/* What mailing to the tax document address does, under the address. */
.finance-tax-address-hint {
  display: block;
  margin-top: 2px;
}

/* A name, an amount and its share of pay. The columns meet with no gap,
   each figure padded off the one before, so a total's rule runs across
   the line unbroken. */
/* Any width beyond the figures goes to the names, so on a phone the
   amounts and shares stay together at the right edge. */
.finance-tax-estimate .finance-worth-line {
  grid-template-columns: 1fr auto auto;
  column-gap: 0;
}

.finance-tax-estimate .finance-worth-line dd {
  padding-left: var(--space-4);
}

/* The share stretches to its row's height like the cells beside it, so a
   total's rule sits level across the line, its smaller figure centred. */
.finance-tax-estimate .finance-worth-line .finance-tax-share {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  padding-left: var(--space-3);
  font-size: var(--text-label);
}

/* On a phone the ring is centred on its own line and the two lists under
   it take the card's width, so their amounts end at one edge. */
@media (max-width: 600px) {
  .finance-tax-estimate > .spending-ring {
    flex: 1 1 100%;
    justify-content: center;
  }

  .finance-tax-estimate .finance-worth-line {
    width: 100%;
  }
}

.finance-asset-name {
  display: inline-block;
  max-width: 22rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  vertical-align: bottom;
}

@media (max-width: 600px) {
  /* The kind's button keeps a thumb's height; the cell pads none around it. */
  .finance-asset-group > td {
    padding-top: 0;
    padding-bottom: 0;
  }

  .finance-asset-name {
    max-width: calc(100vw - 15rem);
  }
}

/* Everything about one transaction: its fields, then the provider's whole
   record of it as text, in a block that scrolls inside the dialog. */
@media (min-width: 641px) {
  .dialog:has(.finance-transaction-details) {
    max-width: min(640px, calc(100vw - 32px));
  }
}

.finance-transaction-details .properties {
  margin-bottom: var(--space-4);
}

/* The fields come in two lists, the receipts between them, so the label
   column is given one floor in both and their values line up. */
@media (min-width: 601px) {
  .finance-transaction-details .properties {
    grid-template-columns: minmax(10rem, max-content) 1fr;
  }
}

.finance-transaction-details .properties dd {
  min-width: 0;
  overflow-wrap: anywhere;
}

.finance-detail-note {
  display: block;
  margin-top: 4px;
  font-size: var(--text-label);
}

/* A mirrored copy's amount, in its row and its details: still shown, but
   struck through and muted, since it is left out of every total. */
.finance-duplicate-amount {
  color: var(--muted);
  text-decoration: line-through;
}

/* The counted copy's duplicates, one account and day a line. */
.finance-duplicate-list {
  margin: 0;
  padding: 0;
  list-style: none;
}

/* What can be done about a copy, under what is said about it. */
.finance-detail-actions {
  margin-top: var(--space-2);
}

.finance-metadata-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
}

.finance-metadata {
  max-width: 100%;
  max-height: 18rem;
  margin: 0 0 var(--space-3);
  padding: var(--space-2) var(--space-3);
  overflow: auto;
  border: 1px solid var(--border);
  border-radius: var(--radius-1);
  background: var(--field);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 12px;
  line-height: 1.5;
  white-space: pre;
  overflow-wrap: normal;
}

/* Where its two scrollbars meet, the block's own background rather than
   the browser's white square, which showed in dark mode. */
.finance-metadata::-webkit-scrollbar-corner {
  background: var(--field);
}

/* The annotation, edited where it is shown in a transaction's details.
   Its label sits level with the first line in the box, as a label does
   beside a field, rather than on a baseline the box and the buttons under
   it share, which put it halfway down: the box's border and padding are
   the 8 pixels it is lowered by. */
.finance-annotation-field {
  width: 100%;
  min-height: 0;
  resize: vertical;
}

.properties .finance-annotation-term,
.properties .finance-annotation-value {
  align-self: start;
}

.properties .finance-annotation-term {
  padding-top: 8px;
}

@media (max-width: 600px) {
  .properties .finance-annotation-term {
    padding-top: 0;
  }
}

/* A word or two of buttons under what they act on, wrapping on a phone. */
.finance-receipt-buttons {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-top: var(--space-2);
}

/* The receipts matched to a transaction, under its fields: each one a
   bordered block with its head (merchant, day, check, actions), how much of
   the charge it explains, and its lines. */
.finance-receipts {
  margin-bottom: var(--space-4);
}

.finance-receipt {
  margin: 0 0 var(--space-3);
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-1);
}

.finance-receipt-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-2);
}

.finance-receipt-title {
  min-width: 0;
  overflow-wrap: anywhere;
}

.finance-receipt-detail {
  margin: 2px 0 var(--space-2);
  font-size: var(--text-label);
}

/* The photo a receipt was read from, above its lines: tall enough to make
   out the merchant and the totals, short enough that the lines are still
   in view below it. Reading it closely is the lightbox's job. */
.finance-receipt-photo {
  margin: 0 0 var(--space-2);
}

.finance-receipt-photo-image {
  display: block;
  max-width: 100%;
  max-height: 280px;
  width: auto;
  height: auto;
  border: 1px solid var(--border);
  border-radius: var(--radius-2);
  background: var(--field);
}

/* Every line of a receipt is one line high, the way the receipt prints
   it, so the rows are even: the description takes what the quantity and
   the amount leave and is cut with an ellipsis there, its whole text on
   hover. max-width 0 keeps a long description from setting the column's
   width (the same trick as td.truncate), so the amount is never pushed
   past the edge of a phone. It stays a table there rather than becoming a
   block of lines like other tables: a description run off the edge took
   the quantity and the amount with it. */
.finance-receipt-lines {
  display: table;
  width: 100%;
  white-space: normal;
}

.finance-receipt-lines thead th:first-child,
.finance-receipt-lines tbody td:first-child {
  width: 100%;
  max-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

/* One height for every row, whether or not a kind tag sits in it. */
.finance-receipt-lines th,
.finance-receipt-lines td {
  height: 26px;
  line-height: 20px;
  vertical-align: middle;
}

/* An account's name gives way before its "not taxed" mark, which says why
   the row is not counted. */
.finance-tax-preview-account {
  display: flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
}

.finance-tax-preview-account > span:first-child {
  overflow: hidden;
  text-overflow: ellipsis;
}

.finance-tax-preview-account > :not(:first-child) {
  flex: none;
}

.finance-receipt-lines-scroll {
  max-width: 100%;
  overflow-x: auto;
}

.finance-receipt-lines {
  font-size: 13px;
}


.finance-receipt-lines th,
.finance-receipt-lines td {
  padding: 3px 6px;
}

.finance-receipt-lines th:first-child,
.finance-receipt-lines td:first-child {
  padding-left: 0;
}

.finance-receipt-lines th:last-child,
.finance-receipt-lines td:last-child {
  padding-right: 0;
}

.finance-receipt-lines thead th {
  color: var(--muted);
  font-size: var(--text-label);
  font-weight: normal;
}

.finance-receipt-lines td:not(:first-child),
.finance-receipt-lines th:not(:first-child) {
  white-space: nowrap;
}

.finance-receipt-lines .numeric {
  text-align: right;
  font-variant-numeric: tabular-nums;
}

/* A discount sits indented under the item it takes money off, its amount
   in the color of money saved. */
.finance-receipt-discount td:first-child {
  padding-left: var(--space-4);
}

.finance-receipt-discount td.numeric {
  color: var(--good);
}

/* Taxes, fees and tips are muted and set apart from the items above. */
.finance-receipt-tax td,
.finance-receipt-fee td,
.finance-receipt-tip td {
  color: var(--muted);
}

.finance-receipt-item + .finance-receipt-tax td,
.finance-receipt-item + .finance-receipt-fee td,
.finance-receipt-item + .finance-receipt-tip td,
.finance-receipt-discount + .finance-receipt-tax td,
.finance-receipt-discount + .finance-receipt-fee td,
.finance-receipt-discount + .finance-receipt-tip td {
  border-top: 1px solid var(--border-strong);
}

/* The kind of a line that is not an item, before what it says. */
.finance-receipt-kind {
  display: inline-block;
  margin-right: 6px;
  padding: 0 5px;
  border: 1px solid var(--border);
  border-radius: var(--radius-1);
  font-size: 11px;
  font-weight: 550;
  line-height: 16px;
  vertical-align: 1px;
  color: var(--muted);
}

.finance-receipt-lines tfoot th,
.finance-receipt-lines .finance-receipt-subtotal th {
  font-weight: normal;
  color: var(--muted);
  text-align: right;
}

.finance-receipt-lines tfoot td,
.finance-receipt-lines .finance-receipt-subtotal td {
  font-weight: 550;
}

.finance-receipt-lines tbody tr:last-child td,
.finance-receipt-lines tfoot tr:last-child th,
.finance-receipt-lines tfoot tr:last-child td {
  border-bottom: 0;
}

/* What of the charge no matched receipt explains. */
.finance-receipt-unexplained {
  display: flex;
  justify-content: space-between;
  gap: var(--space-3);
  margin: 0 0 var(--space-2);
  padding: var(--space-2) var(--space-3);
  border: 1px dashed var(--warn);
  border-radius: var(--radius-1);
  color: var(--warn);
  font-weight: 550;
}

.finance-receipt-reading {
  margin: 0 0 var(--space-2);
}

/* The receipts that could explain a charge, one a line, each with the
   icon that matches it. */
.finance-receipt-candidates {
  margin: 0;
  padding: 0;
  list-style: none;
}

.finance-receipt-candidates li {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-2);
  padding: var(--space-2) 0;
  border-bottom: 1px solid var(--border);
}

.finance-receipt-candidates li:last-child {
  border-bottom: 0;
}

/* On the Receipts section: the charges a receipt explains, one to a line
   in its cell, each a link to the charge's details. */
.finance-receipt-matched {
  margin: 0;
  padding: 0;
  list-style: none;
}

.finance-receipt-matched .link {
  text-align: start;
}

/* On a phone the Receipts table keeps its day, merchant, total and check
   on the screen: the merchant takes two lines at most in a narrow column
   and the cells pad less, and the columns after the check scroll sideways. */
@media (max-width: 600px) {
  .finance-receipts-table .table-surface th,
  .finance-receipts-table .table-surface td {
    padding-left: 6px;
    padding-right: 6px;
  }

  .finance-receipt-merchant {
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    line-clamp: 2;
    max-width: 6rem;
    overflow: hidden;
    white-space: normal;
    overflow-wrap: anywhere;
    line-height: 1.35;
  }

  /* The charges a receipt matched take two or three short lines, so the
     Matched column is read without scrolling to the end of a long one. */
  .finance-receipt-matched {
    min-width: 8rem;
    max-width: 10rem;
  }

  .finance-receipt-matched .link {
    white-space: normal;
    overflow-wrap: anywhere;
    line-height: 1.35;
  }
}

/* What it is matched to is its own part of the receipt, set apart from
   the date and total line above it. */
.finance-receipt-details > .finance-metadata-head {
  margin-top: var(--space-3);
}

.finance-receipt-details .finance-receipt-lines-scroll {
  margin-top: var(--space-3);
}

/* The Taxes section. The year and the upload sit together where a
   section's one action goes, wrapping under the heading on a phone. */
.finance-taxes-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
}

.finance-tax-year-select.select {
  width: 7rem;
  min-width: 0;
}

/* A group of a paystub's lines, or the W-2 against the paystubs, follows
   the table above it with room to tell the two apart. */
.finance-receipt-lines-scroll + .finance-metadata-head,
.finance-tax-note + .finance-metadata-head {
  margin-top: var(--space-3);
}

.finance-tax-note {
  margin-top: var(--space-2);
}

/* An employer's name in a list row is one line high, cut with an ellipsis
   with the whole name on hover, so every row is the same height and the
   amounts beside it stay on a phone's screen. */
.finance-tax-name {
  display: inline-block;
  max-width: 16rem;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  vertical-align: bottom;
}

@media (max-width: 600px) {
  .finance-tax-name {
    max-width: 8rem;
  }

  .finance-paystubs-table .table-surface th,
  .finance-paystubs-table .table-surface td {
    padding-left: 6px;
    padding-right: 6px;
  }
}

.finance-tax-findings li > div {
  min-width: 0;
  overflow-wrap: anywhere;
}

.finance-tax-findings .link {
  text-align: start;
}

/* A paystub's take-home pay, under its lines: the figure the rest add up
   to, set apart as a total. */
.finance-paystub-net {
  display: flex;
  justify-content: space-between;
  gap: var(--space-3);
  margin: var(--space-3) 0 0;
  padding-top: var(--space-2);
  border-top: 1px solid var(--border-strong);
  font-weight: 550;
}

.finance-paystub-net .numeric-value {
  font-variant-numeric: tabular-nums;
}

/* In the transactions list: the mark of a row with a receipt, and the
   annotation under the description, one line cut short. */
.finance-receipt-mark {
  display: inline-flex;
  vertical-align: middle;
  color: var(--muted);
}

.finance-annotation-line {
  display: block;
  max-width: 28rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--muted);
  font-size: var(--text-label);
}

@media (max-width: 600px) {
  .finance-annotation-line {
    max-width: 14rem;
  }
}

@media (max-width: 600px) {
  /* Room left beside the swatch, the amount and its share. */
  .finance-group-name {
    max-width: calc(100vw - 14rem);
  }

  /* On a phone the controls keep the height a thumb needs, so the cells
     pad less still, and a long description takes two lines rather than
     running off the edge. */
  .finance-transactions-table .table-surface td {
    padding-top: 4px;
    padding-bottom: 4px;
  }

  .finance-description {
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    line-clamp: 2;
    min-width: 7rem;
    max-width: 10rem;
    white-space: normal;
    line-height: 1.35;
  }

  /* Width reclaimed for the category, the column the row is for: the cells
     pad less and the dropdown is only as wide as its words need. */
  .finance-transactions-table .table-surface th,
  .finance-transactions-table .table-surface td {
    padding-left: 6px;
    padding-right: 6px;
  }

  .finance-category-cell {
    min-width: 9rem;
  }

  .finance-category-cell .select {
    min-width: 0;
  }
}

/* A currency's part of a converted total, under it: a detail, not a second
   total. */
.finance-currency-part td {
  color: var(--muted);
  font-weight: normal;
}

/* The person's herdr sessions: a question with its options as buttons, a
   session's last turns or its screen, and a box to type into it. The same
   question card is drawn in the drawer, under the line it was written under,
   and in the session's dialog on the agent page. */
.herdr-question {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: var(--space-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-2);
  background: var(--surface);
}

.herdr-question-card {
  margin-top: var(--space-2);
}

.herdr-question-kind {
  margin: 0;
  font-size: var(--text-label);
}

/* What the coding agent asked, as it drew it: a plan or a command keeps
   its lines. */
.herdr-question-text {
  max-height: 14em;
  margin: 0;
  overflow-y: auto;
  font-family: inherit;
  font-size: var(--text-body);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

.herdr-question-options {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

/* One option a button, the whole width, its label and under it what the
   coding agent said of it: a tap on a phone needs the room. */
.herdr-question-option {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 2px;
  width: 100%;
  padding: var(--space-2) var(--space-3);
  text-align: left;
  white-space: normal;
  overflow-wrap: anywhere;
}

/* The generic "label span" is a muted caption above a field; here the
   span is the option itself, its label in the text's color and its
   description muted under it. */
.herdr-question-option.tick > span {
  display: block;
  margin: 0;
  color: var(--text);
  font-size: var(--text-body);
}

.herdr-question-option.tick > span > .muted {
  display: block;
  margin-top: 2px;
  color: var(--muted);
  font-size: var(--text-label);
}

.herdr-question-option.tick {
  flex-direction: row;
  align-items: flex-start;
  gap: var(--space-2);
  margin-bottom: 0;
  border: 1px solid var(--border);
  border-radius: var(--radius-1);
  color: var(--text);
  font-size: var(--text-body);
  cursor: pointer;
}

.herdr-question-option .muted {
  font-size: var(--text-label);
}

.herdr-question-free {
  display: flex;
  gap: var(--space-2);
}

.herdr-question-free input {
  flex: 1;
  min-width: 0;
}

.herdr-question-done {
  margin: var(--space-1) 0 0;
  font-size: var(--text-label);
}

/* The pane as it stands: kept as drawn, so its forms line up, and scrolled
   sideways in its own box rather than the page's. */
/* A terminal's ground and text in either theme: the colors a program
   draws on its screen were chosen against a dark one. */
.terminal-text {
  --terminal-background: #1e1e1e;
  --terminal-text: #d4d4d4;
  background: var(--terminal-background);
  color: var(--terminal-text);
}

.herdr-screen {
  margin: 0;
  padding: var(--space-2) var(--space-3);
  overflow: auto;
  border: 1px solid var(--border);
  border-radius: var(--radius-1);
  font-size: 12px;
  line-height: 1.4;
  white-space: pre;
}

/* The turns are the conversation's own bubbles (.agent-line), in a column. */
.herdr-turns {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin: 0;
  padding: 0;
  list-style: none;
}

.herdr-turn {
  display: flex;
  flex-direction: column;
}

.herdr-turn .agent-line.user {
  align-self: flex-end;
}

.herdr-turn .agent-line.assistant {
  align-self: flex-start;
}

/* A tool call is one muted line in the terminal's own face. */
.herdr-tool-line {
  font-family: var(--mono, ui-monospace, monospace);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* A tool call with its detail: the line is what is clicked, and opens to
   the input and the answer in the terminal's face. */
.herdr-tool > summary {
  display: block;
  cursor: pointer;
  list-style: none;
}

.herdr-tool > summary::-webkit-details-marker {
  display: none;
}

.herdr-tool > summary:hover {
  color: var(--text);
}

.herdr-tool-detail {
  margin: var(--space-1) 0 var(--space-2);
  padding: var(--space-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-1);
  background: var(--field);
}

.herdr-tool-detail h5 {
  margin: 0 0 var(--space-1);
  color: var(--muted);
  font-size: 12px;
}

.herdr-tool-detail pre {
  margin: 0 0 var(--space-2);
  max-height: 24em;
  overflow: auto;
  font-size: 12px;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

.herdr-tool-detail pre:last-child {
  margin-bottom: 0;
}

/* The send form is drawn with the drawer's classes (agent-drawer-input,
   agent-send, agent-drawer-pending), so the two boxes cannot drift apart.
   What differs is the growth: this box grows with the words, from JS, and
   scrolls only past its tallest, so an empty one never shows a scrollbar for
   a rounding pixel. */
.herdr-send textarea {
  overflow-y: hidden;
}

.herdr-send textarea.is-overflowing {
  overflow-y: auto;
}

/* One line a session, the pane first and how it stands beside it: on a
   phone those two are what is in view before the table scrolls. */
.herdr-sessions td {
  white-space: nowrap;
  vertical-align: middle;
}

.herdr-hooks table {
  width: 100%;
}

.herdr-hooks-actions {
  display: flex;
  justify-content: flex-end;
  gap: var(--space-2);
}

/* Under the agent's words that say what a coding session asks: its
   options, the drawer's width, to read and tap on a phone. */
.agent-line.herdr-question-line {
  align-self: stretch;
  max-width: 100%;
}

.agent-line.herdr-question-line .herdr-question-card {
  margin-top: 0;
}

/* A coding session's question in the drawer is one box: a head that says
   what it asks, what it was answered with, and who asks, and under it the
   options. Answered, it folds to its head, which opens it again in place:
   the same box, the same head, the options frozen with the chosen one
   marked. */
.agent-line.herdr-question-line {
  align-self: stretch;
  max-width: 100%;
}

.herdr-question-box {
  border: 1px solid var(--border);
  border-radius: var(--radius-2);
  background: var(--surface);
  overflow: hidden;
}

.herdr-question-head {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
  width: 100%;
  margin: 0;
  padding: var(--space-2) var(--space-3);
  border: 0;
  border-radius: 0;
  background: none;
  color: var(--text);
  font: inherit;
  text-align: left;
}

button.herdr-question-head {
  cursor: pointer;
}

.herdr-question-head > svg {
  flex: 0 0 auto;
  margin-top: 3px;
}

.herdr-question-box.waiting .herdr-question-head > svg:first-child {
  color: var(--warn);
}

.herdr-question-box.folded .herdr-question-head > svg:first-child {
  color: var(--good);
}

.herdr-question-head > svg:last-child:not(:first-child) {
  margin-left: auto;
  color: var(--muted);
}

.herdr-question-head-text {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.herdr-question-head-asked {
  font-size: var(--text-body);
  font-weight: 600;
  overflow-wrap: anywhere;
}

.herdr-question-head-answer {
  color: var(--good);
}

.herdr-question-head-who {
  color: var(--muted);
  font-size: var(--text-label);
  overflow-wrap: anywhere;
}

/* The options sit in the same box, under a hairline, without a box of
   their own. */
.herdr-question-body {
  padding: 0 var(--space-3) var(--space-3);
}

.herdr-question-body .herdr-question-card {
  margin: 0;
}

.herdr-question-body .herdr-question {
  padding: var(--space-2) 0 0;
  border: 0;
  border-top: 1px solid var(--border);
  border-radius: 0;
  background: none;
}

/* Frozen: nothing to press, the chosen option marked. */
.herdr-question.frozen .herdr-question-option {
  opacity: 0.6;
  cursor: default;
}

.herdr-question.frozen .herdr-question-option.chosen {
  border-color: var(--good);
  opacity: 1;
}

.herdr-question-option.chosen strong > svg {
  margin-right: 4px;
  vertical-align: -2px;
  color: var(--good);
}

.herdr-question-typed {
  margin: 0;
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--good);
  border-radius: var(--radius-1);
  overflow-wrap: anywhere;
}

.herdr-question-unknown {
  margin: 0;
  font-size: var(--text-label);
}

/* Who asks, over the question: the coding agent, its pane, its computer. */
.herdr-question-who {
  display: flex;
  flex-wrap: wrap;
  gap: 0 var(--space-2);
  margin: 0 0 var(--space-2);
  color: var(--muted);
  font-size: var(--text-label);
}

.herdr-question-who strong {
  color: var(--text);
}

.herdr-question-checking {
  margin: 0;
  font-size: var(--text-label);
}

/* Folded, the head keeps to two lines; opened, it says the whole. */
.herdr-question-box.folded:not(.open) .herdr-question-head-asked {
  display: -webkit-box;
  overflow: hidden;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
}

.recording-original { display: flex; flex-direction: column; gap: var(--space-2); }
.recording-player { display: block; width: 100%; max-height: 65vh; }
.recording-player[hidden] { display: none; }
/* The one player: a play button, the time, a scrubber along the recording's
   whole timeline, and its length. The range input lies over the drawn track,
   invisible, so a drag, a tap and the arrow keys all reach it. */
.recording-player-controls { display: flex; align-items: center; gap: var(--space-2); }
.recording-player-time { font-size: 13px; font-variant-numeric: tabular-nums; min-width: 2.75rem; }
.recording-player-time:last-child { text-align: right; }
.recording-scrubber { position: relative; flex: 1; min-width: 0; height: 28px; }
.recording-scrubber-track {
  position: absolute;
  inset: 11px 0;
  border-radius: 3px;
  background: var(--border-strong);
}
.recording-scrubber-fill { position: absolute; inset: 0 auto 0 0; border-radius: 3px; background: var(--accent); }
/* Time between two pieces that nobody captured, which the player skips. */
.recording-scrubber-gap { position: absolute; top: -2px; bottom: -2px; background: var(--surface); }
.recording-scrubber-thumb {
  position: absolute;
  top: 50%;
  width: 14px;
  height: 14px;
  margin: -7px 0 0 -7px;
  border-radius: 50%;
  background: var(--accent);
  box-shadow: 0 0 0 2px var(--surface);
}
.recording-scrubber:focus-within .recording-scrubber-thumb { box-shadow: 0 0 0 2px var(--surface), 0 0 0 4px var(--border-strong); }
.recording-scrubber input[type='range'] {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  opacity: 0;
  cursor: pointer;
}
.recording-progress { width: 100%; }

/* --- a recording as a conversation -----------------------------------------
   The bubbles are the agent chat's own (`.agent-line user` for the account
   owner on the right, `.agent-line assistant` for everyone else on the left),
   so only what a recording adds is styled here. */
.recording-detail {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  max-width: 820px;
}
/* The title sits above the column, so the More menu beside it ends where the
   column does rather than at the far edge of the window. */
.content:has(.recording-detail) .page-heading-row {
  max-width: 820px;
}
.recording-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-3);
}
.recording-header-text {
  min-width: 0;
}
.recording-header h2 {
  margin: 0 0 4px;
  overflow-wrap: anywhere;
}
/* The page heading above already says the title on a wide screen. */
@media (min-width: 901px) {
  .recording-header h2 {
    display: none;
  }
}
.recording-original .muted {
  margin: 0;
  font-size: 13px;
}
/* The original's buttons are the dashboard's own action row; only its margins
   go, since the page's column already spaces what is in it. */
.recording-original .page-actions {
  margin: 0;
}
.recording-meta {
  margin: 0;
  color: var(--muted);
}
.recording-summary h3 {
  margin: 0 0 4px;
}
.recording-summary p {
  margin: 0;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}
.recording-title-link {
  font-weight: 600;
}
.recording-meta-line {
  display: none;
}
@media (max-width: 760px) {
  .recording-title-link {
    display: block;
  }
  .recording-meta-line {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 2px var(--space-3);
    margin-top: 2px;
    font-size: 12px;
  }
}
.recording-conversation {
  display: flex;
  flex-direction: column;
  gap: 8px;
  font-size: 13.5px;
  line-height: 1.5;
  /* A swiped bubble carries its mark beside it; neither widens the page. */
  overflow-x: clip;
}
.recording-turn {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 2px;
}
.recording-turn.own {
  align-items: flex-end;
}
.recording-turn-name {
  padding: 0 10px;
  font-size: var(--text-label);
  font-weight: 600;
}
.recording-turn-name.muted {
  font-weight: 500;
}
.recording-passage.cited {
  background: color-mix(in srgb, currentColor 22%, transparent);
}
.recording-conversation-filter {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
}
.recording-earlier-notice {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
}
.recording-drawer-scrim {
  position: fixed;
  inset: 0;
  z-index: 42;
  display: flex;
  justify-content: flex-end;
  background: rgb(0 0 0 / 0.35);
}
.recording-drawer {
  display: flex;
  flex-direction: column;
  width: min(560px, 100%);
  height: 100%;
  background: var(--background);
  border-left: 1px solid var(--border);
  box-shadow: var(--shadow-floating);
}
.recording-drawer-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 12px 16px;
  border-bottom: 1px solid var(--border);
}
.recording-drawer-head h3 {
  margin: 0;
}
.recording-drawer-body {
  flex: 1;
  overflow-y: auto;
  padding: 16px;
  /* The last item scrolls up above the iPhone's home indicator. */
  padding-bottom: calc(16px + env(safe-area-inset-bottom));
}

/* The player that floats over the page while the recording plays: a pause
   button, the position and a close, at the foot of the window above the
   agent's button. The room after the conversation keeps the last bubble clear. */
.recording-floating-player {
  position: fixed;
  left: 50%;
  bottom: calc(76px + env(safe-area-inset-bottom));
  z-index: 29;
  display: flex;
  align-items: center;
  gap: var(--space-2);
  transform: translateX(-50%);
  padding: var(--space-1) var(--space-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-round);
  background: var(--surface);
  box-shadow: var(--shadow-floating);
}
/* The time keeps its width as it counts, centered between the two buttons. */
.recording-floating-player .recording-player-time {
  text-align: center;
}
.recording-floating-player-room {
  height: 72px;
}

/* --- the recording bar -----------------------------------------------------
   Fixed at the foot of the dashboard while a recording is made or sent, above
   the agent's button (44px high, 20px from the corner) so neither covers the
   other. With the agent's box open it stands beside the box, not under it. */
.recording-bar {
  position: fixed;
  right: 20px;
  bottom: calc(76px + env(safe-area-inset-bottom));
  z-index: 29;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  box-sizing: border-box;
  width: min(380px, calc(100vw - 40px));
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-3);
  background: var(--surface);
  box-shadow: var(--shadow-floating);
  font-size: var(--text-body);
}
.recording-bar-main {
  display: flex;
  flex: 1 1 0;
  align-items: center;
  gap: var(--space-2);
  min-width: 0;
  color: inherit;
  text-decoration: none;
}
.recording-bar-dot {
  flex: none;
  width: 10px;
  height: 10px;
  border-radius: var(--radius-round);
  background: var(--bad);
}
.recording-bar-dot.paused,
.recording-bar-dot.idle,
.recording-bar-dot.starting,
.recording-bar-dot.stopping {
  background: var(--muted);
}
@media (prefers-reduced-motion: no-preference) {
  .recording-bar-dot.recording {
    animation: recording-bar-pulse 1.6s ease-in-out infinite;
  }
}
@keyframes recording-bar-pulse {
  50% {
    opacity: 0.35;
  }
}
.recording-bar-time {
  flex: none;
  font-variant-numeric: tabular-nums;
  font-weight: 600;
}
.recording-bar-words {
  flex: 1 1 0;
  min-width: 0;
  overflow: hidden;
  color: var(--muted);
  text-overflow: ellipsis;
  white-space: nowrap;
}
.recording-bar-note {
  flex-basis: 100%;
  margin: 0;
  font-size: var(--text-label);
}
.recording-bar .row-actions {
  flex: none;
}
@media (min-width: 721px) {
  body:has(.agent-drawer:not(.standalone)) .recording-bar {
    right: 476px;
    bottom: 20px;
    width: min(380px, calc(100vw - 496px));
  }
}
/* The page keeps its last lines clear of the bar. */
body.has-recording-bar .content {
  padding-bottom: calc(140px + env(safe-area-inset-bottom));
}
.recording-turn.live {
  align-items: flex-start;
}
.recording-live-line {
  opacity: 0.85;
  font-style: italic;
}
.recording-live-note {
  margin: 0;
}
.recording-live-line {
  width: fit-content;
  max-width: 100%;
}

/* A paystub's lines carry up to five amounts each: the dialog is wider than
   a receipt's, the line's name keeps room for a word or two before it is
   cut, and on a narrow screen the table scrolls sideways inside its box
   rather than squeezing the names to a letter. The totals row's label is
   one line. */
.dialog.dialog-wide:has(.finance-paystub-lines) {
  max-width: min(780px, calc(100vw - 32px));
}

.finance-paystub-lines thead th:first-child,
.finance-paystub-lines tbody td:first-child {
  width: auto;
  min-width: 11rem;
  max-width: 16rem;
}

.finance-paystub-lines tfoot th {
  white-space: nowrap;
}

.finance-paystub-lines th,
.finance-paystub-lines td {
  white-space: nowrap;
}

/* --- recording by hand -------------------------------------------------
   The dialogs that record a receipt, a paystub, a tax form or the rows of
   a statement. Their fields sit in a grid that folds to one column on a
   phone; a repeated line (a receipt line, a box, a row) is a bordered group
   of its own fields with its remove button at the end. */
.finance-entry-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
}

.dialog.dialog-wide:has(.finance-entry-fields) {
  max-width: min(780px, calc(100vw - 32px));
}

.finance-entry-fields {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr));
  column-gap: var(--space-3);
}

.finance-entry-label {
  display: block;
  color: var(--muted);
  font-size: 13px;
  margin-bottom: 3px;
}

.finance-entry-fields > div {
  margin-bottom: var(--space-3);
}

.finance-entry-rate {
  margin-top: var(--space-1);
}

.finance-entry-lines {
  border: 1px solid var(--border);
  border-radius: var(--radius-3);
  margin: 0 0 var(--space-3);
  padding: var(--space-3);
  min-width: 0;
}

.finance-entry-lines legend {
  padding: 0 var(--space-1);
  font-weight: 600;
}

.finance-entry-line {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(7rem, 1fr));
  column-gap: var(--space-2);
  align-items: end;
  border-bottom: 1px solid var(--border);
  margin-bottom: var(--space-3);
}

.finance-entry-line .finance-entry-wide {
  grid-column: span 2;
}

.finance-entry-line .finance-entry-line-number {
  grid-column: 1 / -1;
  margin-bottom: var(--space-1);
}

.finance-entry-line .finance-entry-line-remove {
  justify-self: end;
  margin-bottom: var(--space-3);
}

.finance-entry-preview {
  border-top: 1px solid var(--border);
  padding-top: var(--space-3);
  margin-bottom: var(--space-3);
}

.finance-entry-preview p {
  margin: var(--space-1) 0;
}

.finance-entry-candidates {
  list-style: none;
  margin: 0 0 var(--space-3);
  padding: 0;
}

.finance-entry-candidates li {
  display: flex;
  flex-direction: column;
  padding: var(--space-1) 0;
}

.finance-entry-rows-table {
  margin-top: var(--space-2);
}

.finance-entry-rows-table caption {
  text-align: left;
  color: var(--muted);
  font-size: 13px;
  padding: var(--space-2) var(--space-3);
}

.finance-entry-rows-table td,
.finance-entry-rows-table th {
  white-space: nowrap;
}

@media (max-width: 600px) {
  .finance-entry-line .finance-entry-wide {
    grid-column: span 1;
  }
}

/* --- the Coding page -------------------------------------------------------
   The frame takes the height left on the page, as the knowledge and the mail
   pages do, so the transcript scrolls inside it and the box to type into
   stays at the foot. A list and the session beside it where there is room,
   one at a time where there is not: the frame's own width decides, and the
   address says which. */
.coding-frame {
  container-type: inline-size;
  container-name: coding;
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
}

main.content:has(> .coding-frame) {
  display: flex;
  flex-direction: column;
}

main.content:has(> .coding-frame) > :not(.coding-frame) {
  flex-shrink: 0;
}

.coding-split {
  display: grid;
  /* The mailbox's widths, so the two pages read the same. */
  grid-template-columns: minmax(280px, 380px) minmax(0, 1fr);
  flex: 1 1 auto;
  min-width: 0;
  min-height: 0;
  border: 1px solid var(--border);
  border-radius: 8px;
  overflow: hidden;
  background: var(--surface);
}

.coding-list {
  display: flex;
  flex-direction: column;
  min-width: 0;
  min-height: 0;
  border-right: 1px solid var(--border);
}

/* On a phone, where the heading is not drawn, the way to open a session sits
   at the top of the list, at the right. */
.coding-toolbar {
  display: flex;
  justify-content: flex-end;
  padding: var(--space-2) var(--space-3);
  border-bottom: 1px solid var(--border);
}

/* On a phone the list starts right under the top bar, as a list app's does,
   rather than below the page's usual top margin. */
@media (max-width: 600px) {
  main.content:has(> .coding-frame) {
    padding-top: 0;
  }
}

.coding-list-rows {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
}

/* Each computer is a band across the list, as wide as the rows under it,
   that stays at the top while its sessions scroll and folds them away. */
.coding-computer h3 {
  position: sticky;
  top: 0;
  z-index: 1;
  display: flex;
  align-items: stretch;
  margin: 0;
  border-bottom: 1px solid var(--border);
  background: var(--field);
  font-size: inherit;
}

/* The computer's menu sits at the end of its header, on the same ground. */
.coding-computer h3 .menu-button {
  flex: 0 0 auto;
  align-self: center;
  margin-right: 6px;
}

.coding-computer-head {
  display: flex;
  align-items: center;
  gap: 6px;
  flex: 1 1 auto;
  min-width: 0;
  min-height: 40px;
  padding: 8px 10px;
  border: 0;
  background: transparent;
  color: inherit;
  font: inherit;
  font-weight: 600;
  text-align: left;
  cursor: pointer;
}

.coding-computer-name {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.coding-computer-counts {
  margin-left: auto;
  flex: 0 0 auto;
  font-size: 12px;
  font-weight: 400;
}

.coding-workspace h4 {
  display: flex;
  align-items: center;
  gap: 6px;
  margin: 0;
  padding: 10px 10px 4px;
  color: var(--muted);
  font-size: 12px;
  font-weight: 600;
}

.coding-row {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  width: 100%;
  min-height: 44px;
  padding: 8px 10px 8px 30px;
  border: 0;
  border-bottom: 1px solid var(--border);
  background: none;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
}

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

.coding-row.active {
  background: var(--sidebar-hover);
}

.coding-row-text {
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
  min-width: 0;
}

.coding-row-text > * {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.coding-row-text .muted {
  font-size: 12px;
}

.coding-question-mark {
  display: inline-flex;
  flex: 0 0 auto;
  align-items: center;
  justify-content: center;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: var(--warn);
  color: var(--background);
  font-size: 12px;
  font-weight: 700;
}

.coding-pane {
  display: flex;
  flex-direction: column;
  min-width: 0;
  min-height: 0;
}

.coding-gone {
  padding: var(--space-4);
}

.coding-session {
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
  min-width: 0;
  min-height: 0;
}

/* One row: the way back, the title over where the session is, and the
   actions. The title and the directory give way to the actions. */
.coding-header {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
  border-bottom: 1px solid var(--border);
}

.coding-header-text {
  flex: 1 1 0;
  min-width: 0;
}

.coding-header h2 {
  margin: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.coding-header p {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin: 2px 0 0;
  font-size: var(--text-label);
  white-space: nowrap;
}

.coding-header p > .mono {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}

.coding-header .row-actions {
  flex: 0 0 auto;
  flex-wrap: nowrap;
  align-items: center;
}

/* Only a narrow frame needs the way back to the list. */
.coding-back {
  display: none;
}

.coding-tabstrip {
  display: flex;
  align-items: flex-end;
  gap: var(--space-1);
  padding: 0 var(--space-3);
  border-bottom: 1px solid var(--border);
}

.coding-tabstrip .tabs {
  flex: 1 1 auto;
  min-width: 0;
  margin-bottom: 0;
  border-bottom: 0;
}

.coding-tabstrip .tabs button {
  /* Holds the hidden "On screen" words inside the strip, which scrolls:
     placed against the page instead, a tab far along the strip pushed the
     page wider than a phone. */
  position: relative;
  min-height: 44px;
  white-space: nowrap;
}

.coding-tabstrip .menu-button {
  align-self: center;
}

.herdr-onscreen {
  display: inline-block;
  width: 7px;
  height: 7px;
  margin-left: 6px;
  vertical-align: middle;
  border-radius: 50%;
  background: var(--accent, var(--text));
}

/* The room is there on every tab, so choosing one moves nothing. */
.herdr-onscreen.reserved {
  background: none;
}


/* The transcript, or the screen, with the button that switches between
   them floating over its top right corner. */
.coding-body {
  position: relative;
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
  min-height: 0;
}

.coding-view-switch {
  position: absolute;
  top: var(--space-2);
  right: var(--space-3);
  z-index: 1;
  background: var(--surface);
  box-shadow: 0 1px 3px rgb(0 0 0 / 12%);
}

.coding-view-switch button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 40px;
  min-height: 36px;
}

.coding-scroll {
  flex: 1 1 auto;
  min-height: 0;
  padding: var(--space-2) var(--space-3);
  overflow-y: auto;
  overscroll-behavior: contain;
}

/* The agents tree scrolls sideways with the whole pane, so that the bar is at
   the pane's foot rather than under a short tree in the middle of it. */
.coding-scroll.showing-agents {
  overflow-x: auto;
}

.coding-scroll.showing-agents .agents-tree {
  overflow-x: visible;
}

/* The heading stays at the pane's left while the tree scrolls under it: the
   view is as wide as the tree, and the heading only as wide as its words, so
   that it has room to stay. */
.coding-scroll.showing-agents .agents-view {
  width: max-content;
  min-width: 100%;
}

.coding-scroll.showing-agents .agents-heading {
  position: sticky;
  left: 0;
  width: fit-content;
}

.coding-earlier {
  display: block;
  width: 100%;
  min-height: 44px;
  margin-bottom: var(--space-2);
}

/* The box inside is the drawer's own (agent-drawer-input), padding and rule
   included, and it runs the whole width of the pane. */
.coding-composer[hidden] {
  display: none;
}

/* At the end of the transcript, scrolling with it. */
.coding-question {
  margin-top: var(--space-3);
}

/* The assistant's round button floats over the window's bottom right corner,
   which on a wide window is where this box's send mark is. The box used to
   keep that corner empty with padding on its right, which left a band of
   white beside the send mark; the button rides above the box instead. Up to
   900px the page's own bottom padding already leaves it room. */
@media (min-width: 901px) {
  body:has(.coding-composer:not([hidden])) .agent-drawer-toggle {
    bottom: 96px;
  }

  /* Raised there, the button reaches over the pane's right edge just above
     the box, where a person's lines end; the transcript keeps clear of it. */
  .coding-session:has(.coding-composer:not([hidden])) .coding-scroll {
    padding-right: calc(var(--space-3) + 24px);
  }
}

.herdr-tab-label {
  display: inline-block;
  vertical-align: bottom;
  max-width: 16em;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

@container coding (max-width: 860px) {
  .coding-split {
    grid-template-columns: minmax(0, 1fr);
  }
  .coding-list {
    border-right: 0;
  }
  .coding-split.reading .coding-list {
    display: none;
  }
  .coding-split:not(.reading) .coding-pane {
    display: none;
  }
  .coding-back {
    display: flex;
  }
}

/* On a phone the page is the list, as the mailbox is: the frame runs to the
   screen's edges with no border at the sides, rather than spending the
   width on the page's padding and a box. The terminal's screen, wider than
   a phone already, takes every column of it. */
@media (max-width: 600px) {
  .coding-split {
    margin-left: calc(var(--space-3) * -1);
    margin-right: calc(var(--space-3) * -1);
    border-left: none;
    border-right: none;
    border-radius: 0;
  }

  .coding-scroll.showing-screen {
    padding: 0;
  }

  .coding-scroll.showing-screen .herdr-screen {
    min-height: 100%;
    border: none;
    border-radius: 0;
  }

  .coding-scroll.showing-screen .coding-question {
    margin: var(--space-3);
  }
}

.coding-empty {
  margin: var(--space-4) 0;
  text-align: center;
}

/* The drawer's thinking dots under the last turn, while the session works. */
.coding-working {
  width: fit-content;
  margin-top: var(--space-2);
}

/* --- the Agents view -------------------------------------------------------
   One list of cards, drawn two ways by the frame's width. Wide: the session's
   card centered at the top, its agents in a row under it with lines joining
   them to it, and each agent's own agents in a column under it with a line
   down its left. Narrow: every level is an indented list with the line down
   its left, so that nothing pushes the page sideways. A wide tree with more
   agents than the pane has room for scrolls inside its own box. */
.agents-view {
  padding-bottom: var(--space-3);
}

.agents-heading {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-1) var(--space-2);
  min-height: 36px;
  margin: 0 0 var(--space-2);
  /* The view switch floats over this corner. */
  padding-right: 136px;
  font-size: var(--text-label);
}

.agents-heading .muted {
  font-weight: 400;
}

.agents-tree {
  /* The lines are the muted text's color: the border's is too faint to
     read as a graph in dark mode. */
  --agents-line: var(--muted);
  --agents-gap: 12px;
  /* A tree wider than the pane scrolls sideways here, never the page. */
  overflow-x: auto;
  overscroll-behavior-x: contain;
  padding-bottom: var(--space-2);
}

/* As wide as its widest row of agents, so that the session's card is over
   the middle of them, and never narrower than the pane. */
.agents-graph {
  display: flex;
  flex-direction: column;
  align-items: center;
  width: max-content;
  min-width: 100%;
}

.agents-root {
  display: flex;
  justify-content: center;
}

/* A node of the graph is a box on every screen: the phone's rule that runs
   cards to the screen's edges without sides would make the tree a list. */
.agents-tree .agent-card {
  border: 1px solid var(--border);
  border-radius: var(--radius-3);
}

.agent-card {
  display: flex;
  flex-direction: column;
  gap: 6px;
  width: 260px;
  max-width: 100%;
  margin: 0;
  padding: 10px 12px;
}

.agent-card-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-2);
}

.agent-card-title {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.agent-card-title > * {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.agent-card-title .muted {
  font-size: 12px;
}

.agent-badge {
  display: inline-flex;
  flex: 0 0 auto;
  align-items: center;
  gap: 5px;
}

.agent-badge .agent-dots i {
  background: currentColor;
}

.agent-card-task {
  display: -webkit-box;
  margin: 0;
  overflow: hidden;
  font-size: 13px;
  overflow-wrap: anywhere;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  line-clamp: 2;
}

.agent-card-foot {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-2);
  font-size: 12px;
}

.agent-card-tools {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.agent-card-time {
  flex: 0 0 auto;
  margin-left: auto;
}

.agent-card-toggle {
  display: inline-flex;
  align-items: center;
  align-self: flex-start;
  gap: 4px;
  min-height: 28px;
  padding: 2px 8px 2px 4px;
  border-color: transparent;
  background: transparent;
  color: var(--muted);
  font-size: 12px;
}

.agent-card-toggle:hover:not(:disabled) {
  color: var(--text);
  background: var(--sidebar-hover);
}

.agents-children {
  margin: 0;
  padding: 0;
  list-style: none;
}

.agents-children > li {
  position: relative;
}

/* Wide: the first level is a row hung from the session by a stem, each
   card dropped from a bar that runs from the first card to the last. */
.agents-top {
  position: relative;
  display: flex;
  justify-content: center;
  align-items: flex-start;
  padding-top: var(--agents-gap);
}

.agents-top::before {
  content: '';
  position: absolute;
  top: 0;
  left: 50%;
  height: var(--agents-gap);
  border-left: 1px solid var(--agents-line);
}

/* A dot where the session's line meets the session's card. */
.agents-top::after {
  content: '';
  position: absolute;
  top: -4px;
  left: 50%;
  width: 7px;
  height: 7px;
  border: 1px solid var(--agents-line);
  border-radius: 50%;
  background: var(--surface);
  transform: translateX(-50%);
}

.agents-top > li {
  flex: 0 0 auto;
  padding: var(--agents-gap) 6px 0;
}

.agents-top > li::before,
.agents-top > li::after {
  content: '';
  position: absolute;
  top: 0;
  width: 50%;
  height: var(--agents-gap);
  border-top: 1px solid var(--agents-line);
}

.agents-top > li::before {
  right: 50%;
}

.agents-top > li::after {
  left: 50%;
  border-left: 1px solid var(--agents-line);
}

.agents-top > li:first-child::before,
.agents-top > li:last-child::after {
  border-top: 0;
}

/* Deeper levels are a column under the card with a line down its left. */
.agents-nested {
  margin: var(--space-2) 0 0 12px;
  padding-left: 12px;
  border-left: 1px solid var(--agents-line);
}

.agents-nested > li + li {
  margin-top: var(--space-2);
}

.agents-nested .agent-card {
  width: 100%;
}

/* In a narrow pane the cards are narrower, so that more of the tree is in
   view; it still scrolls sideways when it is wider. */
@container coding (max-width: 860px) {
  .agent-card {
    width: 200px;
  }
}

/* --- photos ---------------------------------------------------------------
   The library's timeline, the viewer over it, and the upload tray. */
.photos-page {
  position: relative;
}

.photos-page > .settings-section {
  margin-top: var(--space-4);
}

/* The one row under the tabs, on every page of Photos: what the page searches
   with at the left and what can be done on it at the right. Its height is the
   height of a control, so that the row is as tall with a selection (the bar
   takes its place) as without, with nothing to search as with, and the page
   does not move when any of them changes. */
.photo-toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2) var(--space-3);
  margin-bottom: var(--space-4);
  min-height: 40px;
}

.photo-toolbar-tools {
  display: flex;
  flex: 1 1 280px;
  align-items: center;
  gap: var(--space-2);
  min-width: 0;
}

.photo-toolbar-tools:empty {
  display: none;
}

.photo-toolbar-tools input[type='search'] {
  flex: 1 1 160px;
  max-width: 440px;
  min-width: 0;
  width: auto;
}

.photo-toolbar-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-end;
  gap: var(--space-2);
  margin-left: auto;
}

.photo-toolbar-actions:empty {
  display: none;
}

/* Every control of the row is one height, whatever it is: the search, the
   filter, the words and the icons. */
.photo-toolbar input[type='search'],
.photo-toolbar .photo-filter-button,
.photo-toolbar-actions > button {
  min-height: 40px;
}

.photo-toolbar-actions > button,
.photo-filter-button {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
}

/* An icon in the row is a button of the row's height with the border the
   others have, not the bare 28px a table row's icon is. */
.photo-toolbar-actions .icon-action {
  width: 40px;
  height: 40px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text);
}

.photo-toolbar-actions .icon-action.danger {
  color: var(--bad);
}

.photo-toolbar-actions .icon-action:hover:not(:disabled) {
  background: var(--sidebar-hover);
}

/* The actions of a page that are behind the More button on a phone: rows of a
   menu, each an icon and its name, the ones that remove something in red. */
.photo-toolbar-menu .photo-icon-actions {
  display: contents;
}

.photo-toolbar-menu button.is-dangerous {
  color: var(--bad);
}

.photo-filter-button.active {
  border-color: var(--border-strong);
  background: var(--field);
}

/* What the Filter button opens: the kinds one under the other, then the two
   dates, then the way to clear them. */
.photo-filter-form {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding: var(--space-2);
}

.photo-filter-form label {
  margin-bottom: 0;
}

/* Clear filters is a button with a box, as wide as its words, among the
   form's fields rather than a row of the menu it sits in. */
.menu .photo-filter-clear {
  align-self: flex-start;
  width: auto;
  padding: 7px 14px;
  border: 1px solid var(--border);
  border-radius: var(--radius-2);
  background: var(--surface);
  font-weight: 550;
}

.photo-filter-kinds {
  margin: 0;
  padding: 0;
  border: none;
}

.photo-filter-kinds legend {
  padding: 0;
  margin-bottom: 3px;
  color: var(--muted);
  font-size: 13px;
}

.photo-filter-kinds label.checkbox {
  margin-top: 0;
  padding: 6px 0;
}

/* The same strip of tabs on every page. */
.photo-sections .tabs {
  margin-bottom: var(--space-4);
}

/* The bar that takes the toolbar's place while photos are selected stays at
   the top of the page while the photos scroll under it, with a ground of its
   own, so that what can be done to them is in reach however far down the
   selecting went. */
.photo-selection-bar {
  position: sticky;
  /* Flush with the top of the scrolling page, not with the padding inside it:
     the page's padding is above the bar and photos would show through it. */
  top: calc(-1 * var(--space-6));
  z-index: 6;
  /* Close, the count and five buttons at most (Remove from album is the
     fifth) have to fit a phone's width. */
  column-gap: var(--space-1);
  padding: 2px var(--space-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-2);
  background: var(--surface);
  box-shadow: var(--shadow-raised);
  flex-wrap: nowrap;
  justify-content: flex-start;
}

.photo-selection-bar .icon-button {
  flex: 0 0 auto;
}

/* The page's padding is smaller where the rail is a drawer. */
@media (max-width: 760px) {
  .photo-selection-bar {
    top: calc(-1 * var(--space-4));
  }
}

/* One line, even with a button more in the bar (Remove from album): the count
   gives way with an ellipsis rather than wrapping into two lines. */
.photo-selection-bar strong {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.photo-trash-note {
  margin: 0 0 var(--space-3);
}

/* Rows left over from the list the person has just left, until this list's
   arrive: dimmed, and out of reach of the buttons of the new list. */
.photo-list {
  transition: opacity 120ms ease;
}

.photo-list.is-loading {
  opacity: 0.45;
  pointer-events: none;
}

.photo-day-heading {
  margin: var(--space-6) 0 var(--space-2);
  font-size: var(--text-body);
  font-weight: 600;
}

.photo-day:first-child .photo-day-heading {
  margin-top: 0;
}

/* A hairline between square cells, edge to edge of the content, as many across
   as the width has room for: more on a wide window, five on a phone. */
.photo-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(118px, 1fr));
  gap: 1px;
}

.photo-cell {
  position: relative;
  aspect-ratio: 1;
  overflow: hidden;
  background: var(--field);
  scroll-margin-top: var(--space-4);
}

.photo-open {
  display: block;
  position: relative;
  width: 100%;
  height: 100%;
  padding: 0;
  border: none;
  border-radius: 0;
  background: transparent;
  color: inherit;
  overflow: hidden;
}

.photo-open:hover:not(:disabled) {
  background: transparent;
}

.photo-open:focus-visible {
  outline: 2px solid var(--text);
  outline-offset: -2px;
}

.photo-image {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  background: var(--field);
  transition:
    transform 120ms ease,
    opacity 120ms ease;
}

.photo-cell.is-selected .photo-image {
  transform: scale(0.88);
  border-radius: 4px;
}

.photo-cell.is-selected {
  background: color-mix(in srgb, var(--accent) 22%, var(--field));
}

.photo-state {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--muted);
}

.photo-state-failed {
  color: var(--bad);
}

.photo-spinner {
  width: 22px;
  height: 22px;
  border: 2px solid var(--border-strong);
  border-top-color: var(--muted);
  border-radius: 50%;
  animation: photo-spin 900ms linear infinite;
}

@keyframes photo-spin {
  to {
    transform: rotate(360deg);
  }
}

@media (prefers-reduced-motion: reduce) {
  .photo-spinner {
    animation-duration: 3s;
  }
}

.photo-mark {
  position: absolute;
  display: flex;
  align-items: center;
  color: #fff;
  font-size: 11px;
  font-weight: 600;
  line-height: 1;
  pointer-events: none;
  filter: drop-shadow(0 1px 2px rgb(0 0 0 / 55%));
}

.photo-mark-length {
  right: 5px;
  bottom: 5px;
  padding: 3px 6px;
  border-radius: var(--radius-round);
  background: rgb(0 0 0 / 55%);
  filter: none;
}

.photo-mark-favorite {
  left: 6px;
  bottom: 6px;
}

.photo-mark-favorite svg {
  fill: currentColor;
}

/* The mark Live Photos wear: a ring with a dot in it. */
.photo-mark-live {
  top: 6px;
  right: 6px;
  width: 16px;
  height: 16px;
  justify-content: center;
  border: 2px solid #fff;
  border-radius: 50%;
}

.photo-mark-live::before {
  content: '';
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: #fff;
}

/* The tick that selects. It is a real checkbox, kept for the keyboard and
   for assistive technology, drawn as a round mark over the corner. */
.photo-check {
  position: absolute;
  top: 6px;
  left: 6px;
  display: block;
  width: 22px;
  height: 22px;
  margin: 0;
  opacity: 0;
  cursor: pointer;
}

.photo-check input {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  opacity: 0;
  cursor: pointer;
}

.photo-check-box {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 2px solid #fff;
  border-radius: 50%;
  background: rgb(0 0 0 / 30%);
  color: #fff;
  pointer-events: none;
  box-shadow: 0 1px 3px rgb(0 0 0 / 40%);
}

.photo-cell.is-selected .photo-check-box {
  border-color: var(--accent);
  background: var(--accent);
  color: var(--accent-text);
}

.is-selecting .photo-check,
.photo-check:focus-within {
  opacity: 1;
}

@media (hover: hover) {
  .photo-cell:hover .photo-check {
    opacity: 1;
  }
}

/* A touch screen has no hover to reveal the tick, and ticks on every cell all
   the time crowd the grid. They appear in selection mode (the Select button),
   and until then the invisible tick must not catch a tap meant to open. */
@media (hover: none) {
  .photo-timeline:not(.is-selecting) .photo-check {
    visibility: hidden;
  }
}

/* --- the viewer ---
   A photo on black and nothing over it but what is needed: the day and time
   at the top between a way back and the few controls that belong to the
   picture, a strip of the photos either side along the foot, and under it the
   actions in three groups (save a copy at the left, the ones used most in a
   capsule in the middle, Move to Trash at the right). Always dark, in a light
   dashboard too: a photograph is judged against black. */
@property --photo-viewer-fade {
  syntax: '<number>';
  inherits: true;
  initial-value: 1;
}

@property --photo-chrome-fade {
  syntax: '<number>';
  inherits: true;
  initial-value: 1;
}

.photo-viewer {
  --background: #000000;
  --surface: #1c1c1e;
  --border: #2c2c2e;
  --border-strong: #48484a;
  --field: #1c1c1e;
  --sidebar-hover: #2c2c2e;
  --text: #f5f5f7;
  --muted: #a1a1a6;
  --accent: #f5f5f7;
  --accent-text: #18181b;
  --accent-hover: #ffffff;
  --bad: #ff6b6b;
  --good: #4ade80;
  --photo-glass: rgb(255 255 255 / 14%);
  --photo-glass-hover: rgb(255 255 255 / 24%);

  position: fixed;
  inset: 0;
  z-index: 40;
  display: flex;
  background: rgb(0 0 0 / var(--photo-viewer-fade, 1));
  color: var(--text);
  color-scheme: dark;
  /* The fade follows a finger while it drags, and settles when it lets go. */
  transition:
    --photo-viewer-fade 180ms ease-out,
    --photo-chrome-fade 180ms ease-out;
}

.photo-viewer.is-dragging {
  transition: none;
}

.photo-viewer-main {
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
  min-width: 0;
}

.photo-viewer-bar,
.photo-viewer-strip,
.photo-viewer-actions,
.photo-viewer-details {
  opacity: var(--photo-chrome-fade, 1);
}

/* The way back, the day and time, and what belongs to the picture itself. The
   title is in the middle of the window whatever is at either end. */
.photo-viewer-bar {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
  padding-top: calc(var(--space-2) + env(safe-area-inset-top));
}

.photo-viewer-bar-start,
.photo-viewer-bar-end,
.photo-viewer-actions-start,
.photo-viewer-actions-end {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

.photo-viewer-bar-end,
.photo-viewer-actions-end {
  justify-content: flex-end;
}

.photo-viewer-title {
  margin: 0;
  padding: 5px 20px;
  border-radius: var(--radius-round);
  background: var(--photo-glass);
  font-size: var(--text-body);
  font-weight: 600;
  line-height: 1.25;
  text-align: center;
}

.photo-viewer-title time {
  display: block;
}

.photo-viewer-date,
.photo-viewer-time {
  display: block;
  white-space: nowrap;
}

.photo-viewer-time {
  color: var(--muted);
  font-size: var(--text-label);
  font-weight: 450;
}

/* The round glass button every control of the viewer is, 44px to hit. */
.photo-viewer .photo-round-button {
  width: 44px;
  height: 44px;
  border-radius: 50%;
  background: var(--photo-glass);
  color: var(--text);
  text-decoration: none;
}

.photo-viewer .photo-round-button:hover:not(:disabled) {
  background: var(--photo-glass-hover);
  color: var(--text);
}

.photo-viewer .photo-round-button:disabled {
  opacity: 0.45;
}

.photo-viewer .photo-round-button.is-on {
  background: var(--accent);
  color: var(--accent-text);
}

.photo-viewer .photo-round-button.danger {
  color: var(--bad);
}

.photo-viewer .photo-favorite.is-on {
  background: transparent;
  color: var(--bad);
}

.photo-viewer .photo-favorite.is-on:hover:not(:disabled) {
  background: var(--photo-glass-hover);
  color: var(--bad);
}

.photo-viewer .photo-favorite.is-on svg {
  fill: currentColor;
}

.photo-live-button {
  padding: 4px 12px;
  border-radius: var(--radius-round);
  background: var(--photo-glass);
  color: var(--text);
  font-size: 12px;
  font-weight: 650;
  touch-action: none;
  user-select: none;
}

.photo-live-button.is-on {
  background: var(--accent);
  color: var(--accent-text);
}

.photo-viewer-stage {
  position: relative;
  display: flex;
  flex: 1 1 auto;
  align-items: center;
  justify-content: center;
  min-height: 0;
}

/* A finger on a picture is a drag to close, a swipe to the next photo, a pinch
   or a drag of a zoomed picture: the browser does not take it for a scroll or
   a zoom of the page. */
.photo-viewer-stage.is-zoomable {
  touch-action: none;
  user-select: none;
  overflow: hidden;
}

.photo-viewer-stage.is-zoomed .photo-viewer-picture {
  cursor: grab;
}

.photo-viewer-stage.is-zoomed.is-panning .photo-viewer-picture {
  cursor: grabbing;
}

.photo-viewer-picture {
  position: relative;
  width: 100%;
  height: 100%;
  transform-origin: center;
}

/* A zoom the button or a double click asked for glides; one that follows the
   wheel or the fingers must not lag behind them. */
.photo-viewer-picture.is-easing {
  transition: transform 160ms ease-out;
}

@media (prefers-reduced-motion: reduce) {
  .photo-viewer-picture.is-easing {
    transition: none;
  }
}

/* The photo's own file, over the preview it replaces once it has arrived. */
.photo-original {
  position: absolute;
  inset: 0;
  opacity: 0;
}

.photo-original.is-ready {
  opacity: 1;
}

.photo-fitted,
.photo-live-video,
.photo-viewer-video {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
}

.photo-live-video {
  position: absolute;
  inset: 0;
  background: var(--background);
}

.photo-viewer-video {
  background: #000;
}

.photo-viewer-message {
  color: var(--muted);
}

/* The reason a photo has no picture, and what can be done about it, one
   under the other and centered on the stage. */
.photo-viewer-message-block {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-3);
  max-width: 420px;
  padding: 0 var(--space-4);
  text-align: center;
}

.photo-viewer-message-block .photo-viewer-message {
  margin: 0;
}

/* Previous and next over the photo, for a mouse: they are there while the
   pointer is on the picture or the keyboard is on them. A touch screen turns
   the page with a swipe and the strip, and has no use for them. */
.photo-viewer-step {
  position: absolute;
  top: 50%;
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  padding: 0;
  transform: translateY(-50%);
  border: none;
  border-radius: 50%;
  background: rgb(40 40 44 / 80%);
  color: var(--text);
}

.photo-viewer-previous {
  left: var(--space-3);
}

.photo-viewer-next {
  right: var(--space-3);
}

@media (hover: hover) {
  .photo-viewer-step {
    opacity: 0;
    transition: opacity 120ms ease;
  }

  .photo-viewer-stage:hover .photo-viewer-step,
  .photo-viewer-step:focus-visible {
    opacity: 1;
  }
}

@media (hover: none) {
  .photo-viewer-step {
    display: none;
  }
}

/* The photos either side, small and square, the one shown larger. It scrolls
   sideways with no bar, and the photo shown is kept in the middle of it. */
.photo-viewer-strip {
  display: flex;
  flex: 0 0 auto;
  align-items: center;
  gap: 2px;
  height: 80px;
  padding: 0 var(--space-3);
  overflow-x: auto;
  overflow-y: hidden;
  scrollbar-width: none;
}

.photo-viewer-strip::-webkit-scrollbar {
  display: none;
}

.photo-strip-item {
  flex: 0 0 auto;
  width: 44px;
  height: 56px;
  padding: 0;
  border: none;
  border-radius: 0;
  overflow: hidden;
  background: var(--surface);
  opacity: 0.8;
  transition:
    width 150ms ease,
    opacity 150ms ease;
}

.photo-strip-item:hover:not(:disabled) {
  background: var(--surface);
  opacity: 1;
}

.photo-strip-item img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.photo-strip-item.is-current {
  width: 64px;
  margin: 0 var(--space-1);
  border-radius: 3px;
  opacity: 1;
}

.photo-strip-item:focus-visible {
  outline: 2px solid var(--text);
  outline-offset: 1px;
}

/* The actions in three places: save a copy, the capsule of what is done most,
   and Move to Trash. The capsule is in the middle of the window whatever is at
   either end. */
.photo-viewer-actions {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-1) var(--space-3);
  padding-bottom: calc(var(--space-3) + env(safe-area-inset-bottom));
}

.photo-action-group {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  padding: 3px;
  border-radius: var(--radius-round);
  background: var(--photo-glass);
}

.photo-viewer .photo-action-group .photo-round-button {
  background: transparent;
}

.photo-viewer .photo-action-group .photo-round-button:hover:not(:disabled) {
  background: var(--photo-glass-hover);
}

.photo-viewer .photo-action-group .photo-round-button.is-on {
  background: var(--accent);
}

/* Save to my library, for a photo that is somebody else's: the words stay on a
   wide screen, and are an icon on a phone. */
.photo-viewer .photo-action-group .photo-save-button {
  min-height: 44px;
  padding: 0 var(--space-4);
  border-color: transparent;
  background: transparent;
  color: var(--text);
}

.photo-viewer .photo-action-group .photo-save-button:hover:not(:disabled) {
  background: var(--photo-glass-hover);
}

.photo-viewer-details {
  flex: 0 0 320px;
  overflow-y: auto;
  padding: var(--space-4);
  border-left: 1px solid var(--border);
  background: var(--surface);
}

.photo-viewer-details h2 {
  margin: 0 0 var(--space-3);
  font-size: var(--text-title);
}

.photo-viewer-details dl {
  margin: 0;
}

.photo-viewer-details dl > div + div {
  margin-top: var(--space-3);
}

.photo-viewer-details dt {
  color: var(--muted);
  font-size: var(--text-label);
}

.photo-viewer-details dd {
  margin: 2px 0 0;
  overflow-wrap: anywhere;
}

@media (max-width: 760px) {
  /* Under the picture rather than beside it, and not over it: the viewer
     becomes a column, the picture is fitted to the room the details leave, and
     the details scroll in a box of their own below it. A sheet laid over the
     picture hid half of it. */
  .photo-viewer {
    flex-direction: column;
  }

  .photo-viewer-main {
    flex: 1 1 0;
    min-height: 0;
  }

  .photo-viewer-details {
    flex: 0 1 auto;
    max-height: 45%;
    padding-bottom: calc(var(--space-4) + env(safe-area-inset-bottom));
    border-top: 1px solid var(--border);
    border-left: none;
    overscroll-behavior: contain;
  }

  /* The strip does not fit with the details open: the picture would be left
     a sliver. It comes back when they are closed. */
  .photo-viewer.has-details .photo-viewer-strip {
    display: none;
  }

  .photo-viewer.has-details .photo-viewer-actions {
    padding-bottom: var(--space-2);
  }
}

/* The details could not be read: the sentence and the way to try again. */
.photo-viewer-failure {
  margin-bottom: var(--space-3);
}

.photo-viewer-failure p {
  margin: 0 0 var(--space-2);
}

/* --- the drop target --- */
.photo-drop {
  position: fixed;
  inset: var(--space-4);
  z-index: 48;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  border: 2px dashed var(--border-strong);
  border-radius: var(--radius-3);
  background: color-mix(in srgb, var(--background) 88%, transparent);
  color: var(--text);
  pointer-events: none;
}

/* --- the upload tray --- */
/* Above the agent's button (44px high, 20px from the corner), as the recording
   bar is, so that neither covers the other; the toasts are at the other end of
   the window. */
.photo-tray {
  position: fixed;
  right: 20px;
  bottom: 76px;
  z-index: 45;
  width: 340px;
  max-width: calc(100vw - 2 * var(--space-4));
  border: 1px solid var(--border);
  border-radius: var(--radius-3);
  background: var(--surface);
  box-shadow: var(--shadow-floating);
}

.photo-tray-head {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  padding: var(--space-2) var(--space-2) var(--space-2) var(--space-3);
}

.photo-tray-list {
  max-height: 40vh;
  margin: 0;
  padding: 0;
  overflow-y: auto;
  border-top: 1px solid var(--border);
  list-style: none;
}

.photo-tray-item {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto auto;
  gap: 2px var(--space-2);
  align-items: center;
  padding: 6px var(--space-3);
  font-size: var(--text-label);
}

.photo-tray-item + .photo-tray-item {
  border-top: 1px solid var(--border);
}

/* The line that counts the files the tray does not list. */
.photo-tray-summary {
  display: block;
}

.photo-tray-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.photo-tray-state {
  display: flex;
  align-items: center;
  gap: 4px;
  max-width: 14em;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.photo-tray-item.is-failed .photo-tray-state {
  color: var(--bad);
}

.photo-tray-item .icon-action {
  grid-column: 3;
  grid-row: 1;
  margin: -4px -6px -4px 0;
}

.photo-tray-bar {
  grid-column: 1 / -1;
  grid-row: 2;
  width: 100%;
  height: 4px;
  appearance: none;
  border: none;
  border-radius: var(--radius-round);
  background: var(--border);
  overflow: hidden;
}

.photo-tray-bar::-webkit-progress-bar {
  background: var(--border);
}

.photo-tray-bar::-webkit-progress-value {
  background: var(--accent);
  transition: width 200ms ease;
}

.photo-tray-bar::-moz-progress-bar {
  background: var(--accent);
}

.photo-tray-bar:indeterminate {
  background-color: var(--border);
  background-image: linear-gradient(var(--muted), var(--muted));
  background-repeat: no-repeat;
  background-size: 40% 100%;
  animation: photo-tray-wait 1.4s ease-in-out infinite;
}

@keyframes photo-tray-wait {
  from {
    background-position: -50% 0;
  }
  to {
    background-position: 150% 0;
  }
}

@media (prefers-reduced-motion: reduce) {
  .photo-tray-bar:indeterminate {
    animation: none;
  }
}

.photo-tray-bar.is-reading::-webkit-progress-value {
  background: var(--muted);
}

.photo-tray-bar.is-reading::-moz-progress-bar {
  background: var(--muted);
}

@media (max-width: 600px) {
  .photo-grid {
    grid-template-columns: repeat(5, minmax(0, 1fr));
  }

  /* The width of the screen, standing above the agent's button; the toasts
     rise above it (the tray says how tall it is in --photo-tray-height). */
  .photo-tray {
    right: var(--space-2);
    bottom: calc(84px + env(safe-area-inset-bottom));
    left: var(--space-2);
    width: auto;
    max-width: none;
  }
}

/* --- albums ---------------------------------------------------------------
   The grid of covers, the suggestions above it, and the album menu's rows. */
.photo-album-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(168px, 1fr));
  gap: var(--space-4) var(--space-3);
}

.photo-album-cell {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
  color: var(--text);
  text-decoration: none;
}

.photo-album-cover {
  display: block;
  aspect-ratio: 1;
  margin-bottom: var(--space-1);
  overflow: hidden;
  border-radius: var(--radius-2);
  background: var(--field);
}

.photo-album-cell:hover .photo-album-cover {
  filter: brightness(0.94);
}

.photo-album-cell:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  border-radius: var(--radius-2);
}

.photo-album-cover img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.photo-album-cover-empty {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100%;
  color: var(--muted);
}

.photo-album-title {
  overflow: hidden;
  font-weight: 600;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.photo-album-details {
  overflow: hidden;
  font-size: var(--text-label);
  text-overflow: ellipsis;
  white-space: nowrap;
}

.photo-suggestions {
  margin-bottom: var(--space-6);
}

.photo-section-heading {
  margin: 0 0 var(--space-1);
  font-size: var(--text-title);
}

.photo-section-hint {
  margin: 0 0 var(--space-3);
}

.photo-suggestion-list {
  display: flex;
  flex-direction: column;
  margin: 0;
  padding: 0;
  list-style: none;
  border: 1px solid var(--border);
  border-radius: var(--radius-2);
  background: var(--surface);
}

.photo-suggestion {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  padding: var(--space-3);
}

.photo-suggestion + .photo-suggestion {
  border-top: 1px solid var(--border);
}

.photo-suggestion-samples {
  display: flex;
  flex: 0 1 auto;
  gap: 4px;
  min-width: 0;
  overflow: hidden;
}

.photo-suggestion-samples img,
.photo-suggestion-blank {
  display: block;
  flex: 0 0 auto;
  width: 56px;
  height: 56px;
  border-radius: 4px;
  object-fit: cover;
  background: var(--field);
}

.photo-suggestion-body {
  display: flex;
  flex: 1 1 0;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.photo-suggestion-body > span {
  overflow-wrap: anywhere;
}

.photo-suggestion > button {
  flex: 0 0 auto;
}

.photo-album-choice {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.photo-album-choice-title {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.photo-album-choice .muted {
  font-size: var(--text-label);
}

.photo-viewer-albums {
  margin: 0;
  padding: 0;
  list-style: none;
}

.photo-viewer-albums li + li {
  margin-top: 2px;
}

@media (max-width: 600px) {
  .photo-album-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .photo-suggestion {
    flex-wrap: wrap;
    gap: var(--space-2) var(--space-3);
  }

  .photo-suggestion-samples {
    flex: 1 0 100%;
  }

  .photo-suggestion-body {
    flex: 1 1 60%;
  }
}

/* The picture row at the head of the profile form: the picture, and what can be
   done to it. */
.profile-picture {
  display: flex;
  align-items: center;
  gap: var(--space-4);
}

.profile-picture-body {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  min-width: 0;
}

.profile-picture-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}

/* The number of invitations waiting, at the end of the account menu's
   Circles row. The accent says "look here" without a second color for it. */
.menu-badge {
  flex: 0 0 auto;
  margin-left: auto;
  min-width: 20px;
  padding: 1px 6px;
  border-radius: 10px;
  background: var(--accent);
  color: var(--accent-text);
  font-size: 12px;
  font-weight: 600;
  line-height: 18px;
  text-align: center;
  font-variant-numeric: tabular-nums;
}

/* The circles a person is in, one card each. The card is a link, so the whole
   of it is the target; its members are a few pictures overlapping and a count. */
.circle-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  gap: var(--space-3);
  margin-bottom: var(--space-4);
}

.circle-card {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  margin-bottom: 0;
  color: inherit;
  text-decoration: none;
  min-width: 0;
}

.circle-card:hover {
  border-color: var(--accent);
}

.circle-card-title {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  min-width: 0;
}

.circle-card-title strong {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.circle-card-members {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

/* Overlapping pictures: a ring in the card's own color separates one from the
   next. */
.avatar-stack {
  display: flex;
}

.avatar-stack .avatar {
  box-shadow: 0 0 0 2px var(--surface);
}

.avatar-stack .avatar + .avatar {
  margin-left: -8px;
}

@media (max-width: 600px) {
  .circle-grid {
    grid-template-columns: 1fr;
  }
}


/* Chats in the drawer: a conversation with people in it. The person's own
   messages are the bubbles they always were, on the right; everybody
   else's are on the left with a picture, and a name above the first of a run. */

/* The number on the drawer's button: the chats with something new in them.
   The accent, as the account menu's count of invitations. */
.agent-drawer-badge {
  position: absolute;
  top: -6px;
  right: -6px;
  min-width: 20px;
  padding: 1px 6px;
  border-radius: 10px;
  background: var(--accent);
  color: var(--accent-text);
  font-size: 12px;
  font-weight: 600;
  line-height: 18px;
  text-align: center;
  font-variant-numeric: tabular-nums;
  box-shadow: 0 0 0 2px var(--background);
}

/* In the picker a chat is its pictures, its title and, when something is
   waiting, a bold title and a count at the end of the line. */
.agent-drawer-list-name.chat {
  display: flex;
  align-items: center;
  gap: 6px;
}

.agent-drawer-list-name.chat .chat-title {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}

.agent-drawer-list-row.unread .chat-title {
  font-weight: 700;
}

.chat-muted {
  display: inline-flex;
  flex: none;
  color: var(--muted);
}

.chat-unread {
  flex: none;
  margin-left: auto;
  min-width: 18px;
  padding: 0 5px;
  border-radius: 9px;
  background: var(--accent);
  color: var(--accent-text);
  font-size: 11px;
  font-weight: 600;
  line-height: 18px;
  text-align: center;
  font-variant-numeric: tabular-nums;
}

.chat-avatars {
  flex: none;
}

.chat-avatars .avatar {
  box-shadow: 0 0 0 2px var(--background);
}

/* Four pixels, of which the ring takes two: at 20 pixels, eight covered the
   first picture's letter entirely. */
.chat-avatars .avatar + .avatar {
  margin-left: -4px;
}

/* The offer where the main conversation would be, with the agent off. */
.agent-drawer-offer {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-3);
  margin: auto 0;
}

.head-menu-action {
  width: 100%;
  border: 0;
  background: none;
  font: inherit;
  color: inherit;
  text-align: left;
  cursor: pointer;
  border-radius: 6px;
}

.head-menu-action:hover,
.head-menu-action:focus-visible {
  background: var(--sidebar-hover);
}

.head-menu-action.danger,
.head-menu-action.danger svg {
  color: var(--bad);
}

/* One message of a chat from somebody else: their picture at the left of the
   first of a run, and the bubble beside it. The ones after it in the run
   sit closer under it and keep the picture's room empty. */
.agent-chat-turn {
  display: flex;
  max-width: 100%;
  gap: 8px;
}

.agent-chat-turn:not(.mine):not(.starts-run) {
  margin-top: -5px;
}

.agent-chat-turn.mine {
  align-self: flex-end;
  flex-direction: column;
  align-items: flex-end;
  max-width: 85%;
  gap: 2px;
}

.agent-chat-turn.mine .agent-line.user {
  max-width: 100%;
}

.agent-chat-avatar {
  flex: none;
  width: 28px;
}

.agent-chat-body {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 2px;
  min-width: 0;
}

.agent-chat-author {
  display: flex;
  align-items: center;
  gap: 4px;
  padding-left: 2px;
  color: var(--muted);
  font-size: 12px;
  font-weight: 600;
}

/* A person's words are shown as they were typed, line breaks and all. Not an
   agent's: its answer is Markdown, drawn as elements, and the line breaks
   between its list items would show as empty lines. */
.agent-line.assistant.chat-person:not(.chat-agent) {
  white-space: pre-wrap;
}

.agent-line.pending {
  opacity: 0.6;
}

.agent-line.chat-deleted {
  font-style: italic;
  background: none;
  border: 1px dashed var(--border);
}

.agent-chat-edited {
  margin-left: 6px;
  font-size: 11px;
  opacity: 0.7;
}

/* The bubble is the accent, so a primary button in it is the bubble's own
   color and has no shape. Turned over: the page's color with the accent's
   letters. */
.agent-line.user .agent-chat-edit button.primary {
  border-color: var(--background);
  background: var(--background);
  color: var(--accent);
}

.agent-line.user .agent-chat-edit button.primary:hover:not(:disabled) {
  border-color: var(--background);
  background: var(--background);
  opacity: 0.85;
}

.agent-chat-edit {
  display: flex;
  flex-direction: column;
  gap: 6px;
  min-width: 200px;
  color: var(--text);
}

/* Under the newest message each of the others has read: their pictures,
   small. */
/* What the person's own agent did, one folded row above its answer: under
   the avatar's column, where the answer's words start. */
.agent-line.tool.work {
  align-self: flex-start;
  max-width: 100%;
  margin-left: 36px;
}

.agent-line.tool.work > .agent-tool-toggle {
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.agent-chat-reads {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  font-size: 11px;
}

.agent-chat-typing {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 12px;
}

/* The agents an @ offers, above the box they are chosen for. */
.agent-drawer-input {
  position: relative;
}

.agent-mention-list {
  position: absolute;
  right: 10px;
  bottom: 100%;
  left: 10px;
  z-index: 6;
  display: flex;
  flex-direction: column;
  max-height: 200px;
  margin: 0 0 4px;
  padding: 4px;
  overflow-y: auto;
  list-style: none;
  border: 1px solid var(--border);
  border-radius: var(--radius-2);
  background: var(--surface);
  box-shadow: var(--shadow-floating);
}

.agent-mention-list li {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-height: 36px;
  padding: 4px 8px;
  border-radius: var(--radius-1);
  cursor: pointer;
}

.agent-mention-list li.active {
  background: var(--sidebar-hover);
}

.agent-mention-name {
  font-weight: 600;
}

/* A mention in a chat's message: the @ and the name in the brand green, on a
   wash of it when it is the reader who is mentioned, so their own name
   stands out of the rest. The text keeps its weight, and reads in either
   theme. */
.chat-mention {
  padding: 0;
  background: none;
  color: var(--leaf);
  font-weight: 600;
}

.chat-mention-reader {
  padding: 0 2px;
  border-radius: 3px;
  background: color-mix(in srgb, var(--leaf) 22%, transparent);
  color: inherit;
}

/* The level the chat is set to, ticked at the end of its row. */
.head-menu-row-check {
  margin-left: auto;
}

/* What a phone is told about: a group of ticks under the name, with a bold
   heading as the chat's switches have, not the browser's own box with its
   legend over the border. */
.phone-kinds {
  margin: var(--space-4) 0 0;
  padding: 0;
  border: 0;
}

.phone-kinds legend {
  padding: 0;
  font-weight: 600;
}

.phone-kinds .field-hint {
  margin: var(--space-1) 0 0;
}

.phone-kinds label.checkbox {
  margin: var(--space-3) 0 0;
}

/* What a member allows their agent in the chat: two switches, each with the
   sentence that says what it means. */
.chat-agent-switches {
  margin: var(--space-4) 0 0;
  padding: var(--space-3) 0 0;
  border: 0;
  border-top: 1px solid var(--border);
}

/* The legend floats so it sits under the line rather than on it, where a
   fieldset's border would otherwise run into its words. */
.chat-agent-switches legend {
  float: left;
  width: 100%;
  margin-bottom: var(--space-3);
  padding: 0;
  font-weight: 600;
}

.chat-agent-switches legend + * {
  clear: both;
}

.chat-agent-switches label.checkbox {
  margin-top: var(--space-4);
  color: var(--text);
  font-size: var(--text-body);
}

.chat-agent-switches legend + label.checkbox {
  margin-top: 0;
}

/* Each sentence sits under its own switch and a gap away from the next. */
.chat-agent-switches input[type='checkbox'] {
  flex: 0 0 auto;
  width: 16px;
  height: 16px;
}

.chat-agent-switches p {
  margin: var(--space-1) 0 0 24px;
  max-width: var(--measure);
  font-size: 13px;
}

.chat-agent-switches p.chat-agent-off {
  margin-left: 0;
  font-size: var(--text-body);
}

.chat-agent-switches p.chat-agent-off + p.chat-agent-off {
  margin-top: var(--space-2);
}

/* The people to choose from, in the dialogs for a new group, a message to
   someone and adding to a group. Taller than the three rows it once was; on
   a phone, where the dialog is the screen, the list takes the room between
   the group's name and the buttons. */
.chat-people {
  display: flex;
  flex-direction: column;
  max-height: min(56dvh, 440px);
  overflow-y: auto;
}

@media (max-width: 640px) {
  .dialog:has(> .chat-people) {
    display: flex;
    flex-direction: column;
  }

  .dialog:has(> .chat-people) > .chat-people {
    flex: 1 1 auto;
    max-height: none;
  }
}

/* One person: a box, a picture, the name and the username. The class is not
   the message bubble's (.chat-person), which shares the word and not the
   rules. The dialog styles every label as a field, and every span in a label
   as its help text, so both are undone here the way a row of any other
   checkbox list does it. */
.chat-people label.chat-person-row {
  display: flex;
  align-items: center;
  gap: 10px;
  flex: 0 0 auto;
  min-height: 40px;
  margin: 0;
  padding: 5px 0;
  font-size: var(--text-body);
  color: var(--text);
  cursor: pointer;
}

.chat-person-row input {
  flex: 0 0 auto;
  min-height: 0;
  margin: 0;
}

/* The name gives way to the username, which stays whole: a name cut with an
   ellipsis is still a name, and a username cut in groups of letters is not
   one. */
.chat-people .chat-person-row .chat-person-name {
  display: block;
  flex: 1 1 auto;
  min-width: 0;
  margin: 0;
  overflow: hidden;
  color: var(--text);
  font-size: inherit;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.chat-people .chat-person-row .chat-person-username {
  display: block;
  flex: 0 0 auto;
  margin: 0;
  font-size: 13px;
  overflow-wrap: normal;
  white-space: nowrap;
}

.chat-members {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin: 0;
  padding: 0;
  list-style: none;
}

.chat-members li {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

.chat-member-text {
  display: flex;
  flex: 1;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-1) var(--space-2);
  min-width: 0;
}

/* A tag longer than the row (some languages say a lot) wraps inside it
   rather than running over the button beside it. */
.chat-member-text .tag {
  max-width: 100%;
  white-space: normal;
}

.chat-member-name {
  min-width: 0;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* On the page of conversations a chat's tile leads with its pictures. */
.conversation-tile-title .chat-avatars {
  margin-right: 6px;
  vertical-align: middle;
}

.tile-detail .chat-unread {
  display: inline-block;
  margin-left: 8px;
}

/* The second way into a circle, for an operator: under the invitation form,
   set apart by a line, the button and the one sentence that says what it
   does. */
.circle-account {
  margin-top: var(--space-5);
  padding-top: var(--space-5);
  border-top: 1px solid var(--border);
}

.circle-account p {
  margin: var(--space-2) 0 0;
  max-width: var(--measure);
}

/* A call of a skill the person shared, waiting for their word. The card the
   drawer and the agent page both draw (components/skillShare.tsx). */
.skill-share-call-card {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 12px 14px;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--surface);
  max-width: 100%;
}
.skill-share-call-card-head {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}
.skill-share-call-card-title {
  flex: 1 1 12rem;
  min-width: 0;
  overflow-wrap: anywhere;
}
.skill-share-call-card-facts {
  display: grid;
  grid-template-columns: max-content minmax(0, 1fr);
  gap: 4px 12px;
  margin: 0;
}
.skill-share-call-card-facts dt {
  color: var(--muted);
  text-transform: capitalize;
}
.skill-share-call-card-facts dd {
  margin: 0;
  overflow-wrap: anywhere;
}
.skill-share-call-card-line {
  display: contents;
}
/* An argument is shown whole, in the lines it was written in. */
.skill-share-call-card-line dd {
  white-space: pre-wrap;
}
.skill-share-call-card-amount {
  font-weight: 600;
  font-size: 1.1em;
}
.skill-share-call-card-reason {
  margin: 0;
  padding: 6px 10px;
  border-left: 3px solid var(--border);
}
.skill-share-call-card-reason blockquote {
  margin: 2px 0 0;
  overflow-wrap: anywhere;
  white-space: pre-wrap;
}
.skill-share-call-card-note {
  width: 100%;
}
.skill-share-call-card-actions {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
}
.skill-share-call-card p {
  margin: 0;
}
.skill-share-calls-heading {
  margin: var(--space-2) 0 0;
  font-size: var(--text-label);
  font-weight: 600;
  color: var(--muted);
}
.skill-share-calls {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 4px 0 8px 36px;
}
.skill-share-tools {
  border: 0;
  padding: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.skill-share-tools legend {
  padding: 0;
  margin-bottom: 4px;
}

/* A choice of the form that shares a skill: the name in the text's own weight
   and color, the words about it a muted line under it. The dialog's rule for a
   span in a label (small, muted, a block) made the name look as disabled as its
   hint, and the hint went after a dot. Written with the dialog in it to beat
   that rule and the one for a label with a tick box. */
.dialog label.skill-share-choice {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
  margin: 0;
}

.dialog label.skill-share-choice > span {
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin: 0;
  color: var(--text);
  font-size: var(--text-body);
}

.dialog label.skill-share-choice > input {
  margin-top: 3px;
}

.dialog label.skill-share-choice > span > .skill-share-choice-name {
  margin: 0;
  color: var(--text);
  font-size: var(--text-body);
}

.dialog label.skill-share-choice > span > .skill-share-choice-hint {
  margin: 0;
  color: var(--muted);
  font-size: var(--text-label);
}

/* What is wrong with a field, said under it and above the next label, as
   close to the field as to the label that follows. */
.dialog label + span.error {
  display: block;
  margin: calc(var(--space-2) * -1) 0 var(--space-3);
}

/* The one after the tools stands apart from them as a field does. */
.dialog .skill-share-tools + label.skill-share-choice {
  margin: var(--space-3) 0;
}
@media (max-width: 640px) {
  .skill-share-calls {
    padding-left: 0;
  }
}

/* --- onboarding: Photos on a new server -------------------------------------
   The welcome page's second way in, for somebody who came for their photos
   and not for an assistant; the card that says how to back an iPhone up; and
   the warning edge a notice takes when it is something to act on. */
.notice.warn {
  border-left-color: var(--warn);
}

.welcome-other {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin-top: var(--space-5);
  text-align: center;
}

.welcome-other > p {
  /* With the column's gap, the space it always had before the buttons. */
  margin: 0 0 calc(var(--space-3) - var(--space-2));
  font-size: var(--text-body);
  line-height: 1.55;
  text-wrap: balance;
}

.photo-backup {
  margin-top: var(--space-4);
}

.photo-backup-steps {
  margin: 0;
  padding-left: var(--space-5);
}

.photo-backup-steps code {
  overflow-wrap: anywhere;
}

.photo-backup > .notice {
  margin-bottom: 0;
}

/* The other ways into an empty library, stacked under the iPhone's. */
.photo-way {
  margin-top: var(--space-4);
}

/* A command to copy: the words wrap on a phone, the button stays beside them
   when there is room and drops under them when there is not. */
.photo-way-command {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2) var(--space-3);
  margin-top: var(--space-2);
}

.photo-way-command code {
  flex: 1 1 16rem;
  min-width: 0;
  padding: 8px 10px;
  background: var(--field);
  border-radius: var(--radius-2);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 12px;
  overflow-wrap: anywhere;
}

.services-review {
  margin-top: var(--space-3);
}

.services-saved {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2) var(--space-3);
  margin: var(--space-3) 0 0;
}

.services-saved > p {
  margin: 0;
}

.waiting-restart .page-actions {
  margin-top: var(--space-3);
  align-items: stretch;
}

.waiting-restart .page-actions a.button {
  justify-content: center;
}

/* --- shared albums ---------------------------------------------------------
   Whose an album or a photo is, the dialog that shares an album, and the
   picker that adds photos to one. */
.photo-album-section + .photo-album-section {
  margin-top: var(--space-6);
}

.photo-owner-mark {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  min-width: 0;
  font-size: var(--text-label);
  color: var(--muted);
}

.photo-owner-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* --- the Location page ------------------------------------------------------
   A map with the people under it, then tabs of rows. The rows are buttons that
   choose a person, a place or a visit, and keep one height whether chosen or
   not: the border is there from the start, transparent, and only its color
   changes. */
.location-map-card {
  padding: 0;
  overflow: hidden;
}

.location-map {
  position: relative;
  height: 360px;
  background: var(--rail);
}

/* MapLibre's own stylesheet makes this element position: relative, loaded
   after this one, so its size is given outright rather than by its edges. */
.location-map-canvas {
  width: 100%;
  height: 100%;
}

/* MapLibre's styles are light. In dark mode the map is dimmed rather than
   drawn from a second style; the markers are elements above the canvas, so
   the pictures and icons on them keep their colors. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme='light']) .location-map .maplibregl-canvas {
    filter: brightness(0.8) contrast(1.1);
  }
}

:root[data-theme='dark'] .location-map .maplibregl-canvas {
  filter: brightness(0.8) contrast(1.1);
}

.location-marker {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: 2px solid var(--surface);
  border-radius: var(--radius-round);
  background: var(--surface);
  color: var(--text);
  box-shadow: var(--shadow-raised);
  cursor: pointer;
}

/* People are drawn above places, so a person at one of their places shows
   their picture on top of its pin; the one chosen is above the rest of its
   kind. The minimum height the phone gives every button would stretch a
   place's round pin into a pill: the marker has its own press area (below),
   so it keeps its size. */
.location-marker-person {
  z-index: 3;
  width: 40px;
  height: 40px;
}

.location-marker-person.is-selected {
  z-index: 4;
}

.location-marker-place {
  z-index: 1;
  width: 28px;
  height: 28px;
  min-height: 0;
}

.location-marker-place.is-selected {
  z-index: 2;
}

/* A marker is drawn small and pressed at 44px: the pseudo-element is part of
   the button, so a press on it counts, and a finger finds a place marker that
   is 28px across. The map positions the button itself (absolute), which is
   what the pseudo-element is placed against. */
.location-marker::after {
  content: '';
  position: absolute;
  inset: -8px;
}

.location-marker-person::after {
  inset: -2px;
}

.location-marker.is-selected {
  border-color: var(--leaf);
}

/* People close enough to cover each other are one marker: their pictures
   overlapping, the first in front, and a list of names under them. Above
   single people, and the open one above everything. */
.location-marker-group {
  z-index: 3;
}

.location-marker-group.is-selected {
  z-index: 4;
}

.location-marker-group.is-open {
  z-index: 5;
}

.location-marker-stack {
  display: flex;
  align-items: center;
  min-height: 44px;
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
}

.location-marker-stack-picture {
  position: relative;
  display: flex;
  border: 2px solid var(--surface);
  border-radius: var(--radius-round);
  background: var(--surface);
  box-shadow: var(--shadow-raised);
}

.location-marker-stack-picture + .location-marker-stack-picture {
  margin-left: -18px;
}

.location-marker-stack-picture.is-selected {
  border-color: var(--leaf);
}

.location-marker-stack-more {
  margin-left: var(--space-1);
  padding: 2px var(--space-2);
  border-radius: var(--radius-round);
  background: var(--surface);
  color: var(--text);
  font-size: var(--text-label);
  font-weight: 600;
  box-shadow: var(--shadow-raised);
}

.location-marker-menu {
  position: absolute;
  top: calc(100% + var(--space-1));
  left: 50%;
  min-width: 180px;
  margin: 0;
  padding: var(--space-1);
  border: 1px solid var(--border);
  border-radius: var(--radius-3);
  background: var(--surface);
  box-shadow: var(--shadow-raised);
  list-style: none;
  overflow-y: auto;
  transform: translateX(-50%);
}

/* Opened above the pictures, where the map has more room. */
.location-marker-menu.is-above {
  top: auto;
  bottom: calc(100% + var(--space-1));
}

.location-marker-menu button {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  width: 100%;
  min-height: 44px;
  padding: 0 var(--space-2);
  border: 0;
  border-radius: var(--radius-2);
  background: none;
  color: var(--text);
  font-weight: inherit;
  text-align: left;
}

.location-marker-menu button.is-selected {
  background: color-mix(in srgb, var(--leaf) 10%, var(--surface));
}

.location-place-visits {
  margin: 0 0 var(--space-3);
  padding-left: var(--space-4);
}

.location-people {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-4);
  border-top: 1px solid var(--border);
}

.location-row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  width: 100%;
  min-height: 56px;
  margin-bottom: var(--space-2);
  padding: var(--space-2) var(--space-3);
  text-align: left;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-2);
  color: var(--text);
}

.location-people .location-row {
  margin-bottom: 0;
}

/* A visit and its menu side by side: the visit takes the width. */
.location-visit-row {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
}

.location-visit-row > .location-row {
  flex: 1 1 auto;
  width: auto;
  min-width: 0;
}

/* As wide as the menu button beside the other visits. */
.location-visit-menu-space {
  flex: none;
  width: 28px;
}

.location-row.is-selected {
  border-color: var(--leaf);
  background: color-mix(in srgb, var(--leaf) 10%, var(--surface));
}

.location-row-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 28px;
}

.location-row-body {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-width: 0;
}

.location-row-body > * {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The line under a person's name says where they are and since when. It wraps
   to a second line rather than losing the time to an ellipsis. */
.location-people .location-row-body > .muted {
  overflow: visible;
  text-overflow: clip;
  white-space: normal;
}

.location-trip {
  flex-direction: column;
  align-items: stretch;
  gap: var(--space-1);
}

/* The tag stays on the line of the leg, which truncates with an ellipsis as the
   other rows do, and does not make the line taller than its text. */
.location-trip-leg .tag {
  padding-block: 0;
  vertical-align: middle;
}

.location-trip-leg {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-height: 32px;
}

.location-travel-paging {
  display: flex;
  justify-content: flex-end;
  gap: var(--space-2);
}

.location-row-meta {
  flex: 0 0 auto;
  white-space: nowrap;
  font-size: var(--text-label);
}

/* The pictures taken during a visit, under its row, starting under the
   place's name: one line of at most five squares, which grow smaller together
   on a narrow screen rather than wrap the count onto a line of its own. */
.location-visit-photos {
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 56px));
  gap: var(--space-2);
  margin: 0 0 var(--space-3) calc(var(--space-3) + 28px + var(--space-3) + 1px);
}

.location-visit-photo {
  display: flex;
  align-items: center;
  justify-content: center;
  aspect-ratio: 1;
  overflow: hidden;
  border-radius: var(--radius-2);
  background: var(--border);
  color: var(--text);
}

.location-visit-photo img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.location-visit-photo-more {
  border: 1px solid var(--border);
  font-weight: 600;
  text-decoration: none;
}

.location-day-picker {
  display: flex;
  align-items: end;
  flex-wrap: wrap;
  gap: var(--space-3);
  margin-bottom: var(--space-3);
}

/* The label's own gap under it would lift the field above the button beside
   it, which lines up with the field's bottom edge. */
.location-day-picker label {
  margin-bottom: 0;
}

/* An empty date field has no text to be as wide as once its native look is
   off, and Safari draws it as a sliver: as wide as a date, as the usage
   card's range is. */
.location-day-picker input[type='date'] {
  min-width: 9.5rem;
}

/* A day's name, and at its end the button that draws the day's path on the
   map, as the iPhone's day heading does. */
.location-day-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  margin: var(--space-4) 0 var(--space-2);
}

.location-day-head h4 {
  margin: 0;
}

.location-day-head .button-quiet {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  flex-shrink: 0;
}

.location-credit {
  margin: var(--space-3) 0 0;
  font-size: var(--text-label);
}

.location-find {
  display: flex;
  gap: var(--space-2);
  margin-bottom: var(--space-3);
}

.location-find input {
  flex: 1 1 auto;
  min-width: 0;
}

.location-find button {
  flex: 0 0 auto;
  white-space: nowrap;
}

.location-nearby {
  display: flex;
  flex-direction: column;
  max-height: 50vh;
  overflow-y: auto;
}

@media (max-width: 600px) {
  .location-map {
    height: 300px;
  }
}

/* The map inside the place editor, after the rule above so a phone keeps it
   small too. */
.location-map-compact {
  height: 220px;
  border-radius: var(--radius-2);
  overflow: hidden;
}

.location-keep-typed {
  display: flex;
  flex-direction: column;
  gap: 8px;
  align-items: flex-start;
}

.photo-circle-chip {
  display: inline-flex;
  flex: 0 0 auto;
  align-items: center;
  justify-content: center;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: var(--field);
  color: var(--muted);
}

.photo-circle-chip-large {
  width: 28px;
  height: 28px;
}

.photo-album-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-1) var(--space-3);
  margin-bottom: var(--space-3);
}

.photo-album-head .photo-owner-mark {
  color: var(--text);
  font-size: inherit;
  font-weight: 600;
}

.photo-share-heading {
  margin: var(--space-3) 0 0;
  font-size: var(--text-label);
  font-weight: 600;
}

.photo-share-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin: var(--space-2) 0;
  padding: 0;
  list-style: none;
}

.photo-share-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-1) var(--space-2);
}

.photo-share-name {
  flex: 1 1 8rem;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.photo-share-tags {
  display: inline-flex;
  flex-wrap: wrap;
  gap: var(--space-1);
}

.photo-share-choices {
  display: flex;
  flex-direction: column;
  gap: 2px;
  max-height: 14rem;
  margin-top: var(--space-2);
  overflow-y: auto;
}

.photo-share-choice {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  width: 100%;
  min-width: 0;
  padding: var(--space-1) var(--space-2);
  border: 1px solid transparent;
  background: transparent;
  font-weight: 400;
  text-align: left;
}

.photo-share-choice.is-chosen {
  border-color: var(--accent);
  background: var(--field);
}

.photo-share-choice-name {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The tick's room is there whether the row is chosen or not, so a row is as
   tall chosen as not. */
.photo-share-choice-check {
  display: flex;
  flex: 0 0 auto;
  align-items: center;
  justify-content: center;
  width: 16px;
  height: 28px;
}

.photo-share-levels {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin-top: var(--space-2);
}

/* A choice and what it means: the choice at the text size, the meaning under
   it in muted text, in line with the words and not with the box. */
.photo-share-option {
  margin-top: var(--space-2);
}

.photo-share-levels .photo-share-option {
  margin-top: 0;
}

.photo-share-option label.checkbox {
  margin: 0;
}

.photo-share-option-hint {
  margin: 2px 0 0 calc(13px + var(--space-2));
  font-size: var(--text-label);
}

.photo-picker {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(84px, 1fr));
  gap: 4px;
  max-height: 22rem;
  margin-top: var(--space-2);
  overflow-y: auto;
}

.photo-picker-cell {
  position: relative;
  display: block;
  aspect-ratio: 1;
  padding: 0;
  overflow: hidden;
  border: none;
  border-radius: 4px;
  background: var(--field);
}

.photo-picker-cell img,
.photo-picker-blank {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 120ms ease;
}

.photo-picker-cell.is-selected img {
  transform: scale(0.88);
  border-radius: 4px;
}

.photo-picker-cell.is-selected {
  background: color-mix(in srgb, var(--accent) 22%, var(--field));
}

.photo-picker-check {
  inset: 6px auto auto 6px;
  width: 22px;
  height: 22px;
}

.photo-picker-cell.is-selected .photo-picker-check {
  border-color: var(--accent);
  background: var(--accent);
  color: var(--accent-text);
}

.photo-save-button {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  min-height: 34px;
  padding: 4px 12px;
  border-radius: var(--radius-round);
  font-size: var(--text-label);
  font-weight: 650;
}

/* The map under the place in the details: a short one, the same map as
   Location's. Written after .location-map's phone rule to win over it. */
.photo-viewer-map {
  margin-top: var(--space-2);
}

.photo-viewer-map .location-map {
  height: 180px;
  border-radius: var(--radius-2);
  overflow: hidden;
}

.photo-change-date,
.photo-change-location {
  margin-left: var(--space-1);
  vertical-align: middle;
}

/* The cities a search found, in the dialog that changes where photos were
   taken: a column of buttons, the name first and the distance under it. */
.photo-place-results {
  list-style: none;
  margin: var(--space-3) 0 0;
  padding: 0;
  max-height: 14rem;
  overflow-y: auto;
}

.photo-place-results button {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 2px;
  width: 100%;
  min-height: 44px;
  border-color: transparent;
  background: none;
  text-align: left;
}

.photo-place-results button:hover:not(:disabled) {
  background: var(--sidebar-hover);
}


/* --- photos, the second look ------------------------------------------------
   One toolbar under one strip of tabs, rows that say what they are, and a hit
   area a thumb can find. */

/* A checkbox or a radio whose words are the page's own, not a hint under a
   field: the text size and color, with the explanation under it muted. */
label.checkbox.is-plain {
  color: var(--text);
  font-size: var(--text-body);
}

label.checkbox.is-plain > span {
  color: var(--text);
  font-size: var(--text-body);
  margin-bottom: 0;
}

label.checkbox.is-plain > span > .muted {
  color: var(--muted);
  font-size: 13px;
}

/* A picture that has not arrived is the cell's neutral ground, not the alt text
   in a broken-image box. The alt text is still there for a screen reader. */
.photo-image:not(.is-loaded) {
  opacity: 0;
}

.photo-day-heading .photo-day-note {
  display: block;
  margin-top: 2px;
  font-size: var(--text-label);
  font-weight: 400;
}

.photo-closest {
  margin-bottom: var(--space-3);
}

.photo-closest .photo-day-heading {
  margin: 0;
}

.photo-closest p {
  margin: 2px 0 0;
  font-size: var(--text-label);
}

.photo-suggestion-actions {
  display: flex;
  flex: 0 0 auto;
  align-items: center;
  gap: var(--space-2);
}

.photo-show-hidden.checkbox {
  margin-top: 0;
}

@media (max-width: 600px) {
  /* The strip of tabs is the width of the page and its tabs share it; four
     tabs of a few words each fit the narrowest phone, and a tab is as tall as
     a finger. */
  .photo-sections .tabs {
    gap: 0;
  }

  .photo-sections .tabs button {
    flex: 1 1 auto;
    min-height: 44px;
    margin-right: 0;
    padding: 8px var(--space-1);
    text-align: center;
  }

  /* One row: the tools take the room the actions leave. What does not fit
     is behind the More button, so the row never wraps onto a second line of
     a different length. */
  .photo-toolbar {
    flex-wrap: nowrap;
  }

  .photo-toolbar-tools {
    flex: 1 1 0;
  }

  .photo-toolbar-actions {
    flex: 0 0 auto;
    flex-wrap: nowrap;
  }

  /* Whatever is touched is at least 44px each way. */
  .photos-page input[type='search'],
  .photos-page .photo-toolbar button,
  .photo-filter-button {
    min-height: 44px;
  }

  .photo-toolbar-actions .icon-action {
    width: 44px;
    height: 44px;
  }

  .photo-selection-bar .icon-button,
  .photo-viewer .icon-button,
  .photo-viewer-actions .photo-save-button,
  .photo-tray .icon-button {
    width: 44px;
    height: 44px;
  }

  .photo-viewer-bar .photo-live-button,
  .photo-viewer-actions .photo-save-button {
    min-height: 44px;
  }

  /* An icon like its neighbors on a phone: the words stay for a screen reader,
     and the capsule keeps to the width. */
  .photo-viewer-actions .photo-save-button {
    justify-content: center;
    padding: 0;
    border-color: transparent;
    background: transparent;
    box-shadow: none;
  }

  .photo-viewer-actions .photo-save-button .photo-save-label {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }

  .photo-selection-bar {
    padding-inline: 2px;
  }

  .photo-viewer-bar,
  .photo-viewer-actions {
    padding-inline: var(--space-2);
  }

  .photo-viewer-person-not,
  .photo-viewer-details .icon-action {
    width: 44px;
    height: 44px;
  }

  /* The corner tick is 22px to look at and 44px to hit. */
  .photo-check {
    top: 0;
    left: 0;
    width: 44px;
    height: 44px;
  }

  .photo-check-box {
    inset: auto;
    top: 8px;
    left: 8px;
    width: 22px;
    height: 22px;
  }

  .photo-show-hidden,
  .photo-filter-kinds label.checkbox,
  .photo-share-option label.checkbox,
  .photo-person-name-button {
    min-height: 44px;
  }

  .photo-person-cell .photo-person-title {
    min-height: 44px;
  }

  .contact-picker-row {
    min-height: 48px;
  }

  .photo-suggestion-actions .icon-action,
  .photo-share-row .icon-action {
    width: 44px;
    height: 44px;
  }

  .photo-share-choice,
  .photo-suggestion-actions button,
  .photo-viewer-action {
    min-height: 44px;
  }

  /* The choice is 44px to hit; its explanation reads as under its words, not
     under its box. */
  .photo-share-option-hint {
    margin-top: -8px;
  }
}

/* The one thing the details offer to do with a place: look around it. A button
   under the place's name, at the size of a row's own. */
.photo-near-button {
  display: block;
  margin-top: var(--space-1);
  padding: 4px 12px;
  font-size: var(--text-label);
}

.photo-near-filter {
  flex: 0 1 auto;
  min-width: 0;
  max-width: 100%;
  min-height: 40px;
  padding-left: var(--space-3);
  font-size: var(--text-body);
}

/* The place is what gives way when the chip is longer than the room it has:
   it is cut with an ellipsis, and the close button beside it keeps its size. */
.photo-near-label {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

@media (max-width: 600px) {
  .photo-near-button {
    min-height: 44px;
  }

  /* The chip goes under the search when it does not fit beside it, rather
     than squeezing the search box, and its close button is a 44px square. */
  .photo-toolbar-tools {
    flex-wrap: wrap;
  }

  .photo-toolbar-tools input[type='search'] {
    min-width: 160px;
  }

  .photo-near-filter {
    min-height: 44px;
    padding-block: 0;
    padding-right: 0;
  }

  .photo-near-filter .icon-button {
    width: 44px;
    height: 44px;
  }
}

/* A page's icons stay together when its row of actions wraps: two icons on a
   line of their own, not one left over. */
.photo-icon-actions {
  display: inline-flex;
  flex-wrap: nowrap;
  gap: var(--space-2);
}

/* On a phone every Photos control is a 44px target, the small ones in lists
   and trays too, and the page ends clear of the floating assistant button,
   which would otherwise sit over its last line. */
@media (max-width: 600px) {
  .photos-page {
    padding-bottom: 88px;
  }

  .photo-viewer-albums a {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
  }

  .photo-toolbar-menu button {
    min-height: 44px;
  }

  .photo-person-head-body button {
    min-height: 44px;
  }

  .photo-tray-item .icon-action {
    width: 44px;
    height: 44px;
  }
}

/* On a phone a finger needs 44 pixels: the buttons that end a dialog, the
   fields in one, and the top bar's menu button are made that tall there. A
   desktop keeps the 40-pixel controls the pages are measured around. */
@media (max-width: 600px) {
  .dialog-actions button {
    min-height: 44px;
  }

  .dialog input:not([type='checkbox']):not([type='radio']),
  .dialog select {
    min-height: 44px;
  }

  .topbar .icon-button {
    width: 44px;
    height: 44px;
  }
}


.location-here {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2) var(--space-3);
  padding: var(--space-3) var(--space-4);
  border-top: 1px solid var(--border);
}

/* The place editor is a column of fields with one gap between them, and a
   smaller one between a field's name and what it holds. */
.location-place-editor {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}

.location-place-editor label,
.location-place-editor-group {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin: 0;
}

.location-place-editor p,
.location-place-editor h4,
.location-place-editor .location-place-visits {
  margin: 0;
}

.location-place-editor section {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.location-address-row {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-3);
}

.location-address-text {
  min-width: 0;
  overflow-wrap: anywhere;
}

/* The box for the exact number beside the slider for a rough one. */
.location-radius-row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
}

.location-radius-row input[type='number'] {
  width: 96px;
  flex: 0 0 auto;
}

.location-radius-row input[type='range'] {
  flex: 1 1 auto;
  min-width: 0;
  padding: 0;
  border: none;
  background: none;
}

/* --- photos, the look of a photo library ------------------------------------
   The days on the screen under the heading, a switch between the grid and an
   overview by year or month, and the overview's big pictures. */

/* The line under the page's heading: which days the photos on the screen were
   taken on. It is part of the heading's block, so the heading's own margin
   under it is taken away. */
.page-heading-row:has(+ .photo-date-range) {
  margin-bottom: 2px;
}

.photo-date-range {
  margin: 0 0 var(--space-2);
  min-height: 1.4em;
  color: var(--muted);
  font-size: var(--text-body);
  font-variant-numeric: tabular-nums;
}

/* The switch in the toolbar: the segmented group, at the height of the
   buttons beside it, the chosen one filled. */
.photo-scale-switch {
  align-self: stretch;
  min-height: 40px;
  border-radius: var(--radius-2);
}

.photo-scale-switch button {
  min-width: 56px;
  padding: 0 var(--space-3);
  font-size: var(--text-body);
}

.photo-scale-switch button.active {
  background: var(--accent);
  color: var(--accent-text);
}

.photo-scale-switch button.active:hover {
  background: var(--accent);
  color: var(--accent-text);
}

/* The overview: one big picture for each month (under its year), or for each
   year. */
.photo-overview {
  transition: opacity 120ms ease;
}

.photo-overview.is-choosing {
  opacity: 0.55;
  pointer-events: none;
}

.photo-overview-year + .photo-overview-year {
  margin-top: var(--space-6);
}

.photo-overview-heading {
  margin: 0 0 var(--space-2);
  font-size: 20px;
  font-weight: 700;
  letter-spacing: -0.01em;
}

.photo-overview-waiting {
  min-height: 160px;
  background: var(--field);
  opacity: 0.5;
}

.photo-period-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: 2px;
}

.photo-period-grid.is-years {
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
}

.photo-period {
  position: relative;
  display: block;
  aspect-ratio: 4 / 3;
  width: 100%;
  padding: 0;
  border: none;
  border-radius: 0;
  overflow: hidden;
  background: var(--field);
  color: #fff;
  cursor: pointer;
}

.photo-period:hover:not(:disabled) {
  background: var(--field);
}

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

.photo-period .photo-image {
  position: absolute;
  inset: 0;
}

/* The name sits on a shade at the foot, so that it can be read over any
   picture. */
.photo-period-label {
  position: absolute;
  right: 0;
  bottom: 0;
  left: 0;
  padding: var(--space-5) var(--space-3) var(--space-2);
  background: linear-gradient(to top, rgb(0 0 0 / 60%), rgb(0 0 0 / 0%));
  font-size: 18px;
  font-weight: 700;
  line-height: 1.2;
  text-align: left;
  text-shadow: 0 1px 2px rgb(0 0 0 / 40%);
}

.photo-period-grid.is-years .photo-period-label {
  font-size: 24px;
}

@media (max-width: 600px) {
  .photo-period-grid,
  .photo-period-grid.is-years {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .photo-period-label,
  .photo-period-grid.is-years .photo-period-label {
    padding-inline: var(--space-2);
    font-size: 16px;
  }

  /* On a phone the switch floats over the grid at the foot of the screen,
     where a thumb is, as a capsule of its own; the toolbar keeps its two rows.
     The page has room under it for the last row to be read. */
  .photo-scale-switch {
    position: fixed;
    bottom: calc(var(--space-4) + env(safe-area-inset-bottom));
    left: 50%;
    z-index: 5;
    min-height: 48px;
    transform: translateX(-50%);
    border-color: var(--border-strong);
    border-radius: var(--radius-round);
    background: color-mix(in srgb, var(--surface) 92%, transparent);
    box-shadow: var(--shadow-floating);
    backdrop-filter: blur(12px);
  }

  .photo-scale-switch button {
    min-width: 64px;
    min-height: 44px;
  }

  .photos-page:has(.photo-scale-switch) {
    padding-bottom: 72px;
  }
}

/* The Health page: the kinds of data in a column beside the chosen one's
   chart, readings and route, or above it on a narrower window. On a phone the
   list is the page until a type is chosen (it is in the address), and then
   the type is, with a way back to the list. */
.health-toolbar {
  display: flex;
  align-items: flex-end;
  gap: var(--space-3);
  margin-bottom: var(--space-4);
}

.health-layout {
  display: grid;
  grid-template-columns: minmax(240px, 300px) minmax(0, 1fr);
  gap: var(--space-4);
  align-items: start;
}

.health-types-pane,
.health-detail {
  min-width: 0;
}

.health-types h4 {
  margin: var(--space-3) 0 var(--space-2);
}

.health-types section:first-child h4 {
  margin-top: 0;
}

.health-type-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  width: 100%;
  min-height: 48px;
  margin-bottom: var(--space-2);
  padding: var(--space-2) var(--space-3);
  text-align: left;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-2);
  color: var(--text);
}

.health-type-row.is-selected {
  border-color: var(--leaf);
  background: color-mix(in srgb, var(--leaf) 10%, var(--surface));
}

.health-type-name {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.health-type-name > .muted {
  font-size: var(--text-label);
}

.health-type-value {
  flex: 0 0 auto;
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  white-space: nowrap;
}

.health-controls {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  justify-content: flex-end;
  margin-bottom: var(--space-3);
}

/* The chart under the buttons while the next one is read: the same chart,
   dimmed, so the card does not jump and the button just pressed stays. */
.health-chart {
  transition: opacity 0.15s ease;
}

.health-chart.is-stale {
  opacity: 0.45;
  pointer-events: none;
}

.health-status {
  margin: 0 0 var(--space-3);
}

.health-route-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-3);
}

.health-route-head h3 {
  margin: 0;
  min-width: 0;
  overflow-wrap: anywhere;
}

.health-numbers {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-5);
  margin: var(--space-3) 0 0;
}

.health-numbers dt {
  font-size: var(--text-label);
}

.health-numbers dd {
  margin: 0;
  font-size: var(--text-title);
  font-variant-numeric: tabular-nums;
}

.health-back {
  display: none;
  margin-bottom: var(--space-3);
}

.health-route-map {
  margin-top: var(--space-3);
  border-radius: var(--radius-2);
  overflow: hidden;
}

.health-share {
  margin: 0 0 var(--space-3);
  padding: var(--space-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-2);
}

.health-share-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
}

.health-share-categories {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-4);
  margin-top: var(--space-2);
}

/* A tablet keeps the list beside the chart, narrower, so the chart is not
   below a hundred rows. */
@media (max-width: 900px) {
  .health-layout {
    grid-template-columns: minmax(200px, 240px) minmax(0, 1fr);
  }
}

@media (max-width: 760px) {
  .health-layout {
    grid-template-columns: minmax(0, 1fr);
  }

  .health-layout .health-detail {
    display: none;
  }

  .health-layout.has-type .health-detail {
    display: block;
  }

  .health-layout.has-type .health-types-pane {
    display: none;
  }

  .health-layout.has-type .health-back {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
  }
}

/* On a phone the page ends clear of the floating assistant button, which
   would otherwise sit over the last row of the list, as on the Photos page. */
@media (max-width: 600px) {
  .health-layout {
    padding-bottom: 88px;
  }
}

/* The search box at the top of the rail, between the mark and the rows. The
   rail is the narrow column, so the box takes its width and is the global
   input in every other respect. Narrowed to icons it is one icon button. Its
   sides are the rows' 8px margin, and on the right the rows' scrollbar too, so
   the box and the row you are on line up. */
.sidebar-search {
  position: relative;
  flex: 0 0 auto;
  padding: 0 calc(8px + var(--sidebar-scrollbar-width, 0px)) var(--space-2) 8px;
}
.sidebar-search input[type='search'] {
  font-size: 13px;
}
.sidebar.collapsed .sidebar-search {
  display: flex;
  justify-content: center;
  padding-left: 0;
  padding-right: 0;
}
.sidebar-search-open {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  padding: 0;
  border: 1px solid transparent;
  border-radius: var(--radius-2);
  background: none;
  color: var(--muted);
  cursor: pointer;
}
.sidebar-search-open:hover {
  color: var(--text);
}

/* The pages the words are in the names of, and the search for them, under the
   box and over the rail's rows. A row is the width of the list, so it can be
   pressed anywhere along it, and the row the keyboard is on looks like a rail
   row under the pointer. */
.sidebar-search-list {
  position: absolute;
  top: calc(100% - var(--space-2) + 2px);
  right: calc(8px + var(--sidebar-scrollbar-width, 0px));
  left: 8px;
  z-index: 20;
  display: flex;
  flex-direction: column;
  max-height: 320px;
  padding: 4px;
  overflow-y: auto;
  border: 1px solid var(--border);
  border-radius: var(--radius-2);
  background: var(--surface);
  box-shadow: var(--shadow-floating);
}
.sidebar-search-row {
  display: flex;
  align-items: center;
  gap: 11px;
  width: 100%;
  min-height: 0;
  padding: 7px 10px;
  border: 0;
  border-radius: var(--radius-1);
  background: transparent;
  color: var(--muted);
  font: inherit;
  font-size: 14px;
  font-weight: 500;
  text-align: left;
  cursor: pointer;
}
.sidebar-search-row.active {
  background: var(--sidebar-hover);
  color: var(--text);
}
.sidebar-search-row.active .sidebar-icon {
  color: var(--text);
}
.sidebar-search-label {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* The words searched for are what the row says, so they wrap rather than
   being cut short in a rail too narrow for them. */
.sidebar-search-everything .sidebar-search-label {
  white-space: normal;
  overflow-wrap: anywhere;
}
@media (max-width: 760px) {
  /* The overlay is never narrowed to icons. */
  .sidebar.collapsed .sidebar-search {
    display: block;
    padding: 0 calc(8px + var(--sidebar-scrollbar-width, 0px)) var(--space-2) 8px;
  }
}

/* The results page: a panel for each area that had something. A result is a
   row that is the button, as an idea is, with when it happened at the right
   where there is room. Nothing here is wider than the window: text wraps and
   the title is cut to its line. */
.search-page {
  min-width: 0;
}
.search-page-form {
  margin-bottom: var(--space-4);
}
.search-group h3 {
  margin-bottom: var(--space-2);
}
.search-results {
  display: flex;
  flex-direction: column;
}
.search-result {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
  width: 100%;
  padding: var(--space-2) var(--space-2);
  border: 0;
  border-top: 1px solid var(--border);
  border-radius: 0;
  background: none;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
}
.search-result:first-child {
  border-top: 0;
}
.search-result:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}
.search-result-text {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}
.search-result-title {
  overflow: hidden;
  font-weight: 600;
  line-height: 1.35;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.search-result-detail {
  display: -webkit-box;
  overflow: hidden;
  overflow-wrap: anywhere;
  font-size: var(--text-label);
  line-height: 1.4;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
}
.search-result-time {
  flex: none;
  font-size: var(--text-label);
  white-space: nowrap;
}
/* Photos found are tiles of the Photos page's grid (.photo-grid, .photo-cell),
   a little smaller, so that four fit across a phone and a card on a wide
   window holds a row of them. */
.search-photo-grid {
  grid-template-columns: repeat(auto-fill, minmax(104px, 1fr));
  gap: 2px;
  border-radius: var(--radius-2);
  overflow: hidden;
}
@media (max-width: 600px) {
  .search-photo-grid {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }
}
.search-group .page-actions {
  margin-top: var(--space-3);
}

/* A person's card: the head with the picture, the name and what can be done,
   then a panel for each part of the app that has something of theirs. A row in
   a panel is a line of text, or a button or link that opens the thing it
   names, as a search result is. Nothing is wider than the window: text wraps
   and a title is cut to its line. */
.person-page {
  min-width: 0;
}
.person-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3) var(--space-4);
}
.person-head-who {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  min-width: 0;
}
.person-head-who .avatar {
  flex: none;
}
.person-head-body {
  min-width: 0;
}
.person-head-body p {
  margin: 0;
  overflow-wrap: anywhere;
}
.person-head-name {
  font-size: var(--text-title);
  font-weight: 600;
}
.person-head-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}
.person-section h3 {
  margin-bottom: var(--space-2);
}
.person-subheading {
  margin: var(--space-3) 0 var(--space-1);
  color: var(--muted);
  font-size: var(--text-label);
  font-weight: 600;
}
.person-section > .person-subheading:first-of-type {
  margin-top: 0;
}
.person-empty {
  margin: 0;
  padding: var(--space-1) 0;
}
.person-section .page-actions {
  margin-top: var(--space-3);
}
.person-row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  width: 100%;
  min-height: 40px;
  padding: var(--space-2) var(--space-2);
  border: 0;
  border-top: 1px solid var(--border);
  border-radius: 0;
  background: none;
  color: inherit;
  font: inherit;
  text-align: left;
  text-decoration: none;
}
.person-section h3 + .person-row,
.person-section .person-subheading + .person-row {
  border-top: 0;
}
a.person-row:hover:not(:disabled),
button.person-row:hover:not(:disabled) {
  background: var(--field);
}
a.person-row,
button.person-row {
  cursor: pointer;
}
a.person-row:focus-visible,
button.person-row:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}
.person-row-icon {
  display: flex;
  flex: none;
  color: var(--muted);
}
.person-row-cover {
  flex: none;
  width: 40px;
  height: 40px;
  overflow: hidden;
  border-radius: var(--radius-2);
  background: var(--field);
}
.person-row-cover img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.person-row-text {
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}
.person-row-title {
  font-weight: 600;
  line-height: 1.35;
  overflow-wrap: anywhere;
}
.person-row-detail {
  display: -webkit-box;
  overflow: hidden;
  overflow-wrap: anywhere;
  font-size: var(--text-label);
  line-height: 1.4;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
}
.person-row-meta {
  flex: none;
  font-size: var(--text-label);
  white-space: nowrap;
}
.person-photos {
  grid-template-columns: repeat(auto-fill, minmax(88px, 120px));
  gap: var(--space-1);
}
.person-details {
  margin: 0;
}
.person-details > div,
.person-details > dt,
.person-details > dd {
  margin: 0;
}
.person-details > div {
  display: flex;
  flex-wrap: wrap;
  gap: 0 var(--space-3);
}
.person-details dt {
  flex: 0 0 7rem;
  color: var(--muted);
}
.person-details dd {
  flex: 1 1 12rem;
  min-width: 0;
  overflow-wrap: anywhere;
}
@media (max-width: 600px) {
  .person-head-actions {
    width: 100%;
  }
  .person-head-actions .button,
  .person-head-actions button {
    flex: 1 1 8rem;
    min-height: 44px;
  }
}

/* The contact a speaker's name is tied to, under the name. */
.speaker-contact {
  display: inline-flex;
  gap: var(--space-1);
}

/* A person's name above a chat message opens their card, in the quiet
   color of the name it replaces. */
.agent-chat-author a {
  color: inherit;
  text-decoration: none;
}
.agent-chat-author a:hover {
  text-decoration: underline;
}
.agent-chat-avatar a {
  display: block;
  line-height: 0;
}

/* A link in the person's own bubble is the bubble's letters: the page's text
   color is the bubble's own color there, and would not be seen. */
.agent-line.user a {
  color: inherit;
  text-decoration-color: currentColor;
}

/* The card under a message that holds a link: the page's picture, its site,
   its title and two lines about it. The whole card opens the link, so it is an
   anchor with no underline, and it keeps its size under the pointer: only the
   border answers a hover. The marker is what the card waits to be near the
   screen by; out of the flow, so it adds neither height nor a flex gap. */
.link-card {
  display: block;
  flex: 0 0 auto;
  width: 320px;
  max-width: 100%;
  overflow: hidden;
  border: 1px solid var(--border);
  border-radius: var(--radius-3);
  background: var(--surface);
  color: var(--text);
  text-decoration: none;
}

.link-card:hover {
  border-color: var(--border-strong);
}

/* In a chat the bubble and what is under it sit two pixels apart, which is
   too close for a card. */
.agent-chat-body > .link-card,
.agent-chat-turn.mine > .link-card {
  margin-top: 4px;
}

.link-card.is-own {
  align-self: flex-end;
}

.link-card-image {
  display: block;
  width: 100%;
  height: auto;
  max-height: 200px;
  object-fit: cover;
  background: var(--field);
}

.link-card-text {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: var(--space-2) var(--space-3);
}

.link-card-site {
  overflow: hidden;
  color: var(--muted);
  font-size: var(--text-label);
  text-overflow: ellipsis;
  white-space: nowrap;
}

.link-card-title {
  display: -webkit-box;
  overflow: hidden;
  font-size: var(--text-body);
  font-weight: 600;
  line-height: 1.3;
  overflow-wrap: anywhere;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
}

.link-card-description {
  display: -webkit-box;
  overflow: hidden;
  color: var(--muted);
  font-size: var(--text-label);
  line-height: 1.35;
  overflow-wrap: anywhere;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
}

.link-card-marker {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  pointer-events: none;
}

/* --- the Browser page ------------------------------------------------------
   The person's tab live: a strip of the tabs there are, then one panel with
   who controls the tab above the picture. The picture is as wide as the panel
   and keeps its shape; the strip scrolls sideways on a phone rather than
   wrapping, so the picture stays near the top. */
.browser-page {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  min-width: 0;
}

.browser-page > .muted,
.browser-page > .settings-empty {
  margin: 0;
}

.browser-tabs {
  display: flex;
  gap: var(--space-2);
  overflow-x: auto;
}

/* Every tab has the same border, and the chosen one only darkens it, so
   choosing one moves nothing. */
.browser-tab {
  display: flex;
  flex: 0 0 auto;
  flex-direction: column;
  align-items: flex-start;
  gap: 2px;
  min-height: 44px;
  max-width: 240px;
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-2);
  background: var(--surface);
  color: var(--text);
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.browser-tab:hover {
  background: var(--field);
}

.browser-tab.active {
  border-color: var(--text);
  background: var(--sidebar-hover);
}

.browser-tab strong,
.browser-tab .muted {
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.browser-tab .muted {
  font-size: var(--text-label);
}

.browser-tab-tags {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1);
}

.browser-stage {
  margin-bottom: 0;
}

/* The box the picture is zoomed and moved in with fingers. While the tab is
   held, or the picture zoomed, fingers on it are the page's to handle;
   otherwise a finger scrolls the dashboard, and a mostly upright pinch may
   be taken by the browser as a scroll too. */
.browser-viewport {
  overflow: hidden;
  touch-action: pan-y;
}

.browser-viewport.is-gesturing {
  touch-action: none;
}

.browser-viewport .browser-picture {
  transform-origin: 0 0;
}

/* The agent's request to take the tab over, above the row. */
.browser-request {
  margin: 0 0 var(--space-2);
}

/* One row: who controls the tab, then what can be done about it. The hand
   back form takes the whole row on a phone, under the words. */
.browser-control {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  margin-bottom: var(--space-3);
}

.browser-control > p {
  flex: 1 1 auto;
  margin: 0;
  font-weight: 550;
}

.browser-hand-back {
  display: flex;
  flex: 1 1 100%;
  align-items: center;
  gap: var(--space-2);
}

.browser-hand-back input {
  flex: 1 1 auto;
  min-width: 0;
}

/* The phone's keyboard opens from this button, which a wide screen has no use
   for: it has a keyboard. */
.browser-keyboard {
  display: none;
}

.browser-picture {
  display: block;
  width: 100%;
  max-width: 100%;
  height: auto;
  border: 1px solid var(--border);
  border-radius: var(--radius-1);
  background: var(--field);
  /* The picture is not a thing to drag away or select. */
  user-select: none;
  -webkit-user-drag: none;
}

/* While the person controls the tab, a finger on the picture is the tab's
   pointer and not the page's scrolling. */
.browser-picture.is-controlling {
  cursor: crosshair;
  touch-action: none;
}

@media (max-width: 600px) {
  .browser-keyboard {
    display: flex;
  }
}

/* --- Passwords ---------------------------------------------------------------
   The toolbar over the list, the table (a table-surface, so it scrolls
   sideways on a phone and stays a table), one item's fields, and the parts the
   dialogs share. */
.passwords-toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  margin-bottom: var(--space-3);
}

.passwords-search {
  flex: 1 1 200px;
  min-width: 0;
}

.passwords-marks {
  display: inline-flex;
  flex-wrap: nowrap;
  align-items: center;
  gap: var(--space-2);
}

.passwords-back {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  margin-bottom: var(--space-3);
}

.passwords-tags {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin: 0 0 var(--space-3);
}

/* Whom an item is shared with, and the button that changes it, on one line
   that wraps under the words on a phone. */
.passwords-shared-with {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2) var(--space-3);
  margin: 0 0 var(--space-3);
}

.passwords-shared-with p {
  margin: 0;
}

.passwords-fields {
  margin: 0;
}

.passwords-field {
  padding: var(--space-2) 0;
  border-bottom: 1px solid var(--border);
}

.passwords-field:last-child {
  border-bottom: none;
}

.passwords-field dt {
  font-size: var(--text-label);
}

.passwords-field dd {
  margin: 2px 0 0;
  min-width: 0;
  overflow-wrap: anywhere;
}

.passwords-sites,
.passwords-passkeys {
  margin: 0;
  padding-left: var(--space-4);
}

.passwords-multiline {
  flex: 1;
  min-width: 0;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

.passwords-code {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: var(--text-title);
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.05em;
}

.passwords-code-seconds {
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.passwords-switch-note {
  display: block;
}

.passwords-strength {
  margin-top: calc(var(--space-2) * -1);
}

.passwords-recovery-key {
  font-size: 15px;
  user-select: all;
  overflow-wrap: anywhere;
  white-space: pre-wrap;
}

.passwords-masked {
  /* A card number or security code typed in a text field, hidden by the
     browser's own mask so that it is not taken for a password to save. */
  -webkit-text-security: disc;
}

.passwords-generator {
  margin: 0 0 var(--space-3);
  padding: var(--space-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-2);
}

.passwords-generator-kinds {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-4);
  margin-bottom: var(--space-3);
}

.passwords-new-kinds {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-bottom: var(--space-3);
}

.passwords-share {
  position: relative;
  margin: 0 0 var(--space-3);
  padding: var(--space-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-2);
}

.passwords-share-remove {
  position: absolute;
  top: var(--space-2);
  right: var(--space-2);
}

.passwords-recipients {
  margin: 0;
  padding: 0;
  list-style: none;
}

.passwords-recipients li {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: var(--space-1) var(--space-3);
  padding: var(--space-1) 0;
}

/* A recipient whose key is not the one kept for them: the warning, the code
   from before and the button that trusts the new one, under their row. */
.passwords-key-changed {
  flex-basis: 100%;
}

.passwords-key-changed p {
  margin: 0 0 var(--space-1);
}

.passwords-safety-code {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* A thumb has to hit the icon actions on this page: 44px where the pointer is
   coarse, as the other buttons on a phone are. Only here, since the 28px
   icon-action is shared with rows that are not about passwords. */
@media (pointer: coarse) {
  .passwords-page .icon-action,
  .passwords-share-remove {
    width: 44px;
    height: 44px;
  }
}

/* --- shopping lists -------------------------------------------------------
   The Shopping page: the person's lists beside what is open (a list, a store
   or a receipt). On a phone the lists become a strip above it that scrolls
   sideways, and the tables stay tables inside a box that scrolls. */
/* The page is the mailbox's frame and panes (.mailbox-frame, .mailbox,
   .mailbox-list, .mailbox-pane); what is here is the rows in the column and
   the parts of the pane only Shopping has. */
.shopping-list-toolbar {
  justify-content: flex-start;
}

.shopping-list-toolbar button {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
}

.shopping-rows {
  list-style: none;
  margin: 0;
  padding: 0;
}

.shopping-rows-heading {
  margin: 0;
  padding: var(--space-3) var(--space-3) var(--space-1);
  border-bottom: 1px solid var(--border);
  color: var(--muted);
  font-size: var(--text-label);
  font-weight: 600;
}

/* A list or a store in the column: its name with how many are needed at
   the end, and under the name who it is shared with. */
.shopping-row-link {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 2px var(--space-2);
  align-items: baseline;
  padding: var(--space-2) var(--space-3);
  border-bottom: 1px solid var(--border);
  color: inherit;
  font-size: 13px;
  text-decoration: none;
}

.shopping-row-link:hover {
  background: var(--field);
}

.shopping-row-link.active {
  background: var(--sidebar-hover);
}

.shopping-row-name {
  overflow: hidden;
  font-weight: 550;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.shopping-row-count {
  color: var(--muted);
  font-size: 12px;
  font-variant-numeric: tabular-nums;
}

.shopping-row-meta {
  grid-column: 1 / -1;
  overflow: hidden;
  color: var(--muted);
  font-size: 12px;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The pane scrolls on its own, as the mailbox's reading side does, so the
   column keeps its place while a long list is scrolled. */
.shopping-pane {
  overflow-y: auto;
}

/* The words a screen reader says for a column without a heading are placed
   absolutely; without a positioned box around them they escape the table's
   own scroll box and widen the whole page on a phone. */
.shopping-pane .table-wrap,
.dialog .table-wrap {
  position: relative;
}

/* Back to the column only where the column is not beside the pane. */
.shopping-pane-back {
  display: none;
  flex: none;
}

@container mailbox (max-width: 860px) {
  .shopping-pane-back {
    display: inline-flex;
  }
}

@supports not (container-type: inline-size) {
  @media (max-width: 860px) {
    .shopping-pane-back {
      display: inline-flex;
    }
  }
}

.shopping-home {
  padding: var(--space-3) 0;
}

.shopping-home h3 {
  margin-top: 0;
}

.shopping-list-card {
  min-width: 0;
}

.shopping-list-head {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: var(--space-3);
}

.shopping-list-summary {
  flex: 1;
  min-width: 0;
}

.shopping-list-summary p {
  margin: 0 0 var(--space-1);
}

.shopping-trip-line {
  font-weight: 550;
}

.shopping-list-actions {
  margin-top: var(--space-3);
}

.shopping-table {
  min-width: 560px;
}

.shopping-table td.wrap {
  min-width: 12rem;
}

/* A heading, a count with its unit, a price and a day stay on one line:
   "1 pack" broken over two lines made its row taller than the others. */
.shopping-table th,
.shopping-table td:not(.wrap) {
  white-space: nowrap;
}

.shopping-table th.shrink,
.shopping-table td.shrink {
  width: 1%;
  white-space: nowrap;
}

/* The store and aisle a run of items is under: a band across the table. */
.shopping-group-row th {
  padding: var(--space-2) 10px;
  background: var(--field);
  font-size: var(--text-label);
  font-weight: 600;
}

.shopping-table td {
  vertical-align: middle;
}

.shopping-item-name {
  min-width: 0;
}

.shopping-item-description {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-1);
}

.shopping-item-name .muted {
  font-size: var(--text-label);
}

/* An item in the cart is crossed out, and keeps its place in the table. */
.shopping-row.is-in-cart .shopping-item-description strong {
  color: var(--muted);
  text-decoration: line-through;
}

.shopping-picture {
  display: block;
  width: 40px;
  height: 40px;
  border: 1px solid var(--border);
  border-radius: var(--radius-1);
  object-fit: cover;
}

.shopping-picture-large {
  width: 96px;
  height: 96px;
  border-radius: var(--radius-2);
}

.shopping-picture-empty {
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--field);
  color: var(--muted);
  font-weight: 600;
}

.shopping-picture-large.shopping-picture-empty {
  font-size: 32px;
}

/* A picture across the top of a card in the pictures view: square, the
   whole product shown rather than cropped, on the field color so a picture
   with its own white ground still reads as a tile. */
.shopping-picture-card {
  width: 100%;
  height: auto;
  aspect-ratio: 1;
  border: none;
  border-bottom: 1px solid var(--border);
  border-radius: 0;
  object-fit: contain;
  background: var(--field);
}

.shopping-picture-card.shopping-picture-empty {
  font-size: 48px;
}

.shopping-card .picture-opener {
  width: 100%;
}

/* The tabs of a list and the layout switch on one line, the switch at the
   end; the line under the tabs runs under both. */
.shopping-tabs-row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  border-bottom: 1px solid var(--border);
  margin-bottom: 16px;
}

.shopping-tabs-row .tabs {
  flex: 1;
  min-width: 0;
  border-bottom: none;
  margin-bottom: 0;
}

/* In a narrow pane the tabs need the whole width: the switch goes on a line
   of its own above them, at the end. Measured against the frame rather than
   the window, since the rail beside it takes a share of a tablet's width. */
@container mailbox (max-width: 640px) {
  .shopping-cards {
    grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  }

  .shopping-tabs-row {
    flex-wrap: wrap;
    row-gap: var(--space-2);
  }

  .shopping-tabs-row .tabs {
    flex-basis: 100%;
  }

  .shopping-tabs-row .shopping-view-switch {
    order: -1;
    margin-left: auto;
  }
}

/* The store's list buttons and the layout switch share a line, the switch
   at the end. */
.shopping-view-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  margin-bottom: var(--space-3);
}

.shopping-view-bar .shopping-list-toggles {
  margin-bottom: 0;
}

.shopping-view-bar .shopping-view-switch {
  margin-left: auto;
}

.shopping-view-switch {
  flex: none;
}

/* Two cards side by side on a phone, as many as fit on a desktop, a little
   larger there. */
.shopping-cards {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
  gap: var(--space-3);
  margin: 0;
  padding: 0;
  list-style: none;
}

.shopping-card {
  display: flex;
  flex-direction: column;
  min-width: 0;
  overflow: hidden;
  border: 1px solid var(--border);
  border-radius: var(--radius-2);
  background: var(--surface);
}

.shopping-card-body {
  display: flex;
  flex: 1;
  flex-direction: column;
  gap: var(--space-1);
  padding: var(--space-2) var(--space-3) var(--space-3);
}

.shopping-card-name {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
}

.shopping-card-name input[type='checkbox'] {
  flex: none;
  margin-top: 3px;
}

/* A store and an aisle together can be long: two lines, then cut. */
.shopping-card-group-name {
  display: -webkit-box;
  overflow: hidden;
  color: var(--muted);
  font-size: var(--text-label);
  font-weight: 600;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
}

.shopping-card-words {
  display: flex;
  flex: 1;
  flex-direction: column;
  gap: var(--space-1);
  min-width: 0;
}

.shopping-card-facts {
  color: var(--muted);
  font-size: var(--text-label);
}

/* The buttons sit at the foot of the card, so a row of cards lines its
   buttons up whatever the names above them take. */
.shopping-card-actions {
  justify-content: flex-end;
  margin-top: auto;
  padding-top: var(--space-1);
}

.shopping-card.is-in-cart .shopping-item-description strong {
  color: var(--muted);
  text-decoration: line-through;
}

.shopping-picture-field {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin-bottom: var(--space-3);
}

.shopping-picture-buttons {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}

.shopping-prices {
  margin-top: var(--space-4);
}

/* The item's dialog and the import's are wide, as their tables need: the
   general dialog rule caps every dialog at 420px, which put the item's four
   buttons on two lines and squeezed the price table's headings. On a phone a
   dialog is the whole screen, as everywhere. */
@media (min-width: 641px) {
  .dialog.dialog-wide:has(.shopping-picture-field),
  .dialog.dialog-wide:has(.shopping-import-files) {
    max-width: min(640px, calc(100vw - 32px));
  }
}

/* On a phone the item dialog's own actions (out of stock, remove) take a
   line of their own, so Cancel and Save stay together on the next. */
@media (max-width: 600px) {
  .dialog:has(.shopping-picture-field) .dialog-actions-other {
    flex-basis: 100%;
  }
}

.shopping-price-range {
  margin: 0 0 var(--space-3);
  font-size: var(--text-label);
}

.shopping-prices h4 {
  margin: 0 0 var(--space-2);
}

.shopping-price-chart {
  display: block;
  width: 100%;
  max-width: 360px;
  height: auto;
  margin-bottom: var(--space-3);
}

.shopping-price-chart polyline {
  fill: none;
  stroke: var(--accent);
  stroke-width: 2;
  stroke-linejoin: round;
  stroke-linecap: round;
}

.shopping-price-chart circle {
  fill: var(--accent);
}

.shopping-import-files {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  margin-bottom: var(--space-3);
}

.shopping-import-files button {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
}

.shopping-import-file {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: 2px 4px 2px var(--space-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-2);
  font-size: var(--text-label);
}

.shopping-share-heading {
  margin: var(--space-4) 0 var(--space-2);
}

.shopping-shares {
  margin-bottom: var(--space-3);
}

.shopping-share-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  padding: var(--space-1) 0;
  border-bottom: 1px solid var(--border);
}

.shopping-share-name {
  min-width: 0;
  overflow-wrap: anywhere;
}

/* The lists on the store screen, switched on and off. */
.shopping-list-toggles {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-bottom: var(--space-3);
}

.shopping-list-toggle {
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--muted);
}

.shopping-list-toggle.is-included {
  border-color: var(--text);
  background: var(--sidebar-hover);
  color: var(--text);
}

.tab-count {
  margin-left: var(--space-1);
  font-size: var(--text-label);
  font-variant-numeric: tabular-nums;
  color: var(--muted);
}

.shopping-receipt-head {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  align-items: center;
  gap: var(--space-3);
  margin-bottom: var(--space-3);
}

/* The receipt's line takes the room beside the way back; the buttons go
   under it once the two do not fit on one line. */
.shopping-receipt-head p {
  flex: 1 1 14rem;
  min-width: 0;
  margin: 0;
}

.shopping-receipt-add-to {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  margin-bottom: var(--space-3);
}

.shopping-receipt-matches {
  list-style: none;
  margin: 0;
  padding: 0;
}

.shopping-receipt-matches li {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-1) 0;
}

.shopping-receipt-other {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
}

.shopping-receipt-section {
  margin-top: var(--space-4);
}

.shopping-receipt-section-head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--space-2);
  margin-bottom: var(--space-2);
}
/* "3 of 12", beside the arrows that move through them. */
.lightbox-position {
  flex: 0 0 auto;
  font-size: var(--text-label);
  font-variant-numeric: tabular-nums;
}


/* --- the journal ----------------------------------------------------------
   One page for each day, and the weeks, months and years above the days. The
   person's own words and what TeaNode wrote are set in a serif face, so that
   they read as writing and the rest of the page reads as the dashboard. A
   mood is one of five colors (--mood-1 to --mood-5 in tokens.css) and is
   always said in words as well. */
:root {
  --journal-serif: 'New York', 'Iowan Old Style', Charter, Georgia, serif;
}

/* What each part of the page is made of, with a gap between the cards that
   a card's own bottom margin would double. */
.journal-main,
.journal-aside,
.journal-split-main,
.journal-split-side {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  min-width: 0;
}

.journal-main > .card,
.journal-aside > .card,
.journal-split-main > .card,
.journal-split-side > .card,
.journal-pair > .card {
  margin-bottom: 0;
}

.journal-columns,
.journal-split {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: var(--space-4);
  margin-bottom: var(--space-4);
}

.journal-main,
.journal-split-main {
  flex: 3 1 560px;
}

.journal-aside,
.journal-split-side {
  flex: 2 1 340px;
}

.journal-pair {
  display: flex;
  flex-wrap: wrap;
  /* Each card is as tall as it is: a section that is folded up to its head
     should not be stretched to the height of the one beside it. */
  align-items: flex-start;
  gap: var(--space-4);
}

.journal-pair > * {
  flex: 1 1 300px;
  min-width: 0;
}

/* The paper the person's own words sit on: a step off the card, so the words
   read as a page of their own. */
.journal-paper {
  background: color-mix(in srgb, var(--rail) 55%, var(--surface));
}

.journal-card {
  min-width: 0;
}

.journal-card-flush {
  padding-bottom: var(--space-1);
}

/* Words that can be long (a folder, a subject, a title with no spaces) break
   anywhere rather than push the card wider than the window, and the lines of
   a card give up their width to let them. */
.journal-card,
.journal-day-card,
.journal-day-column,
.journal-month-cell,
.journal-year-month {
  overflow-wrap: anywhere;
}

/* The head of a panel: the title and the buttons share the first line, on a
   phone too, and the sentence under them has the whole width. The
   dashboard's own head drops the action under the title on a phone, which
   is right for a button of words and wrong for a chevron. Buttons too wide
   to share the line (the switch between the touched-up words and the
   original) drop under the title, at the right. */
.journal-card-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  column-gap: var(--space-3);
  row-gap: 4px;
  margin-bottom: var(--space-5);
}

.journal-card-head.is-alone {
  margin-bottom: 0;
}

.journal-card-head h3 {
  flex: 1 1 8rem;
  min-width: 0;
  margin: 0;
}

.journal-card-head-actions {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--space-2);
  margin-left: auto;
  white-space: nowrap;
}

/* The description always takes the line under the title. A basis of 100%
   alone is not enough: the card's paragraphs keep to the reading measure, so
   the capped paragraph still fitted beside the title on a wide card. An empty
   item the full width of the header, ordered before it, ends the title's line;
   the paragraph takes back the row gap the empty line adds. */
.journal-card-head:has(> p)::after {
  content: '';
  flex: 0 0 100%;
  order: 1;
}

.journal-card-head p {
  flex: 0 0 100%;
  order: 2;
  margin: -4px 0 0;
}

/* A part of a panel that is folded until asked for. */
.journal-disclosure {
  margin-top: var(--space-3);
}

.journal-disclosure:first-child {
  margin-top: 0;
}

.journal-disclosure-toggle {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  min-height: 32px;
  padding: 0 var(--space-2) 0 0;
  border: 0;
  border-radius: var(--radius-1);
  background: none;
  color: var(--muted);
  font: inherit;
  font-size: var(--text-label);
  font-weight: 600;
  cursor: pointer;
}

.journal-disclosure-toggle:hover {
  color: var(--text);
}

.journal-details {
  margin-top: var(--space-1);
}

.journal-details > .journal-row:last-of-type {
  padding-bottom: 0;
}

/* The foot of a list that shows its first rows: the line of the row it would
   have been, and the button for the rest. */
.journal-show-all {
  display: flex;
  padding: var(--space-3) 0 var(--space-2);
  border-top: 1px solid var(--border);
}

.journal-show-all .button-quiet {
  font-size: 13px;
}

.journal-moments + .journal-show-all,
.journal-shipped .journal-show-all {
  padding-top: var(--space-2);
  border-top: 0;
}

/* What was done in one project: a panel of its own inside the day's work. */
.journal-projects {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.journal-project {
  min-width: 0;
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--border);
  border-radius: var(--radius-2);
}

.journal-project-name {
  margin: 0;
  font-size: var(--text-title);
  font-weight: 650;
  line-height: 1.35;
}

.journal-project-time {
  margin-top: 2px;
  font-size: var(--text-label);
  font-variant-numeric: tabular-nums;
}

.journal-project-summary {
  max-width: var(--measure);
  margin: var(--space-2) 0 0;
  font-family: var(--journal-serif);
  font-size: 15.5px;
  line-height: 1.6;
}

.journal-project-sessions {
  margin: var(--space-2) 0 0;
  font-size: var(--text-label);
  line-height: 1.5;
}

.journal-shipped {
  margin-top: var(--space-3);
  padding-top: var(--space-2);
  border-top: 1px solid var(--border);
}

.journal-shipped-label {
  margin-bottom: 4px;
  font-size: var(--text-label);
}

.journal-shipped ul {
  list-style: none;
  margin: 0;
  padding: 0;
}

.journal-shipped li {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 2px var(--space-2);
  padding: 5px 0;
  font-size: var(--text-body);
}

.journal-shipped a {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  min-width: 0;
}

.journal-shipped a svg {
  flex: 0 0 auto;
}

.journal-title-with-icon {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
}

.journal-card-foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  margin-top: var(--space-3);
  font-size: var(--text-label);
  color: var(--muted);
}

.journal-card-foot-actions {
  display: inline-flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  margin-left: auto;
}

.journal-card-foot .button-quiet {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  font-size: 13px;
}

.journal-status {
  margin: var(--space-3) 0 0;
  font-size: 13px;
}

/* Writing: the person's words and the narrative. A line break the person
   typed stays one. */
.journal-prose {
  max-width: var(--measure);
  font-family: var(--journal-serif);
  font-size: 17px;
  line-height: 1.7;
}

.journal-prose p {
  margin: 0 0 var(--space-3);
  white-space: pre-line;
}

.journal-prose p:last-child {
  margin-bottom: 0;
}

.journal-narrative {
  font-size: 16.5px;
  line-height: 1.75;
}

/* A mention in the narrative: the record's title, bold in the sentence's own
   face, underlined lightly so it reads as something to open. */
.journal-mention {
  font-weight: 650;
  color: var(--text);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-decoration-color: color-mix(in srgb, var(--text) 30%, transparent);
  text-underline-offset: 3px;
}

.journal-mention:hover {
  text-decoration-color: currentColor;
}

/* A place chip on the week: its name and how many days, in the page's own
   face. */
.journal-chip {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 1px 8px 1px 6px;
  border: 1px solid var(--border);
  border-radius: var(--radius-1);
  background: var(--surface);
  color: var(--text);
  font:
    550 12.5px/1.6 -apple-system,
    BlinkMacSystemFont,
    'Segoe UI',
    Roboto,
    Helvetica,
    Arial,
    sans-serif;
  text-decoration: none;
  vertical-align: 1px;
}

a.journal-chip:hover {
  background: var(--sidebar-hover);
}

.journal-corrections {
  list-style: none;
  margin: var(--space-3) 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.journal-corrections li {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-2);
  background: color-mix(in srgb, var(--leaf) 12%, var(--surface));
  font-size: 13px;
}

.journal-notes {
  margin-top: var(--space-4);
  padding-top: var(--space-3);
  border-top: 1px solid var(--border);
}

.journal-notes-label {
  margin-bottom: var(--space-2);
  font-size: var(--text-label);
  font-weight: 600;
}

.journal-note + .journal-note {
  margin-top: var(--space-3);
}

.journal-note-title {
  margin-bottom: 2px;
  font-weight: 600;
}

/* The original, edited in the writing face and no wider than a line reads. */
/* The version somebody else saved first, above the person's kept draft. */
.journal-rescued {
  margin-bottom: var(--space-3);
  padding: var(--space-3) var(--space-4);
  border: 1px dashed var(--border-strong);
  border-radius: var(--radius-2);
}

.journal-rescued > p:first-child {
  margin: 0 0 var(--space-2);
  font-size: var(--text-label);
}

/* The reading measure holds for the whole form, so its buttons end where
   the text box does rather than at the far edge of a wide card. */
.journal-editor {
  max-width: var(--measure);
}

.journal-editor textarea {
  font-family: var(--journal-serif);
  font-size: 16px;
  line-height: 1.6;
}

.journal-editor > label:first-child {
  display: block;
  margin-bottom: var(--space-2);
  font-size: 12.5px;
  font-weight: 600;
  color: var(--muted);
}

.journal-how {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin-top: var(--space-3);
  font-size: 13px;
  color: var(--muted);
}

.journal-how input {
  flex: 0 1 280px;
}

/* --- the controls at the top of a page ------------------------------------ */
.journal-toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2) var(--space-3);
}

.journal-toolbar-nav {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2) var(--space-3);
}

.journal-stepper {
  display: inline-flex;
  align-items: center;
  gap: 2px;
}

.journal-stepper .icon-button {
  text-decoration: none;
}

.journal-stepper .icon-button:disabled {
  opacity: 0.4;
  cursor: default;
}

.journal-search {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  min-width: 220px;
  padding: 0 var(--space-2) 0 var(--space-3);
  border-radius: var(--radius-2);
  background: var(--field);
  color: var(--muted);
}

.journal-search input {
  flex: 1;
  min-width: 0;
  padding-left: 0;
  border: 0;
  background: transparent;
}

/* --- mood and weather ----------------------------------------------------- */
[data-level='1'] {
  --journal-mood: var(--mood-1);
}
[data-level='2'] {
  --journal-mood: var(--mood-2);
}
[data-level='3'] {
  --journal-mood: var(--mood-3);
}
[data-level='4'] {
  --journal-mood: var(--mood-4);
}
[data-level='5'] {
  --journal-mood: var(--mood-5);
}

.journal-mood-dot {
  display: inline-block;
  width: 10px;
  height: 10px;
  flex: 0 0 auto;
  border-radius: 50%;
  background: var(--journal-mood, var(--sidebar-hover));
}

/* A face is an outline; the day's own is filled with its color. */
.journal-mood-disc {
  fill: none;
}

.journal-mood-face {
  display: inline-flex;
  color: var(--muted);
}

.journal-mood-face[data-filled='true'][data-level] {
  color: var(--accent-text);
}

.journal-mood-face[data-filled='true'][data-level] .journal-mood-disc {
  fill: var(--journal-mood);
  stroke: var(--journal-mood);
}

.journal-glance {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-4);
  margin-bottom: var(--space-4);
}

.journal-glance > .card {
  margin-bottom: 0;
}

.journal-mood-card {
  flex: 3 1 380px;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-5);
}

.journal-mood-faces {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-1);
}

.journal-mood-words {
  flex: 1 1 160px;
  min-width: 0;
}

.journal-field-label {
  margin-bottom: 6px;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--muted);
}

.journal-mood-radios {
  display: flex;
  gap: 2px;
}

/* Nothing moves when a face is chosen: the border is there from the start. */
.journal-mood-pick {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  width: 56px;
  padding: 6px 0;
  border: 1px solid transparent;
  border-radius: 10px;
  background: transparent;
  color: var(--muted);
  font-size: 11.5px;
  font-weight: 550;
  line-height: 1.2;
  cursor: pointer;
}

.journal-mood-pick:hover:not(:disabled) {
  background: var(--sidebar-hover);
  color: var(--text);
}

.journal-mood-pick[aria-checked='true'] {
  background: var(--sidebar-hover);
  border-color: var(--border);
  color: var(--text);
}

.journal-mood-pick[aria-checked='true'] .journal-mood-disc {
  fill: var(--journal-mood);
  stroke: var(--journal-mood);
}

.journal-mood-pick[aria-checked='true'] svg {
  color: var(--accent-text);
}

.journal-mood-pick[aria-checked='true'] svg path {
  stroke: var(--accent-text);
}

.journal-source-tag {
  margin-top: 2px;
}

.journal-clear {
  margin-top: var(--space-1);
  font-size: 12px;
}

.journal-weather-card {
  /* Grows when the two cards stack, so it is as wide as the one above it. */
  flex: 1 1 260px;
  min-width: 0;
  display: flex;
  align-items: center;
  gap: var(--space-4);
}

.journal-weather-icon {
  display: flex;
  color: var(--text);
}

.journal-weather-temperature {
  font-size: 22px;
  font-weight: 650;
  letter-spacing: -0.01em;
  font-variant-numeric: tabular-nums;
}

.journal-weather-words {
  font-size: 13px;
}

.journal-tiles {
  margin-bottom: var(--space-4);
}

.journal-tile {
  padding: var(--space-3) var(--space-4);
}

.journal-tile-label {
  display: flex;
  align-items: center;
  gap: 6px;
}

.journal-tile .tile-value {
  margin-top: 2px;
  font-size: 24px;
}

.journal-tile .tile-detail {
  padding-top: 0;
}

/* --- the lines of a section ----------------------------------------------- */
.journal-row {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  padding: var(--space-3) 0;
  border-top: 1px solid var(--border);
}

.journal-row-note {
  align-items: center;
  justify-content: space-between;
  font-size: 13px;
}

.journal-row-icon {
  flex: 0 0 32px;
  height: 32px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-2);
  background: var(--field);
  color: var(--muted);
}

.journal-row-body {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.journal-row-title {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 2px var(--space-3);
  font-weight: 600;
}

.journal-row-title a {
  text-decoration: none;
}

.journal-row-title a:hover {
  text-decoration: underline;
}

.journal-row-where,
.journal-row-detail,
.journal-row-time {
  font-size: 12.5px;
  font-weight: 400;
}

.journal-row-detail {
  font-size: 14px;
}

.journal-row-time {
  flex: 0 0 auto;
  font-variant-numeric: tabular-nums;
}

/* Why the agent chose something, in a few words. */
.journal-reason {
  align-self: flex-start;
  margin-top: var(--space-1);
  padding: 1px 8px;
  border-radius: var(--radius-1);
  background: color-mix(in srgb, var(--leaf) 12%, var(--surface));
  font-size: 12px;
  font-weight: 550;
}

.journal-avatar {
  flex: 0 0 32px;
  height: 32px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background: var(--muted);
  color: var(--surface);
  font-size: 13px;
  font-weight: 600;
}

.journal-photo-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(118px, 1fr));
  gap: 2px;
  border-radius: var(--radius-2);
  overflow: hidden;
}

.journal-photo {
  position: relative;
  display: block;
  aspect-ratio: 1;
  background: var(--field);
}

.journal-photo img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.journal-photo-time {
  position: absolute;
  left: 6px;
  bottom: 5px;
  font-size: 11px;
  font-weight: 600;
  /* Over a photograph of any color, so it carries its own ground. */
  color: #fff;
  text-shadow: 0 1px 2px rgb(0 0 0 / 45%);
}

.journal-photo-more {
  margin: var(--space-2) 0 0;
  font-size: 12.5px;
}

.journal-recording {
  padding: var(--space-3) 0;
  border-top: 1px solid var(--border);
}

.journal-recording-summary {
  margin: 2px 0 0;
  font-size: 13px;
}

.journal-table-scroll {
  overflow-x: auto;
}

.journal-table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--text-body);
}

.journal-table th {
  padding: var(--space-2);
  text-align: left;
  font-size: var(--text-label);
  font-weight: 600;
  color: var(--muted);
}

.journal-table td {
  padding: 10px var(--space-2);
  border-top: 1px solid var(--border);
}

.journal-table th:first-child,
.journal-table td:first-child {
  padding-left: 0;
  white-space: nowrap;
}

.journal-table th:last-child,
.journal-table td:last-child {
  padding-right: 0;
}

.journal-table td.journal-number,
.journal-table th.journal-number {
  text-align: right;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}

/* --- the column at the right ---------------------------------------------- */
.journal-map-card {
  padding: 0;
  overflow: hidden;
}

.journal-map .location-map {
  height: 230px;
}

.journal-map-body {
  padding: var(--space-3) var(--card-padding-inline) var(--space-2);
}

.journal-map-body .journal-card-head:not(.is-alone) {
  margin-bottom: var(--space-2);
}

.journal-visits {
  list-style: none;
  margin: 0;
  padding: 0;
}

.journal-visits li {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: 7px 0;
  border-top: 1px solid var(--border);
}

.journal-visits li > a {
  flex: 1 1 auto;
  min-width: 0;
}

.journal-visit-number {
  flex: 0 0 20px;
  height: 20px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background: var(--accent);
  color: var(--accent-text);
  font-size: 11px;
  font-weight: 700;
}

.journal-moments {
  list-style: none;
  margin: 0;
  padding: 0;
}

.journal-moment {
  display: flex;
  gap: 10px;
  margin: 0 -6px;
  padding: 6px;
  border-radius: var(--radius-2);
  color: var(--text);
  text-decoration: none;
}

a.journal-moment:hover {
  background: var(--sidebar-hover);
}

.journal-moment-time {
  flex: 0 0 62px;
  padding-top: 2px;
  font-size: 12px;
  text-align: right;
  font-variant-numeric: tabular-nums;
}

.journal-moment-mark {
  flex: 0 0 26px;
  display: flex;
  flex-direction: column;
  align-items: center;
}

.journal-moment-icon {
  width: 26px;
  height: 26px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--border);
  border-radius: 50%;
  background: var(--surface);
  color: var(--muted);
}

.journal-moment-line {
  flex: 1 1 auto;
  width: 1px;
  min-height: 8px;
  background: var(--border);
}

.journal-moments li:last-child .journal-moment-line {
  display: none;
}

/* A moment that can be pinned keeps its row as it was: the link takes the
   width and the pin button sits beside it, at the height of the first line,
   so it covers none of the words and adds nothing to the row. */
.journal-moments li.has-pin-action {
  display: flex;
  align-items: flex-start;
  gap: 4px;
}

.journal-moments li.has-pin-action > .journal-moment {
  flex: 1 1 auto;
  min-width: 0;
  margin-right: 0;
}

.journal-moment-pin {
  flex: 0 0 auto;
  margin-top: 4px;
}

.journal-moment-text {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  padding-bottom: 6px;
}

.journal-moment-title {
  font-size: var(--text-body);
  font-weight: 550;
}

.journal-moment-detail {
  font-size: 12.5px;
}

.journal-moment-day {
  flex: 0 0 40px;
  font-weight: 600;
  text-transform: uppercase;
}

/* Small drawings of numbers: bars of the hours of a day, and of the days of a
   week. They stretch to the card, so the bars have no width of their own. */
.journal-bars {
  display: block;
  width: 100%;
  height: 64px;
  margin-top: 6px;
  fill: var(--leaf);
}

.journal-bars-small {
  height: 56px;
}

.journal-bars-axis {
  display: flex;
  justify-content: space-between;
  font-size: 11px;
}

.journal-bars-labels {
  display: grid;
  margin-top: 2px;
  font-size: 11px;
  text-align: center;
}

.journal-health-block + .journal-health-block {
  margin-top: var(--space-4);
}

.journal-health-label {
  font-size: 13px;
  font-weight: 600;
}

.journal-sleep {
  display: flex;
  height: 14px;
  margin-top: var(--space-2);
  border-radius: 4px;
  overflow: hidden;
}

.journal-sleep-stage {
  flex-basis: 0;
}

.journal-sleep-stage.deep,
.journal-sleep-swatch.deep {
  background: var(--sleep-deep);
}

.journal-sleep-stage.core,
.journal-sleep-swatch.core {
  background: var(--sleep-core);
}

.journal-sleep-stage.rem,
.journal-sleep-swatch.rem {
  background: var(--sleep-rem);
}

.journal-sleep-stage.awake,
.journal-sleep-swatch.awake {
  background: var(--sleep-awake);
}

.journal-sleep-stage.inBed,
.journal-sleep-swatch.inBed {
  background: var(--border-strong);
}

.journal-sleep-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 4px var(--space-4);
  margin-top: var(--space-2);
  font-size: var(--text-label);
}

.journal-sleep-swatch {
  display: inline-block;
  width: 8px;
  height: 8px;
  margin-right: 4px;
  border-radius: 2px;
}

.journal-health-facts {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  margin-top: var(--space-4);
  padding-top: var(--space-3);
  border-top: 1px solid var(--border);
}

.journal-health-facts > div {
  flex: 1 1 140px;
  display: flex;
  flex-direction: column;
}

.journal-event {
  display: flex;
  gap: 10px;
  padding: 9px 0;
  border-top: 1px solid var(--border);
  color: var(--text);
  text-decoration: none;
}

.journal-event-bar {
  flex: 0 0 3px;
  border-radius: 2px;
  background: var(--leaf);
}

/* --- the timeline --------------------------------------------------------- */
.journal-timeline {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: var(--space-6);
}

.journal-timeline-main {
  flex: 3 1 560px;
  min-width: 0;
}

.journal-timeline-aside {
  flex: 1 1 300px;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}

.journal-timeline-aside > .card {
  margin-bottom: 0;
}

.journal-week {
  margin-bottom: 28px;
}

.journal-week-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 4px var(--space-4);
  padding-bottom: 10px;
}

.journal-week-head h2 {
  margin: 0;
  font-size: var(--text-overline);
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--muted);
}

.journal-week-summary {
  margin: 0 0 var(--space-3);
  padding: var(--space-3) var(--space-4);
  border-radius: 10px;
  background: color-mix(in srgb, var(--leaf) 12%, var(--surface));
  font-family: var(--journal-serif);
  font-size: 15.5px;
  line-height: 1.6;
}

.journal-day-cards {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

/* A day. The headline is the link, stretched over the whole card, and the
   one button inside it sits above that. */
.journal-day-card {
  position: relative;
  display: flex;
  gap: 18px;
  padding: var(--space-4) var(--space-5);
  border: 1px solid var(--border);
  border-radius: var(--radius-3);
  background: var(--surface);
  color: var(--text);
}

.journal-day-card:hover {
  border-color: var(--border-strong);
}

.journal-day-card.is-today {
  border-color: var(--leaf);
  border-style: dashed;
}

.journal-day-link {
  color: inherit;
  text-decoration: none;
}

.journal-day-link::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: var(--radius-3);
}

.journal-day-link:focus-visible::after {
  outline: 2px solid var(--text);
}

.journal-date-block {
  flex: 0 0 56px;
  text-align: center;
}

.journal-date-weekday {
  display: block;
  font-size: 12px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.journal-date-number {
  font-size: 30px;
  font-weight: 650;
  line-height: 1.1;
  letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
}

.journal-mood-badge {
  display: inline-flex;
  margin-top: 6px;
}

.journal-day-body {
  flex: 1 1 auto;
  min-width: 0;
}

.journal-today-label {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-bottom: 4px;
  font-size: 12px;
  font-weight: 600;
  color: var(--leaf);
}

.journal-today-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--leaf);
}

.journal-day-headline {
  margin: 0;
  font-family: var(--journal-serif);
  font-size: 18px;
  font-weight: 500;
  line-height: 1.45;
}

.journal-day-headline.is-numbers {
  font-family: inherit;
  font-size: var(--text-title);
  font-weight: 550;
  color: var(--muted);
}

.journal-excerpt {
  margin: 6px 0 0;
  padding-left: 10px;
  border-left: 2px solid var(--border-strong);
  font-family: var(--journal-serif);
  font-size: 14.5px;
  line-height: 1.55;
  color: var(--muted);
}

.journal-add-words {
  position: relative;
  margin-top: var(--space-2);
}

.journal-add-words .button {
  padding: 4px 10px;
  font-size: 13px;
}

.journal-day-photos {
  display: flex;
  gap: 3px;
  margin-top: var(--space-3);
}

.journal-day-photos img,
.journal-day-photos-more {
  width: 64px;
  height: 64px;
  border-radius: var(--radius-1);
  object-fit: cover;
  background: var(--field);
}

.journal-day-photos-more {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--muted);
  font-size: 13px;
  font-weight: 600;
}

.journal-facts {
  display: flex;
  flex-wrap: wrap;
  gap: 6px var(--space-4);
  margin-top: var(--space-3);
}

.journal-fact {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: 12.5px;
  color: var(--muted);
  white-space: nowrap;
}

.journal-list-foot {
  padding-left: 2px;
  padding-right: 2px;
}

.journal-pager {
  display: inline-flex;
  gap: var(--space-2);
}

.journal-pager button {
  padding: 4px 10px;
  font-size: 13px;
}

.journal-month-steps {
  display: inline-flex;
}

.journal-month-grid {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  gap: 4px;
}

.journal-month-weekdays {
  margin-bottom: 4px;
  font-size: 11px;
  text-align: center;
}

.journal-month-day {
  display: flex;
  align-items: center;
  justify-content: center;
  aspect-ratio: 1;
  border-radius: var(--radius-1);
  background: var(--sidebar-hover);
  color: var(--muted);
  font-size: 11.5px;
  font-weight: 600;
  text-decoration: none;
}

.journal-month-day[data-level] {
  background: var(--journal-mood);
  color: var(--accent-text);
}

.journal-month-day.is-today {
  outline: 2px solid var(--leaf);
  outline-offset: 1px;
}

.journal-month-day.is-future {
  background: transparent;
}

.journal-legend {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px var(--space-3);
  margin-top: var(--space-3);
  font-size: 11.5px;
}

/* The five moods are one unit: when the legend wraps, they go below the
   sentence together, and do not break one word to a line. */
.journal-legend-sentence {
  flex: 1 1 100%;
}

.journal-legend-items {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px var(--space-3);
}

.journal-legend-item {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  white-space: nowrap;
}

.journal-year-ago {
  display: flex;
  gap: var(--space-3);
}

.journal-year-ago img {
  flex: 0 0 72px;
  width: 72px;
  height: 72px;
  border-radius: var(--radius-2);
  object-fit: cover;
}

.journal-year-ago .journal-prose {
  font-size: 15px;
  line-height: 1.55;
}

.journal-numbers {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 7px var(--space-4);
  margin: 0;
  font-size: var(--text-body);
}

/* A row of the list is a pair of its own, so the pair can be a keyed
   element: the grid still lays the words in two columns. */
.journal-numbers > div {
  display: contents;
}

.journal-numbers dd {
  margin: 0;
}

.journal-hits {
  list-style: none;
  margin: 0;
  padding: 0;
}

.journal-hits li + li {
  border-top: 1px solid var(--border);
}

.journal-hits a {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: var(--space-3) 0;
  text-decoration: none;
}

.journal-hit-title {
  font-weight: 600;
}

.journal-hit-headline {
  font-family: var(--journal-serif);
}

.journal-hit-snippet {
  font-size: 13px;
}

.journal-evening-time {
  display: flex;
  align-items: flex-end;
  gap: var(--space-3);
  margin: var(--space-2) 0 var(--space-3);
}

/* --- a week --------------------------------------------------------------- */
/* Seven days in one row where they fit, and in rows of two or three below
   that: never six with the seventh alone underneath. */
.journal-week-days {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  gap: 10px;
  margin-bottom: var(--space-4);
}

@media (max-width: 1100px) {
  .journal-week-days {
    grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  }
}

.journal-day-column {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  min-width: 0;
  padding: 14px;
  border: 1px solid var(--border);
  border-radius: var(--radius-3);
  background: var(--surface);
  color: var(--text);
  text-decoration: none;
}

a.journal-day-column:hover {
  border-color: var(--border-strong);
}

.journal-day-column.is-future {
  background: var(--rail);
  min-height: 80px;
}

.journal-day-column-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.journal-day-column-number {
  display: block;
  font-size: 22px;
  font-weight: 650;
  font-variant-numeric: tabular-nums;
}

.journal-day-column-photo {
  display: block;
  width: 100%;
  height: 86px;
  border-radius: var(--radius-2);
  object-fit: cover;
}

.journal-day-column-weather {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 12.5px;
}

.journal-day-column-headline {
  font-size: 14.5px;
  line-height: 1.45;
}

.journal-week-numbers {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: var(--space-3);
  margin-bottom: var(--space-4);
}

.journal-week-numbers > .card {
  margin-bottom: 0;
}

.journal-chart-card {
  padding: var(--space-4) 18px;
}

.journal-chart-value {
  font-size: 24px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

.journal-chart-value .muted {
  font-size: 13px;
  font-weight: 500;
}

.journal-chart-sleep .journal-bars {
  fill: var(--sleep-core);
}

.journal-chart-work .journal-bars {
  fill: var(--accent);
}

.journal-chart-spent .journal-bars {
  fill: var(--warn);
}

.journal-chart-detail {
  margin-top: 6px;
  font-size: var(--text-label);
}

.journal-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

/* --- a month -------------------------------------------------------------- */
.journal-month-table {
  border: 1px solid var(--border);
  border-radius: var(--radius-3);
  overflow: hidden;
  background: var(--border);
}

.journal-month-head,
.journal-month-cells {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  gap: 1px;
}

.journal-month-head > div {
  padding: var(--space-2) 10px;
  background: var(--rail);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.journal-month-head {
  margin-bottom: 1px;
}

.journal-month-cell {
  display: flex;
  flex-direction: column;
  min-width: 0;
  min-height: 96px;
  background: var(--surface);
  color: var(--text);
  text-decoration: none;
}

.journal-month-cell.is-outside,
.journal-month-cell.is-future {
  background: var(--rail);
}

a.journal-month-cell:hover {
  background: var(--sidebar-hover);
}

.journal-month-photo {
  display: block;
  width: 100%;
  height: 58px;
  object-fit: cover;
}

.journal-month-cell-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 6px var(--space-2) 0;
}

.journal-month-number {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 22px;
  height: 22px;
  border-radius: 11px;
  font-size: 13px;
  font-weight: 650;
  font-variant-numeric: tabular-nums;
}

.journal-month-number.is-today {
  background: var(--accent);
  color: var(--accent-text);
}

.journal-month-cell-marks {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: 11.5px;
}

/* The space below is a margin, not padding: a clamped box clips at its
   padding edge, and padding below showed the top of the next line. */
.journal-month-headline {
  margin: 2px var(--space-2) var(--space-2);
  font-size: 12px;
  line-height: 1.35;
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.journal-mood-bar {
  display: flex;
  height: 14px;
  border-radius: 4px;
  overflow: hidden;
}

.journal-mood-bar > span {
  flex-basis: 0;
  background: var(--journal-mood);
}

.journal-mood-bar-labels {
  display: flex;
  justify-content: space-between;
  gap: var(--space-2);
  margin: 6px 0 var(--space-4);
  font-size: var(--text-label);
}

.journal-chapter {
  display: flex;
  gap: var(--space-3);
  padding: 10px 0;
  border-top: 1px solid var(--border);
  color: var(--text);
  text-decoration: none;
}

/* The year along a line, each chapter as long as it lasted. */
.journal-chapter-strip {
  position: relative;
  height: 34px;
  margin-bottom: var(--space-2);
  border-radius: var(--radius-2);
  background: var(--rail);
  overflow: hidden;
}

.journal-chapter-strip > span {
  position: absolute;
  top: 5px;
  bottom: 5px;
  border-radius: 5px;
  background: var(--mood-2);
}

.journal-chapter-strip > span[data-index='1'] {
  background: var(--mood-3);
}

.journal-chapter-strip > span[data-index='2'] {
  background: var(--mood-5);
}

.journal-chapter-strip > span[data-index='3'] {
  background: var(--mood-4);
}

.journal-chapter-months {
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  margin-bottom: var(--space-2);
  font-size: 11px;
}

.journal-chapter-swatch {
  flex: 0 0 12px;
  height: 12px;
  margin-top: 5px;
  border-radius: 3px;
  background: var(--mood-2);
}

.journal-chapter-swatch[data-index='1'] {
  background: var(--mood-3);
}

.journal-chapter-swatch[data-index='2'] {
  background: var(--mood-5);
}

.journal-chapter-swatch[data-index='3'] {
  background: var(--mood-4);
}

/* --- a year --------------------------------------------------------------- */
.journal-year-months {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  gap: var(--space-3);
  margin-bottom: var(--space-4);
}

.journal-year-month {
  display: block;
  padding: var(--space-4);
  border: 1px solid var(--border);
  border-radius: var(--radius-3);
  background: var(--surface);
  color: var(--text);
  text-decoration: none;
}

.journal-year-month.is-current {
  border-color: var(--leaf);
}

.journal-year-month.is-future {
  background: var(--rail);
  color: var(--muted);
}

a.journal-year-month:hover {
  border-color: var(--border-strong);
}

.journal-year-month-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  margin-bottom: var(--space-2);
  font-size: var(--text-body);
}

.journal-year-month-head .muted {
  font-size: var(--text-label);
}

.journal-year-days {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  gap: 3px;
}

.journal-year-day {
  aspect-ratio: 1;
  border-radius: 3px;
  background: var(--sidebar-hover);
}

.journal-year-day[data-level] {
  background: var(--journal-mood);
}

.journal-year-headline {
  display: block;
  margin-top: 10px;
  font-size: 14px;
  line-height: 1.45;
}

/* A phone: the cards stack (their widths are minimums), a month keeps its
   seven columns and drops what no longer fits in them, and the controls at
   the top wrap. */
@media (max-width: 600px) {
  /* The zoom control is one line of four equal parts; under it the steps and
     the way out to the timeline share a line, one at each end. */
  .journal-toolbar {
    flex-direction: column;
    align-items: stretch;
    gap: var(--space-2);
  }

  .journal-zoom {
    display: flex;
  }

  .journal-zoom > a {
    flex: 1 1 0;
    justify-content: center;
    padding: 7px 4px;
    font-size: 13px;
  }

  .journal-toolbar-nav {
    flex-wrap: nowrap;
    justify-content: space-between;
  }

  /* Two tiles to a row, and an odd one out takes the whole row, so five
     numbers are three short rows and not a screen of one. */
  .journal-tiles {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--space-2);
  }

  .journal-tiles > .tile:last-child:nth-child(odd) {
    grid-column: 1 / -1;
  }

  .journal-tile {
    padding: var(--space-3);
  }

  .journal-tile .tile-value {
    font-size: 20px;
  }

  .journal-tile .tile-detail {
    overflow-wrap: anywhere;
  }

  .journal-card-head-actions {
    gap: var(--space-1);
  }

  /* On a phone the card pads its own sides, the map included, so the text
     under the map lines up with every other section without more. */
  .journal-map-body {
    padding-inline: 0;
  }

  .journal-day-card {
    gap: var(--space-3);
    padding: var(--space-3) var(--space-4);
  }

  .journal-date-block {
    flex-basis: 44px;
  }

  .journal-date-number {
    font-size: 24px;
  }

  .journal-month-cell {
    min-height: 44px;
  }

  .journal-month-photo,
  .journal-month-headline,
  .journal-month-high {
    display: none;
  }

  /* The cells keep only the mood here, which the legend's colors say. */
  .journal-legend-sentence {
    display: none;
  }

  .journal-month-cell-head {
    flex-direction: column;
    gap: 4px;
    padding: 6px 2px;
  }

  .journal-month-head > div {
    padding: var(--space-1) 2px;
    text-align: center;
    font-size: 11px;
    letter-spacing: 0;
  }

  .journal-mood-pick {
    width: 52px;
  }

  .journal-moment-time {
    flex-basis: 52px;
  }

  .journal-search {
    flex: 1 1 100%;
  }
}

