/* jam — white mail on a cool grey page, Jelly's colours. See DESIGN.md.

   Loaded after rastrillo's tokens.css, so the :root blocks below win
   at equal specificity and every ui partial (fields, pills, buttons)
   takes the warm palette without being touched. */

/* ── Tokens ────────────────────────────────────────────────────────── */
/* The palette is Jelly's: cool neutral greys, strawberry as the one
   primary, blueberry for the team's notes, cherry for danger, and the
   fruit scales for labels — all at Jelly's HSL steps. Messages are
   white; the page around them is a shade darker, never beige. */
:root,
:root[data-theme="light"] {
  color-scheme: light;
  --rst-font: "Source Sans 3", "Helvetica Neue", Arial, sans-serif;
  --jam-serif: "Source Serif 4", Georgia, "Times New Roman", serif;
  /* The same serif, measured as the sans is, for the three headings
     that stand in one row across the app — the team name, the list's
     heading and the conversation's subject — and so must share a
     baseline. layout.html says how and why. */
  --jam-serif-head: "Source Serif 4 Head", "Source Serif 4", Georgia, "Times New Roman", serif;
  --rst-fs-base: 0.9375rem;
  --rst-fs-sm: 0.8125rem;
  --rst-fs-xs: 0.75rem;
  /* The framework paints with var(--rst-radius*) but never defines
     them, so every consumer fell back to square. Jam's corners. */
  --rst-radius: 12px;
  --rst-radius-sm: 8px;
  --rst-radius-pill: 999px;

  --rst-bg: hsl(240 5% 98.5%);          /* the page: barely off white */
  --rst-surface: #ffffff;               /* a message */
  --rst-surface-2: hsl(240 5% 95.5%);
  --rst-line: hsl(240 5% 90%);
  --rst-line-strong: hsl(240 5% 65%);
  --rst-text: hsl(240 5% 15%);          /* gray-800 */
  --rst-text-muted: hsl(240 5% 38%);
  --rst-text-faint: hsl(240 5% 48%);

  --rst-accent: hsl(342 75% 55%);       /* strawberry-600 */
  --rst-accent-strong: hsl(342 70% 46%); /* strawberry-700 */
  --rst-accent-soft: hsl(342 95% 95%);  /* strawberry-50 */
  --rst-on-accent: #ffffff;

  --rst-tone-neutral-fg: hsl(240 5% 25%);  --rst-tone-neutral-bg: hsl(240 5% 92%);
  --rst-tone-positive-fg: hsl(135 88% 26%); --rst-tone-positive-bg: hsl(135 90% 92%);
  --rst-tone-warning-fg: hsl(45 88% 26%);   --rst-tone-warning-bg: hsl(45 90% 92%);
  --rst-tone-negative-fg: hsl(2 70% 46%);   --rst-tone-negative-bg: hsl(2 90% 92%);
  --rst-shadow-pop: 0 10px 28px hsl(240 5% 12% / 0.14);

  --jam-note: hsl(205 90% 93%);
  --jam-note-well: hsl(205 85% 97%);
  --jam-note-mid: hsl(205 60% 62%);   /* the editor field inside the note card */         /* blueberry-100: a team note */
  --jam-note-line: hsl(205 80% 80%);
  --jam-note-ink: hsl(205 88% 22%);
  --jam-out: hsl(342 95% 98.5%);        /* your own mail: the faintest strawberry */
  --jam-av-bg: hsl(240 5% 92%);
  --jam-av-ink: hsl(240 5% 30%);
  --jam-shadow-sheet: 0 1px 2px hsl(240 5% 12% / 0.08), 0 3px 8px hsl(240 5% 12% / 0.07), 0 14px 32px -16px hsl(240 5% 12% / 0.35);
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    color-scheme: dark;
    --rst-bg: hsl(240 5% 6%); --rst-surface: hsl(240 5% 12%); --rst-surface-2: hsl(240 5% 10%);
    --rst-line: hsl(240 5% 19%); --rst-line-strong: hsl(240 5% 40%);
    --rst-text: hsl(240 5% 95%); --rst-text-muted: hsl(240 5% 72%); --rst-text-faint: hsl(240 5% 62%);
    --rst-accent: hsl(342 80% 65%); --rst-accent-strong: hsl(342 80% 72%); --rst-accent-soft: hsl(342 88% 17%); --rst-on-accent: #ffffff;
    --rst-tone-neutral-fg: hsl(240 5% 85%); --rst-tone-neutral-bg: hsl(240 5% 20%);
    --rst-tone-positive-fg: hsl(135 80% 72%); --rst-tone-positive-bg: hsl(135 88% 14%);
    --rst-tone-warning-fg: hsl(45 80% 72%);   --rst-tone-warning-bg: hsl(45 88% 14%);
    --rst-tone-negative-fg: hsl(2 80% 72%);   --rst-tone-negative-bg: hsl(2 88% 17%);
    --rst-shadow-pop: 0 10px 28px rgba(0, 0, 0, 0.55);
    --jam-note: hsl(205 88% 15%); --jam-note-well: hsl(205 60% 10%);
  --jam-note-mid: hsl(205 45% 45%); --jam-note-line: hsl(205 80% 30%); --jam-note-ink: hsl(205 85% 88%);
    --jam-out: hsl(342 18% 14%);
    --jam-av-bg: hsl(240 5% 22%); --jam-av-ink: hsl(240 5% 88%);
    --jam-shadow-sheet: 0 1px 2px rgba(0, 0, 0, 0.4), 0 3px 8px rgba(0, 0, 0, 0.3), 0 14px 32px -16px rgba(0, 0, 0, 0.8);
  }
}
:root[data-theme="dark"] {
  color-scheme: dark;
  --rst-bg: hsl(240 5% 6%); --rst-surface: hsl(240 5% 12%); --rst-surface-2: hsl(240 5% 10%);
  --rst-line: hsl(240 5% 19%); --rst-line-strong: hsl(240 5% 40%);
  --rst-text: hsl(240 5% 95%); --rst-text-muted: hsl(240 5% 72%); --rst-text-faint: hsl(240 5% 62%);
  --rst-accent: hsl(342 80% 65%); --rst-accent-strong: hsl(342 80% 72%); --rst-accent-soft: hsl(342 88% 17%); --rst-on-accent: #ffffff;
  --rst-tone-neutral-fg: hsl(240 5% 85%); --rst-tone-neutral-bg: hsl(240 5% 20%);
  --rst-tone-positive-fg: hsl(135 80% 72%); --rst-tone-positive-bg: hsl(135 88% 14%);
  --rst-tone-warning-fg: hsl(45 80% 72%);   --rst-tone-warning-bg: hsl(45 88% 14%);
  --rst-tone-negative-fg: hsl(2 80% 72%);   --rst-tone-negative-bg: hsl(2 88% 17%);
  --rst-shadow-pop: 0 10px 28px rgba(0, 0, 0, 0.55);
  --jam-note: hsl(205 88% 15%); --jam-note-well: hsl(205 60% 10%);
  --jam-note-mid: hsl(205 45% 45%); --jam-note-line: hsl(205 80% 30%); --jam-note-ink: hsl(205 85% 88%);
  --jam-out: hsl(342 18% 14%);
  --jam-av-bg: hsl(240 5% 22%); --jam-av-ink: hsl(240 5% 88%);
  --jam-shadow-sheet: 0 1px 2px rgba(0, 0, 0, 0.4), 0 3px 8px rgba(0, 0, 0, 0.3), 0 14px 32px -16px rgba(0, 0, 0, 0.8);
}

/* ── Base: the browser surfaces carry the palette too ─────────────── */
html { font-size: 16px; }
/* Buttons are pills, matching the segmented nav the app settled on:
   the framework's buttons keep their padding and weights, only the
   radius is the app's. Icon tiles (toolbar, more-buttons) stay square. */
.rst-btn, [rst-btn] { border-radius: 999px; }
/* Inputs share the editors' gentle corner (the search stays a pill, and
   a row's inline input is a rule under the words, not a box — give that
   one a radius and the line curls up at both ends). */
.rst-input, [rst-input], .rst-textarea, [rst-textarea],
.jam-settings input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not([type="file"]):not(.jam-setlist__rowinput),
.jam-settings select, .jam-settings textarea,
.jam-line select, .jam-compose textarea, .jam-compose-note textarea,
.jam-person__card input:not([type="checkbox"]):not([type="radio"]),
.jam-person__card select, .jam-person__card textarea,
.jam-editor-dialog input[type="url"] { border-radius: 10px; }
/* Jam's type scale: the v2 framework tokens default to a 14px base,
   which is too small for an app people read mail in all day. This
   loads after tokens.css, so the override is the app's to keep. */
:root {
  --rst-fs-base: 1rem;      /* 16px */
  --rst-fs-sm: 0.875rem;    /* 14px — the floor for component text */
  --rst-fs-xs: 0.75rem;     /* 12px — hints, fine print, caps only */
}
/* A phone browser grows the type inside a block it thinks is wider
   than the screen, one block at a time — which is why one row's
   subject came out a size larger than the row above it while the
   stylesheet said the two were identical, and why its preview grew
   with it. The page picks its own sizes at every width, including the
   smaller root size a phone reads at (see "Type on a phone"), so the
   guess is turned off rather than fought. */
html { -webkit-text-size-adjust: 100%; text-size-adjust: 100%; }
body { font-family: var(--rst-font); font-size: var(--rst-fs-base); line-height: 1.55; -webkit-font-smoothing: antialiased; }
::selection { background: var(--rst-accent-soft); color: var(--rst-text); }
:focus-visible { outline: 2px solid var(--rst-accent); outline-offset: 2px; }
* { scrollbar-width: thin; scrollbar-color: var(--rst-line-strong) transparent; }
input, textarea, select, button { font-family: inherit; caret-color: var(--rst-accent); }
a { color: var(--rst-accent-strong); text-decoration-thickness: 1px; text-underline-offset: 0.15em; }
.jam-serif { font-family: var(--jam-serif); }
.jam-muted { color: var(--rst-text-muted); }
.jam-count { font-size: var(--rst-fs-sm); color: var(--rst-text-faint); font-weight: 500; font-variant-numeric: tabular-nums; }

/* ── Top bar ───────────────────────────────────────────────────────── */
.jam-top { display: flex; align-items: center; gap: 1.25rem; padding: 0.6rem 1.5rem; border-bottom: 1px solid var(--rst-line); background: var(--rst-bg); }
.jam-top .jam-search { flex: 1; max-width: 22rem; margin: 0 0 0 auto; }
.jam-top__compose { white-space: nowrap; }
/* The header's own drawings, on the app's 16 grid: the presentation
   is a class rather than eight attributes repeated on every page, and
   the page then sends the path and nothing else. */
.jam-topicon { fill: none; stroke: currentColor; stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; }
/* The compose button's drawing rides beside its words on a wide
   screen and stands alone on a phone, where the words are its name.
   Hidden wide: three words and a pencil say the same thing twice. */
.jam-top__compose-mark { display: none; }
/* The search button: the same square as the views button beside it,
   and the same two states — quiet, then lit while the box is open. */
.jam-findbtn { display: none; align-items: center; justify-content: center; width: 2.25rem; height: 2.25rem; border-radius: 8px; color: var(--rst-text-muted); cursor: pointer; }
.jam-findbtn:hover { background: var(--rst-surface-2); color: var(--rst-text); }
body:has(#jam-find:checked) .jam-findbtn { background: var(--rst-accent-soft); color: var(--rst-accent-strong); }
.jam-menu { position: relative; }
.jam-menu summary { list-style: none; display: inline-flex; align-items: center; gap: 0.35rem; cursor: pointer; padding: 0.2rem 0.4rem 0.2rem 0.2rem; border-radius: 999px; }
.jam-menu summary::-webkit-details-marker { display: none; }
.jam-menu summary:hover, .jam-menu[open] summary { background: var(--rst-surface-2); }
.jam-menu__caret { color: var(--rst-text-faint); font-size: 0.7rem; }
.jam-menu__panel { position: absolute; right: 0; top: calc(100% + 0.4rem); z-index: 30; min-width: 14rem; background: var(--rst-surface); border: 1px solid var(--rst-line); border-radius: 12px; padding: 0.5rem; box-shadow: var(--rst-shadow-pop); display: grid; }
.jam-menu__who { margin: 0.25rem 0.6rem 0.5rem; padding-bottom: 0.5rem; border-bottom: 1px solid var(--rst-line); font-size: var(--rst-fs-sm); font-weight: 600; }
.jam-menu__who .jam-muted { font-weight: 400; }
.jam-menu__panel form { display: contents; }
/* Availability lives in the account menu: four rows, the current one
   ticked, and the avatar wears an amber ring while you are away. */
.jam-menu__label { margin: 0.35rem 0.6rem 0.1rem; font-size: var(--rst-fs-xs); color: var(--rst-text-muted); text-transform: uppercase; letter-spacing: 0.04em; }
/* Availability is a submenu: the summary names the current status with
   its icon; open, four rows of icon + words, the current one ticked. */
.jam-menu__sub > summary { list-style: none; cursor: pointer; }
.jam-menu__sub > summary::-webkit-details-marker { display: none; }
.jam-menu__subsum:hover, .jam-menu__sub[open] > .jam-menu__subsum { background: var(--rst-surface-2); }
.jam-menu__subsum > svg:first-child { width: 1rem; height: 1rem; flex: none; color: var(--rst-tone-positive-fg); }
.jam-menu__subsum.is-away > svg:first-child { color: var(--rst-tone-warning-fg); }
.jam-menu__subsum > span { flex: 1; min-width: 0; }
.jam-menu__subsum .jam-chev { color: var(--rst-text-faint); transition: transform 140ms; }
.jam-menu__sub[open] > .jam-menu__subsum .jam-chev { transform: rotate(180deg); }
.jam-menu__subbody { padding: 0.15rem 0 0.25rem 0.9rem; }
.jam-menu__subbody form { display: contents; }
.jam-menu__panel .jam-menu__avbtn { padding: 0.4rem 0.6rem; font-size: var(--rst-fs-sm); }
.jam-menu__avbtn > svg { width: 1rem; height: 1rem; flex: none; color: var(--rst-text-muted); }
.jam-menu__avbtn > span { flex: 1; }
.jam-menu__panel .jam-menu__avbtn[aria-checked="true"] { font-weight: 600; }
.jam-menu__panel .jam-menu__avbtn[aria-checked="true"]::after { content: "✓"; color: var(--rst-accent-strong); font-weight: 600; }
.jam-menu__avbtn--available[aria-checked="true"] > svg { color: var(--rst-tone-positive-fg); }
.jam-menu__avbtn:not(.jam-menu__avbtn--available)[aria-checked="true"] > svg { color: var(--rst-tone-warning-fg); }
.jam-menu__panel hr { border: 0; border-top: 1px solid var(--rst-line); margin: 0.35rem 0; }
.jam-menu--away > summary .jam-av { box-shadow: 0 0 0 2px var(--rst-surface), 0 0 0 4px var(--rst-tone-warning-fg); }
.jam-menu__away { font-size: var(--rst-fs-sm); color: var(--rst-tone-warning-fg); font-weight: 600; }
/* The brand: the mascot beside the logotype, at the top left of every
   screen. Two elements wear it — the link home and, at a narrow
   viewport, the label that opens the menu — so everything about the
   drawing is written once, against both.

   The mascot is an <img> because it has its own colours; the logotype
   is a mask over currentColor because it has one, and one file then
   reads on the light theme and the dark one. A mask has no intrinsic
   size, so the box states the logotype's own proportions (1410 × 564,
   so 2.5 : 1) — get that wrong and the letterforms squash rather than
   overflow, which is the kind of wrong nothing reports.

   The two are centred against each other rather than sharing a
   height: the mascot is a drawing with air round it and the logotype
   is a line of letters, so the sizes are picked by eye — 1.9rem of
   mascot beside 1.3rem of logotype reads as one mark at header
   size. */
.jam-brand, .jam-brandbtn { display: inline-flex; align-items: center; gap: 0.5rem; flex: none; text-decoration: none; color: var(--rst-text); }
.jam-brand__mascot { display: block; width: auto; height: 1.9rem; }
.jam-brand__word { display: block; width: 3.25rem; height: 1.3rem; background: currentColor; mask: var(--jam-wordmark) center / contain no-repeat; }
/* The menu button is a button, so it takes a press: the same drawing
   in a target big enough to hit, lit while the menu is open. Hidden
   until a viewport asks for it (see "Narrow screens" and the fold). */
.jam-brandbtn { padding: 0.2rem 0.45rem; margin: -0.2rem -0.45rem; border-radius: 999px; cursor: pointer; }
.jam-brandbtn:hover { background: var(--rst-surface-2); }
body:has(#jam-menu:checked) .jam-brandbtn { background: var(--rst-accent-soft); color: var(--rst-accent-strong); }
.jam-nav { display: flex; gap: 0.15rem; }
.jam-nav a { text-decoration: none; color: var(--rst-text-muted); padding: 0.3rem 0.65rem; border-radius: 999px; font-weight: 500; }
.jam-nav a:hover { color: var(--rst-text); background: var(--rst-surface-2); }
.jam-nav a[aria-current="page"] { color: var(--rst-accent-strong); background: var(--rst-accent-soft); }
.jam-bell { display: inline-block; min-width: 1.15rem; padding: 0 0.3rem; border-radius: 999px; background: var(--rst-accent); color: var(--rst-on-accent); font-size: 0.68rem; text-align: center; line-height: 1.15rem; vertical-align: 1px; margin-left: 0.15rem; }

/* ── Public pages (sign-in, invitation, 404) ───────────────────────── */
.jam-auth { max-width: 26rem; margin: 4rem auto 0; }
.jam-auth__title { font-weight: 600; font-size: 1.9rem; margin: 0 0 0.75rem; text-wrap: balance; letter-spacing: -0.01em; }
.jam-auth__lede { color: var(--rst-text-muted); margin: 0 0 1.25rem; }
.jam-auth__google { display: inline-block; width: 100%; box-sizing: border-box; text-align: center; }
.jam-auth__or { text-align: center; color: var(--rst-text-faint); margin: 0.75rem 0; font-size: var(--rst-fs-sm); }
.jam-auth__note { font-size: var(--rst-fs-sm); color: var(--rst-text-muted); }
.jam-recovery { margin-top: 1.5rem; }
.jam-recovery summary { cursor: pointer; color: var(--rst-text-muted); }

/* ── Settings-ish pages ────────────────────────────────────────────── */
/* ── Settings ──────────────────────────────────────────────────────── */
/* One framework for every settings page: a section is a heading, a
   lede, then either a form or a list. A list row is lead · main ·
   side; a row that can be edited is a details whose summary is the
   row and whose body is the editor, so nothing needs a script and
   nothing leaves the page. Adding is the same editor behind "＋". */
.jam-settings { min-width: 0; }
.jam-settings input:not([type="checkbox"]):not([type="radio"]), .jam-settings select, .jam-settings textarea { box-sizing: border-box; max-width: 100%; }
/* A fact is often a URL or a token with no space in it: it wraps
   rather than pushing the whole page sideways on a phone. */
.jam-settings [rst-detail] dd { min-width: 0; overflow-wrap: anywhere; }
/* A fieldset in a settings form is a group with a small-caps legend,
   not the browser's engraved box: the webhook and token editors were
   drawing the default border and inset legend, which only the rule
   editor had reset. The framework's own groups (choice cards, date
   ranges) carry their own reset and are left to it. */
.jam-settings fieldset:not([rst-choice], [rst-field-range]) { border: 0; padding: 0; margin: 0; min-width: 0; }
.jam-settings fieldset:not([rst-choice], [rst-field-range]) > legend { padding: 0; }
.jam-setlist__title { display: inline-flex; align-items: center; gap: 0.5rem; flex-wrap: wrap; }
.jam-lede { margin: 0 0 1rem; color: var(--rst-text-muted); font-size: var(--rst-fs-sm); max-width: 60ch; }
/* The line under a control is prose and takes a measure, the way the
   lede above it does. Uncapped it ran the form's whole 44rem, which at
   the fine-print size is past a hundred characters — a sentence nobody
   finishes because the eye loses the line coming back. */
.jam-settings [rst-field-help], .jam-settings [rst-field-error] { max-width: 62ch; }
.jam-fine { color: var(--rst-text-faint); font-size: var(--rst-fs-xs); margin: 0.75rem 0 0; }
/* Forms follow the framework's: rst-form owns the block rhythm,
   rst-field-row puts two fields side by side, rst-form-foot closes
   with the actions — the primary first, the destructive one alone at
   the far right as a quiet red link. What is left here is the two
   places a form lives inside a card of ours (a setlist editor, a
   person card): the box-sizing floor, and the release of rst-form's
   44rem cap so a form fills the card it was given. */
.jam-setlist__editor, .jam-person__card { min-width: 0; }
.jam-setlist__editor input:not([type="checkbox"]):not([type="radio"]), .jam-setlist__editor select, .jam-setlist__editor textarea,
.jam-person__card input:not([type="checkbox"]):not([type="radio"]), .jam-person__card select, .jam-person__card textarea { box-sizing: border-box; width: 100%; max-width: 100%; }
.jam-setlist__editor[rst-form], .jam-setlist__editor [rst-form], .jam-person__card [rst-form] { max-inline-size: none; }
.jam-setlist__danger { display: flex; align-items: center; gap: 0.75rem; margin-left: auto; }
.jam-setlist__danger .jam-fine { margin: 0; }
.jam-danger-link { margin-left: auto; border: 0; background: none; padding: 0.35rem 0.25rem; font: inherit; font-size: var(--rst-fs-sm); color: var(--rst-danger, #c62828); cursor: pointer; text-decoration: none; }
.jam-danger-link:hover { text-decoration: underline; }
.jam-setlist__foot form { display: contents; }
.jam-setlist__foot form.jam-setlist__danger { display: flex; }
/* No overflow: hidden here, ever. It is the obvious way to make the
   corners clip the first and last rows, and it is why a row menu keeps
   coming out trapped inside the list instead of over the page — an
   absolutely positioned panel cannot escape a clipping ancestor, no
   matter what its z-index says. The corners are the rows' own job
   instead: the item carries the radius and its children inherit it, so
   nothing on this list ever needs to clip. */
.jam-setlist { list-style: none; margin: 0; padding: 0; border: 1px solid var(--rst-line); border-radius: 12px; background: var(--rst-surface); }
.jam-setlist__item:first-child { border-radius: 11px 11px 0 0; }
.jam-setlist__item:last-child { border-radius: 0 0 11px 11px; }
.jam-setlist__item:first-child:last-child { border-radius: 11px; }
.jam-setlist__item > *, .jam-setlist__item > details > * { border-radius: inherit; }
/* …except the panels, which have a radius of their own and must not
   take the row's. */
.jam-setlist__item .jam-pop__body { border-radius: 10px; }
/* The panel escaping the list is only half of it: rows are positioned
   (.jam-pop is relative), so a later row paints over an earlier row's
   panel however high the panel's z-index is — z-index only orders
   siblings within the same stacking context, and each row is its own.
   The row with something open is lifted above the rows after it. */
.jam-setlist__item:has(details[open]) { position: relative; z-index: 5; }
.jam-rowpage:has(details[open]) { position: relative; z-index: 5; }
.jam-setlist__item + .jam-setlist__item { border-top: 1px solid var(--rst-line); }
.jam-setlist__item.is-off .jam-setlist__main { color: var(--rst-text-muted); }
.jam-setlist__row { display: flex; align-items: center; gap: 0.9rem; padding: 0.75rem 1rem; list-style: none; cursor: pointer; min-height: 3.25rem; }
.jam-setlist__row::-webkit-details-marker { display: none; }
.jam-setlist__row--static { cursor: default; }
.jam-setlist__d[open] > .jam-setlist__row { background: var(--rst-surface-2); }
.jam-setlist__row:hover { background: var(--rst-surface-2); }
.jam-setlist__lead { flex: none; display: grid; place-items: center; width: 1.75rem; height: 1.75rem; color: var(--rst-text-muted); }
.jam-setlist__main { flex: 1; min-width: 0; display: grid; gap: 0.1rem; }
.jam-setlist__main strong { font-weight: 600; }
.jam-setlist__meta { color: var(--rst-text-muted); font-size: var(--rst-fs-sm); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.jam-setlist__side { flex: none; display: inline-flex; align-items: center; gap: 0.6rem; }
.jam-setlist__side .jam-av + .jam-av { margin-left: -0.5rem; box-shadow: 0 0 0 2px var(--rst-surface); }
.jam-setlist__edit { display: inline-flex; align-items: center; gap: 0.3rem; font-size: var(--rst-fs-sm); color: var(--rst-text-muted); }
.jam-setlist__d[open] .jam-setlist__edit .jam-chev { transform: rotate(180deg); }
.jam-setlist__editor { padding: 1rem 1rem 1.1rem 3.65rem; border-top: 1px dashed var(--rst-line); background: var(--rst-surface); }
/* In-place row editing (the subject-rename pattern): open, the form
   takes the row's own footprint; the row's pencil shows on hover. */
.jam-setlist__d--inline[open] > summary { display: none; }
.jam-setlist__inline { padding: 0.55rem 1rem 0.65rem; background: var(--rst-surface); }

/* The row form stands exactly where the row stood: same padding, same
   min-height, same lead column, so opening one moves nothing below it.
   The controls swap one for one — swatch for colour menu, name for
   input, pencil for Save and Cancel. */
/* The row form stands exactly where the row stood, and "exactly" is
   load-bearing: .jam-setlist__row is content-box, so its 3.25rem
   min-height sits inside its padding and the row is really 3.25rem plus
   1.5rem. A border-box form with the same numbers came out 24px
   shorter, which is precisely the jump this pattern exists to avoid.
   Same box model, same numbers, same height. */
.jam-setlist__rowform { display: flex; align-items: center; gap: 0.55rem; padding: 0.75rem 1rem; min-height: 3.25rem; background: var(--rst-surface-2); }
/* The underline is a rule under the words, not the bottom edge of a
   rounded box; the radius rule above excludes this class for that
   reason. */
.jam-setlist__rowinput {
  flex: 1 1 5rem; min-width: 0; border: 0; border-radius: 0;
  border-bottom: 1.5px solid var(--rst-accent); background: transparent;
  color: inherit; font: inherit; font-weight: 600; padding: 0.1rem 0 0.15rem;
}
.jam-setlist__rowinput:focus-visible {
  outline: none; border-bottom-color: var(--rst-accent-strong); border-bottom-width: 2px; padding-bottom: calc(0.15rem - 0.5px);
}
.jam-setlist__rowform .jam-rowdots { margin-left: auto; align-self: center; }

/* The pencil follows the words it edits, rather than sitting out at the
   right margin where it names nothing. */
.jam-thread__pencil--inline { align-self: center; margin-left: 0.35rem; }
.jam-setlist__title { display: inline-flex; align-items: center; gap: 0; min-width: 0; }
/* A badge in the title stands off the words. The gap is 0 because
   the pencil hugs the name; a badge after the pencil needs less,
   since the pencil's own width is already between them. */
.jam-setlist__title > [rst-badge] { margin-left: 0.5rem; }
.jam-setlist__title > .jam-thread__pencil + [rst-badge] { margin-left: 0.15rem; }
.jam-setlist__row .jam-thread__pencil { opacity: 0; }
.jam-setlist__row:hover .jam-thread__pencil, .jam-setlist__row:focus-visible .jam-thread__pencil { opacity: 1; }

/* The colour menu: a swatch button that opens the palette by name. */
.jam-colourmenu { position: relative; flex: none; }
.jam-colourmenu__btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 0.3rem;
  box-sizing: border-box; height: 1.9rem; padding: 0 0.4rem; cursor: pointer; list-style: none;
  border: 1px solid var(--rst-line-strong); border-radius: var(--rst-radius-sm); background: var(--rst-surface);
}
/* The swatch is a flex child, so it centres on the cross axis only if it
   stops carrying a baseline of its own. */
.jam-colourmenu__btn > .jam-swatch, .jam-colourmenu__btn > svg { display: block; flex: none; }
.jam-colourmenu__btn::-webkit-details-marker { display: none; }
.jam-colourmenu__btn:hover { border-color: var(--rst-accent); }
.jam-colourmenu[open] > .jam-colourmenu__btn { border-color: var(--rst-accent); }
.jam-colourmenu__panel { max-height: 15rem; overflow-y: auto; min-width: 11rem; }
.jam-colourmenu__panel label { display: flex; align-items: center; gap: 0.55rem; padding: 0.42rem 0.6rem; border-radius: 7px; cursor: pointer; font-size: var(--rst-fs-base); }
.jam-colourmenu__panel label:hover { background: var(--rst-surface-2); }
.jam-colourmenu__panel label:has(input:checked) { background: var(--rst-accent-soft); color: var(--rst-accent-strong); font-weight: 600; }
.jam-colourmenu__panel input { position: absolute; width: 1px; height: 1px; opacity: 0; }
.jam-colourmenu__panel label:has(input:focus-visible) { outline: 2px solid var(--rst-accent); outline-offset: -2px; }

/* A split row: the row body is a link to the thing's page, the pencil
   is a link to ?edit=. The link is stretched over the whole row with
   ::after so the row is one large target; the pencil, the pills and the
   row menu sit above it on z-index 1 and stay clickable. Nothing is
   nested inside anything else, so there is no link inside a link for a
   screen reader to trip over. */
.jam-splitrow { position: relative; }
.jam-setlist__row--split { cursor: default; }
.jam-rowlink { color: inherit; text-decoration: none; }
.jam-rowlink::after { content: ""; position: absolute; inset: 0; }
.jam-splitrow .jam-pencilbtn, .jam-splitrow .jam-setlist__side, .jam-splitrow .jam-badge, .jam-splitrow [rst-badge] { position: relative; z-index: 1; }
.jam-pencilbtn { display: inline-grid; place-items: center; width: 1.5rem; height: 1.5rem; border-radius: 5px; border: 1px solid transparent; color: var(--rst-text-faint); flex: none; margin-left: 0.35rem; opacity: 0; transition: opacity 120ms; }
.jam-splitrow:hover .jam-pencilbtn, .jam-pencilbtn:focus-visible { opacity: 1; }
.jam-pencilbtn:hover { background: var(--rst-surface); border-color: var(--rst-line); color: var(--rst-accent); }
@media (prefers-reduced-motion: reduce) { .jam-pencilbtn { transition: none; } }
/* Only the row being edited swaps; the rest of the list is untouched. */
.jam-splitrow > .jam-setlist__rowform { display: none; }
.jam-splitrow.is-editing > .jam-setlist__row { display: none; }
.jam-splitrow.is-editing > .jam-setlist__rowform { display: flex; }
/* A row's own page is one sheet: white ground, a hairline round it, and
   everything inside it reading as parts of the same thing. On the
   settings ground (which is itself off-white) a page of unbounded
   sections reads as loose parts that happen to be near each other. */
.jam-rowpage { background: var(--rst-surface); border: 1px solid var(--rst-line); border-radius: 12px; padding: 1.1rem 1.25rem 1.25rem; max-width: 44rem; }
.jam-rowpage__title { font-family: var(--jam-serif); font-size: 1.6rem; font-weight: 600; letter-spacing: -0.01em; margin: 0 0 0.15rem; }
.jam-rowpage__lede { color: var(--rst-text-muted); font-size: var(--rst-fs-sm); margin: 0 0 1rem; }
/* rst-form is a flex column with a 1.5rem gap and the field carries a
   margin of its own on top of it, which on a sheet this size reads as
   the name and the members belonging to different sections. One
   spacing, set here, rather than two fighting. */
.jam-rowpage [rst-field] { margin: 0; }
.jam-rowpage [rst-form] { max-inline-size: none; margin: 0; gap: 0.85rem; }
.jam-boxmembers__head { margin: 0 0 0.45rem; }
.jam-boxmembers { margin: 0 0 1.1rem; }
/* One line under one rule: what saves on the left, what destroys as far
   to the right as the sheet allows. */
.jam-rowpage__foot { display: flex; flex-wrap: wrap; align-items: center; gap: 0.6rem; margin-top: 1rem; padding-top: 0.9rem; border-top: 1px solid var(--rst-line); }
.jam-rowpage__facts { display: flex; flex-wrap: wrap; gap: 0.4rem; align-items: center; margin: 0 0 1rem; }
.jam-rowpage__foot .jam-inline { display: contents; }
/* The big-form pages keep their own fieldsets; they only need the sheet
   to stop at a sensible width and its heading to sit tight. */
.jam-rowpage > form:last-child { margin-bottom: 0; }
.jam-rowpage__danger { margin-left: auto; }
/* What follows a foot is reference, not more form — the signing secret
   and the delivery log on the webhook page. Without this they sit
   against the foot's underside and read as part of it. */
.jam-rowpage__foot + * { margin-top: 1.4rem; }

/* ── The confirm sheet ──────────────────────────────────────────────
   A destructive action gets its own page rather than a modal, and that
   page has one job: ask. Every one of them is this column — one shell
   for the whole family, so a new question inherits the layout instead
   of inventing a measure of its own.

   Why a sheet and not the page: the question is two or three
   paragraphs, and on a desk .rst-page's 64rem runs them the width of
   the window with the buttons stranded at the far right, which reads
   as a screen nobody laid out. The sheet is a row page's sheet — the
   same white ground, hairline and corner — so the two look like one
   family; it is narrower because this one is prose to read, not a form
   to fill in.

   The way back is INSIDE the column and above the sheet, not out in
   the page: that is what keeps the ← link on the sheet's own left edge
   at every width. No overflow is set anywhere here — a confirm page
   carries no menu today, and a sheet that clips is how a menu on the
   next screen to use it would be cut off. */
.jam-confirm { max-width: 38rem; margin-inline: auto; }
.jam-confirm__sheet { background: var(--rst-surface); border: 1px solid var(--rst-line); border-radius: 12px; padding: 1.25rem 1.5rem 1.4rem; }
/* The question in the serif, as a row page's title is: it is the one
   sentence on the screen that has to be read before anything is
   pressed. */
.jam-confirm__title { font-family: var(--jam-serif); font-size: 1.6rem; font-weight: 600; letter-spacing: -0.01em; margin: 0 0 0.75rem; }
.jam-confirm__sheet > p { margin: 0 0 0.7rem; }
.jam-confirm__sheet > p:last-of-type { margin-bottom: 0; }
.jam-confirm__sheet > [rst-detail] { margin: 0 0 1rem; }
/* The answer, under a rule — the row page's foot, in the one shape
   ui/confirm-form draws: Cancel first in the source, and last of the
   two to be reached by a keyboard. It wraps, because "Keep editing"
   beside "Discard the draft" is wider than a phone. */
.jam-confirm [rst-form-actions] { flex-wrap: wrap; margin-top: 1.25rem; padding-top: 1rem; border-top: 1px solid var(--rst-line); }
/* The same sheet, lifted over the page that asked for it (confirm.js,
   issue #38). Everything inside is the confirm page's own markup, so
   this styles the frame and nothing else: no chrome of its own, the
   sheet's border and radius still doing the work, and the width the
   sheet's width less a gutter so a phone never scrolls sideways. The
   dialog scrolls, not the page behind it, because showModal has
   already made that page inert. */
.jam-confirm--dialog { width: min(38rem, calc(100vw - 2rem)); max-block-size: calc(100dvh - 2rem); overflow: auto; padding: 0; border: 0; background: none; color: inherit; }
/* --rst-overlay is used by tokens.css and defined by nothing, here or
   in the library, so on its own it computes to transparent and the dim
   never arrives. The fallback is the dim: dark in both themes, because
   an overlay is a shadow over the page and a pale one over a dark
   screen reads as fog. */
.jam-confirm--dialog::backdrop { background: var(--rst-overlay, rgb(0 0 0 / 45%)); }
/* The question parked over the composer it is about (confirm.js): the
   dialog itself is the dark layer, sized by the script to the box that
   is about to lose what is in it, with the question centred on it. Its
   own backdrop steps aside — showModal is still what makes this modal;
   this is only where it sits. */
.jam-confirm--dialog.jam-confirm--over { position: fixed; display: flex; align-items: center; justify-content: center; margin: 0; padding: 1rem; box-sizing: border-box; max-inline-size: none; max-block-size: none; overflow: hidden; border-radius: 12px; background: var(--rst-overlay, rgb(0 0 0 / 45%)); }
.jam-confirm--over::backdrop { background: transparent; }
/* Two words and the one fact that matters. Narrow, centred, and the
   answers on a line with no rule above them — there is nothing above
   them to rule off. */
.jam-confirm .jam-confirm__sheet--brief { max-width: 22rem; text-align: center; box-shadow: var(--rst-shadow-pop); }
.jam-confirm .jam-confirm__sheet--brief [rst-form-actions] { justify-content: center; margin-top: 1rem; padding-top: 0; border-top: 0; }

/* A picker in a row: the current value as a button, the choices behind
   it. The same <details> popover the conversation header's menus use,
   so it needs no script and the radios ride the row's own form. */
.jam-rowpick { position: relative; flex: none; }
.jam-rowpick__btn { display: inline-flex; align-items: center; justify-content: space-between; gap: 0.5rem; box-sizing: border-box; height: 1.9rem; cursor: pointer; list-style: none; padding: 0 0.55rem; border: 1px solid var(--rst-line-strong); border-radius: var(--rst-radius-sm); background: var(--rst-surface); font-size: var(--rst-fs-sm); white-space: nowrap; }
.jam-rowpick__btn > .jam-chev { margin-left: 0; }
.jam-rowpick__btn::-webkit-details-marker { display: none; }
.jam-rowpick__btn:hover, .jam-rowpick[open] > .jam-rowpick__btn { border-color: var(--rst-accent); }

/* The row menu: actions that are not field edits. */
/* The row menu is a control, so it looks like one: a hairline of its
   own rather than three dots floating in the row. Same height as the
   ink buttons beside it, so the row's controls sit on one line. */
.jam-rowdots__btn {
  display: inline-grid; place-items: center; box-sizing: border-box;
  width: 1.9rem; height: 1.9rem; border-radius: var(--rst-radius-sm);
  border: 1px solid var(--rst-line-strong); background: var(--rst-surface);
  color: var(--rst-text-muted); cursor: pointer; list-style: none; flex: none;
}
.jam-rowdots__btn::-webkit-details-marker { display: none; }
.jam-rowdots__btn > svg { display: block; }
.jam-rowdots__btn:hover, .jam-rowdots[open] > .jam-rowdots__btn { border-color: var(--rst-accent); color: var(--rst-accent); }
.jam-menu-list__danger { color: var(--rst-tone-negative-fg, hsl(2 66% 42%)); }
.jam-setlist__row .jam-thread__pencil { opacity: 0; }
.jam-setlist__row:hover .jam-thread__pencil, .jam-setlist__row:focus-visible .jam-thread__pencil { opacity: 1; }
.jam-setlist__editor [rst-form] { margin: 0; }
.jam-setlist__foot { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem; margin-top: 0.25rem; }
.jam-setlist__foot .jam-fine { margin: 0; }
.jam-setlist__empty { padding: 1rem; color: var(--rst-text-muted); font-size: var(--rst-fs-sm); }
.jam-setlist__add { margin-top: 0.6rem; }
.jam-setlist__add > summary { list-style: none; }
.jam-setlist__add > summary::-webkit-details-marker { display: none; }
.jam-setlist__add[open] > summary { background: var(--rst-surface-2); border-color: var(--rst-line); }
.jam-setlist__add > .jam-setlist__editor { margin-top: 0.5rem; border: 1px solid var(--rst-line); border-radius: 12px; padding: 1rem 1.1rem; }
/* A line form: label, control, button on one row — the editor a
   setlist row discloses. rst-field-row is the framework's answer to
   two fields side by side, and it stacks each label above its control,
   which is right for a page-level form and twice the height for a
   one-control row editor. This is the inline-label variant the
   framework does not ship. The row wraps only when a phone forces it. */
.jam-line { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem 0.9rem; }
.jam-line [rst-field] { display: flex; flex-wrap: nowrap; align-items: center; gap: 0.45rem; margin: 0; flex: 1 1 12rem; min-width: 0; max-width: 24rem; }
.jam-line [rst-field]:has(select) { flex: 0 1 auto; }
.jam-line [rst-field-label] { margin: 0; white-space: nowrap; }
.jam-line [rst-field] input, .jam-line [rst-field] select { flex: 1 1 6rem; min-width: 0; width: auto; }
.jam-line [rst-field]:has([rst-field-help], [rst-field-error]) { flex-wrap: wrap; }
.jam-line [rst-field-help], .jam-line [rst-field-error] { flex-basis: 100%; margin: 0; }
.jam-line [rst-btn] { flex: none; }
.jam-line > .jam-danger-link { margin-left: auto; }
.jam-line__note { flex: 1 1 100%; margin: 0; }
.jam-setlist__editor--line { padding-top: 0.7rem; padding-bottom: 0.8rem; }
/* A quiet list is read, not acted on — recent deliveries, earlier
   imports: smaller rows, no hover, no hand cursor. */
.jam-setlist--quiet .jam-setlist__row { cursor: default; min-height: 2.75rem; padding: 0.55rem 1rem; }
.jam-setlist--quiet .jam-setlist__row:hover { background: none; }
.jam-setlist--quiet .jam-setlist__main strong { font-weight: 500; font-size: var(--rst-fs-sm); }
/* A secret or a token is shown once, whole, to be copied: it wraps
   rather than overflowing the column, and a click selects all of it. */
.jam-token { display: inline-block; max-width: 100%; padding: 0.35rem 0.55rem; border: 1px solid var(--rst-line); border-radius: var(--rst-radius-sm); background: var(--rst-surface-2); overflow-wrap: anywhere; user-select: all; }
.jam-hook-secret { margin: 1.5rem 0; }
.jam-hook-secret > p { margin: 0 0 0.5rem; }
/* Every select in Settings dresses like the inputs beside it: the
   browser's own chevron sat hard against the right edge, and only the
   one-line editors had been given this. */
.jam-settings select, .jam-line select { appearance: none; -webkit-appearance: none; border: 1px solid var(--rst-line-strong); border-radius: var(--rst-radius-sm); padding: 0.45rem 1.9rem 0.45rem 0.6rem; font: inherit; color: inherit; background: var(--rst-surface) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10' viewBox='0 0 10 10'%3E%3Cpath d='M2 3.5l3 3 3-3' fill='none' stroke='%23999' stroke-width='1.4' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") no-repeat right 0.6rem center; cursor: pointer; }
.jam-settings select:hover, .jam-line select:hover { border-color: var(--rst-text-faint); }
.jam-upload { display: grid; gap: 0.3rem; margin-top: 0.75rem; max-width: 34rem; }
.jam-upload[hidden] { display: none; }
.jam-upload__bar { width: 100%; height: 0.6rem; accent-color: var(--rst-accent); }
.jam-upload__note { margin: 0; }
.jam-colourpick__ctl { display: inline-flex; align-items: center; gap: 0.45rem; }
.jam-colourpick__ctl select { flex: none; text-transform: capitalize; }
.jam-colourpick__ctl .jam-swatch { flex: none; width: 1.15rem; height: 1.15rem; }
.jam-swatch { display: inline-block; width: 1.05rem; height: 1.05rem; border-radius: 50%; background: var(--jam-label); box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.08); }
.jam-memberlist { list-style: none; margin: 0 0 0.75rem; padding: 0; display: flex; flex-wrap: wrap; gap: 0.35rem; }
.jam-memberlist__btn { display: inline-flex; align-items: center; gap: 0.45rem; padding: 0.25rem 0.6rem 0.25rem 0.3rem; border: 1px solid var(--rst-line); border-radius: 999px; background: var(--rst-surface); font: inherit; font-size: var(--rst-fs-sm); color: var(--rst-text); cursor: pointer; }
.jam-memberlist__btn:hover { border-color: var(--rst-line-strong); background: var(--rst-surface-2); }
.jam-memberlist__btn.is-in { background: var(--rst-accent-soft); border-color: transparent; color: var(--rst-accent-strong); }
.jam-memberlist__state { font-size: var(--rst-fs-sm); color: var(--rst-text-faint); }
.jam-memberlist__btn.is-in .jam-memberlist__state { color: var(--rst-accent-strong); }
.jam-switchrow { display: flex; align-items: center; gap: 0.6rem; margin: 0 0 1rem; font-size: var(--rst-fs-sm); }
/* The track is 2.3 × 1.3rem, which is under the 24px a target must
   be (WCAG 2.5.8). The button is bigger than the track it draws:
   0.2rem of padding all round that the background does not paint,
   so the thing under the finger is 27px tall and the thing on the
   screen is unchanged. */
.jam-switch { position: relative; box-sizing: content-box; width: 2.3rem; height: 1.3rem; padding: 0.2rem; border-radius: 999px; border: 0; background: var(--rst-line-strong); background-clip: content-box; cursor: pointer; transition: background-color 160ms; }
.jam-switch[aria-checked="true"] { background: var(--rst-accent); background-clip: content-box; }
.jam-switch__knob { position: absolute; top: 0.35rem; left: 0.35rem; width: 1rem; height: 1rem; border-radius: 50%; background: #fff; box-shadow: 0 1px 2px rgba(0, 0, 0, 0.25); transition: transform 160ms; }
.jam-switch[aria-checked="true"] .jam-switch__knob { transform: translateX(1rem); }
.jam-switch:focus-visible { outline: 2px solid var(--rst-accent); outline-offset: 2px; }
/* The rule editor: When (field · contains), Then (each action a
   block; its extra choice appears once the action is picked). */
/* A choice card's title reads at the same size as the toggle labels
   beside it; the framework sets it a step small. */
.rst-choice__title, [rst-choice-title] { font-size: var(--rst-fs-base); }
.rst-choice__desc, [rst-choice-desc] { font-size: var(--rst-fs-sm); }
.jam-rule fieldset { border: 0; padding: 0; margin: 0 0 0.75rem; }
.jam-rule legend { margin: 0 0 0.4rem; }
.jam-rule__acts { display: grid; gap: 0.35rem 1.25rem; grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr)); }
.jam-rule__act--checks { display: grid; gap: 0.35rem; align-content: start; }
.jam-rule__more { display: none; margin: -0.5rem 0 0.75rem 0.25rem; }
.jam-rule__act:has(select[name="mailbox"] option:checked:not([value=""])) .jam-rule__more { display: block; }
.jam-rule__act--checks:has(input[name="forward_on"]:checked) .jam-rule__more--forward { display: block; margin: 0.25rem 0 0 1.6rem; }
.jam-rule__preview { min-height: 1.2em; margin: 0.25rem 0 0.75rem; padding: 0.55rem 0.8rem; border-radius: 8px; background: var(--rst-surface-2); color: var(--rst-text-muted); font-size: var(--rst-fs-sm); }
.jam-rule__preview:empty { display: none; }
.jam-rule__preview strong { color: var(--rst-text); }
@media (max-width: 720px) {
  .jam-setlist__editor { padding-left: 1rem; }
  .jam-setlist__meta { white-space: normal; }
}

.jam-section { margin-top: 2.25rem; }
.jam-section h2 { font-weight: 600; font-size: 1.25rem; margin: 0 0 0.5rem; }
.jam-inline { display: inline-flex; gap: 0.4rem; align-items: center; margin: 0 0.25rem 0.25rem 0; }
.jam-actions { white-space: nowrap; }
.jam-table { width: 100%; border-collapse: collapse; }
.jam-table th, .jam-table td { text-align: left; padding: 0.6rem 0.8rem; vertical-align: top; border-bottom: 1px solid var(--rst-line); }
.jam-table th { font-size: var(--rst-fs-xs); text-transform: uppercase; letter-spacing: 0.08em; color: var(--rst-text-faint); font-weight: 600; }
.jam-plain-list { list-style: none; padding: 0; margin: 0; }
.jam-plain-list li { display: flex; justify-content: space-between; align-items: center; gap: 1rem; padding: 0.45rem 0; border-bottom: 1px solid var(--rst-line); }
/* Hairlines between items, not a rule under the list: a trailing one
   read as a divider to whatever came next. */
.jam-plain-list li:last-child { border-bottom: 0; }
.jam-codes { columns: 2; list-style: none; padding: 0; margin: 0 0 1.5rem; }
.jam-codes li { padding: 0.25rem 0; }
.jam-codes code { font-size: 1.05rem; }
.jam-switches { display: grid; gap: 0.4rem; margin: 0.5rem 0 1rem; }
/* field-check has no help slot — a switch's label is meant to be the
   whole of it — so a switch that genuinely needs a sentence gets one
   in the same voice every other control's sentence is written in, and
   the grid's own gap is all that holds it off, so it belongs to the
   switch above rather than to the next thing down. */
.jam-switches [rst-field-help] { margin: 0; }
/* A section's second-rank heading: the small caps label DESIGN.md
   names, not a smaller serif heading — one heading per section is
   the composition, and this is a divider inside one. */
.jam-section h3 { font-weight: 600; font-size: 0.72rem; letter-spacing: 0.08em; text-transform: uppercase; color: var(--rst-text-muted); margin: 1.5rem 0 0.5rem; }
/* The Jelly import's progress panel is a callout carrying the
   framework's own meter. The meter is inline-flex and sizes to its
   content, so a width is what makes the bar readable at a glance;
   the fraction beside it says the same thing in words, because a bar
   alone is a state told in colour and length. */
.jam-import [rst-meter] { width: min(22rem, 100%); margin-right: 0.4rem; vertical-align: middle; }

/* ── The inbox: sidebar + list ─────────────────────────────────────── */
.jam-shell { display: grid; grid-template-columns: 13rem minmax(0, 1fr); gap: 2.5rem; max-width: 74rem; margin: 0 auto; padding: 1.5rem 1.5rem 4rem; }
@media (max-width: 720px) { .jam-shell { grid-template-columns: 1fr; gap: 1rem; } .jam-side { position: static; } }
.jam-side { position: sticky; top: 1rem; align-self: start; }
.jam-search input { width: 100%; box-sizing: border-box; padding: 0.45rem 0.8rem; border: 1px solid var(--rst-line); border-radius: 999px; font: inherit; background: var(--rst-surface); color: inherit; }
.jam-search input::placeholder { color: var(--rst-text-faint); }
.jam-side__list { list-style: none; padding: 0; margin: 0 0 1rem; }
.jam-side__list a { display: flex; align-items: center; gap: 0.5rem; padding: 0.35rem 0.6rem; border-radius: 8px; text-decoration: none; color: var(--rst-text-muted); }
.jam-side__list a .jam-count { margin-left: auto; }
.jam-side__list a:hover { color: var(--rst-text); background: var(--rst-surface-2); }
.jam-side__list a[aria-current="page"] { color: var(--rst-accent-strong); background: var(--rst-accent-soft); font-weight: 600; }
.jam-side__list a[aria-current="page"] .jam-count { color: var(--rst-accent-strong); }
/* An applied scope (a chosen mailbox) is not a page: bold with a tick, no fill. */
.jam-side__list a[data-scope="on"] { color: var(--rst-text); font-weight: 600; }
.jam-side__list a[data-scope="on"]::after { content: "✓"; margin-left: auto; color: var(--rst-accent-strong); font-size: var(--rst-fs-sm); }
.jam-list-head__lead { display: inline-flex; align-items: baseline; gap: 0.6rem; min-width: 0; }
.jam-scope { display: inline-flex; align-items: center; gap: 0.35rem; padding: 0.15rem 0.6rem; border-radius: 999px; border: 1px solid var(--rst-line); background: var(--rst-surface); color: var(--rst-text-muted); font-size: var(--rst-fs-sm); font-weight: 500; text-decoration: none; white-space: nowrap; transform: translateY(-0.2rem); }
.jam-scope__x { color: var(--rst-text-faint); font-size: 1rem; line-height: 1; }
.jam-scope:hover { border-color: var(--rst-line-strong); color: var(--rst-text); }
.jam-scope:hover .jam-scope__x { color: var(--rst-accent-strong); }
.jam-side__title { font-size: var(--rst-fs-xs); text-transform: uppercase; letter-spacing: 0.1em; color: var(--rst-text-faint); font-weight: 600; margin: 1.25rem 0 0.4rem 0.6rem; }
.jam-dot { display: inline-block; width: 0.55rem; height: 0.55rem; border-radius: 50%; background: var(--jam-label, var(--rst-line-strong)); }

/* The heading's box — its type, its line box and the row it stands in
   — is not the list's to choose: both panes' heads are cut from one
   set of measures, under "The heads that stay", so that the list's
   heading and the conversation's subject beside it hold one baseline.
   --jam-head-row is the row the heading and the bulk bar take turns
   in, which is why it is taller than the line box the heading fills:
   a bar has controls in it, and selecting must not step the list. */
/* The list's heading and the views pane's — the team's name — are the
   same heading in two panes, so they are one declaration. A team name
   long enough to wrap does; the first baseline is the one that has to
   line up with the other two panes, and it does not move when the
   lines below it arrive.

   Three headings sit in one row across the app — the team name, this
   heading, and the conversation's subject beside it — so all three are
   set in the serif, and all three take --jam-serif-head rather than
   --jam-serif: it is the face measured to the sans's ascent and
   descent, which is what makes --jam-head-size/--jam-head-leading true
   of a heading here at all. Reaching for the plain serif would put a
   different vertical metric on this baseline than the one the row's
   arithmetic assumes. */
.jam-list-head h1, .jam-side-head h1 { font-family: var(--jam-serif-head); font-weight: 600; font-size: var(--jam-head-size); line-height: var(--jam-head-leading); margin: 0 0 var(--jam-head-gap); letter-spacing: -0.01em; }
.jam-side-head h1 { min-width: 0; overflow-wrap: anywhere; }
/* The ledger: a section is a serif heading over a rule — typographic,
   not a container — and a conversation is an open line between
   hairlines, with a margin column for the unread mark. Air between
   lines and more between sections is what keeps it legible. */
.jam-panel { margin: 0 0 0.75rem; }
/* A section head is a band the full width of the pane, like the rows. */
.jam-section-title { font-weight: 600; font-size: 1.05rem; text-transform: none; letter-spacing: 0; color: var(--rst-text); margin: 0 -1.5rem 0.25rem; padding: 0.45rem 1.5rem; background: var(--jam-sec, var(--rst-surface-2)); border-bottom: 1px solid var(--rst-line); box-shadow: inset 3px 0 0 var(--jam-sec-ink, var(--rst-line-strong)); display: flex; align-items: baseline; gap: 0.6rem; }
/* Each inbox section wears a colour the app already speaks: blush for
   what wants action, blueberry for yours, lemon for pending, stone for
   everyone else's. The wash is the band's alone; rows stay plain. */
.jam-section-title[data-section="new"] { --jam-sec: var(--jam-note); --jam-sec-ink: var(--jam-note-ink); }
.jam-section-title[data-section="mine"] { --jam-sec: var(--jam-note); --jam-sec-ink: var(--jam-note-ink); }
.jam-section-title[data-section="unassigned"] { --jam-sec: var(--rst-tone-warning-bg); --jam-sec-ink: var(--rst-tone-warning-fg); }
/* The band is also the section's disclosure (issue #47). It is a
   <summary>, so the browser draws the marker unless told not to and
   the whole band is the target; the chevron is ours, and it is the
   only thing that turns. Nothing here — and nothing on .jam-panel —
   may clip: a row's menu hangs out of the section it is in. */
.jam-section-title { cursor: pointer; list-style: none; user-select: none; -webkit-user-select: none; }
.jam-section-title::-webkit-details-marker { display: none; }
.jam-section-title h2 { font: inherit; margin: 0; }
.jam-section-title:hover { background: color-mix(in oklch, var(--jam-sec, var(--rst-surface-2)) 90%, var(--jam-sec-ink, var(--rst-line-strong)) 10%); }
.jam-section-title:focus-visible { outline: 2px solid var(--rst-accent); outline-offset: -2px; }
/* Baseline-aligned with the words beside it, and pointing down while
   the section is open — the same chevron, and the same direction of
   travel, as every other menu in the app. */
.jam-section-title__chev { flex: none; align-self: center; color: var(--rst-text-muted); transition: transform 120ms; }
.jam-panel--fold:not([open]) > .jam-section-title > .jam-section-title__chev { transform: rotate(-90deg); }
@media (prefers-reduced-motion: reduce) { .jam-section-title__chev { transition: none; } }
.jam-row__follow { display: inline-flex; color: var(--rst-text-faint); }
.jam-row__follow .jam-mi { width: 13px; height: 13px; }
.jam-following-fact { display: inline-flex; align-items: center; gap: 0.3rem; }
.jam-following-fact .jam-mi { width: 13px; height: 13px; color: var(--rst-text-faint); }
/* Who follows: a quiet footer under the reply, words only. */
.jam-followers { display: flex; align-items: baseline; gap: 0.45rem; margin: 1.1rem 0 0; font-size: var(--rst-fs-sm); color: var(--rst-text-muted); }
.jam-followers .jam-mi { color: var(--rst-text-faint); flex: none; transform: translateY(2px); }

/* The help line: one muted sentence under a header, "Got it" right
   beside it, until the member dismisses it. */
.jam-hint__what { font-weight: 600; color: var(--rst-text); }
.jam-hint { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.5rem; margin: 0 0 0.25rem; padding: 0.15rem 0 0.4rem; font-size: var(--rst-fs-sm); color: var(--rst-text-muted); border-bottom: 1px solid var(--rst-line); }
/* A note under a list, saying what the list is not showing. Quiet,
   and never a control: the answer is in the search box, not here. */
.jam-list-note { margin: 0.6rem 0 0; padding: 0.55rem 0 0; border-top: 1px solid var(--rst-line); font-size: var(--rst-fs-sm); color: var(--rst-text-muted); }
/* "Show all N" under an inbox section. It sits OUTSIDE the section's
   <details>, so it is still there when the section is folded away —
   which is the point: a folded heading still says how much is behind
   it, and this is what does something about it. */
.jam-list-more { margin: -0.4rem 0 0.9rem; font-size: var(--rst-fs-sm); }
.jam-list-more a { color: var(--rst-accent-strong); text-decoration: underline; text-underline-offset: 2px; }
.jam-list-more a:hover { color: var(--rst-accent); }
/* The page strip, under every paged list in the app — a conversation
   list and the activity feed both. rst-pagination draws the controls
   themselves (tokens.css); this only gives them room to stand in. */
.jam-pages { margin: 0.9rem 0 1.5rem; }
.jam-hint form { display: contents; }
/* The button is taller than the words it draws: 0.3rem of padding
   above and below that nothing paints, so the thing under the finger
   clears 24px (WCAG 2.5.8) and the thing on the screen is a line of
   underlined text exactly where it was. */
.jam-hint button { border: 0; background: none; padding: 0.3rem 0; margin: -0.3rem 0; min-height: 24px; font: inherit; font-size: var(--rst-fs-sm); font-weight: 500; color: var(--rst-accent-strong); text-decoration: underline; text-underline-offset: 2px; cursor: pointer; white-space: nowrap; }
.jam-hint button:hover { color: var(--rst-accent); }
.jam-section-title .jam-count { font-weight: 400; font-size: var(--rst-fs-sm); background: none; padding: 0; color: var(--rst-text-muted); }
.jam-rows { list-style: none; padding: 0; margin: 0; }
/* The row's vertical measures live here, not inline in the rules
   that use them: the mark column has to add them up to find the
   subject's line, and a number it cannot see would drift. */
.jam-row { --jam-row-pad-y: 0.55rem; --jam-row-gap: 0.1rem; --jam-row-lh: 1.35; border-bottom: 1px solid var(--rst-line);
  /* What sits below the snippet: the row's own bottom padding, or —
     on a row offering the claim — the button block that takes its
     place. One number, because the mark column has to add it up to
     find the subject's line and a second copy would drift. */
  --jam-claim-h: calc(0.3rem + 1.7rem + var(--jam-row-pad-y)); --jam-row-below: var(--jam-row-pad-y); }
.jam-row:has(> .jam-row__claim) { --jam-row-below: var(--jam-claim-h); }
.jam-row:last-child { border-bottom: none; }
/* The link runs the full width of the pane — its padding is the pane's
   padding, so the text stays aligned with the headings. */
.jam-row__link { display: grid; grid-template-columns: minmax(0, 1fr); gap: 0.9rem; padding: var(--jam-row-pad-y) 1.5rem; margin: 0 -1.5rem; text-decoration: none; color: inherit; position: relative; border-radius: 0; }
.jam-row__link:hover { background: var(--rst-surface-2); }
.jam-row--cursor .jam-row__link { background: var(--rst-surface-2); outline: 2px solid var(--jam-note-mid); outline-offset: -2px; }
/* The row, phone first — which is the width it has to be designed at,
   because it is the width that decides what fits. Three lines: who
   and the meta cluster, the subject, then the preview with the
   labels at the end of it. Every line is one flex row whose left half
   shrinks and whose right half keeps its size, so nothing is ever cut
   in half by the edge of the screen; the labels wrap under themselves
   rather than widening the row, which is the lookbook's rule about a
   token. Wide, the labels move up beside the name — see "The row on a
   wide screen" below. */
.jam-row__body { display: grid; gap: var(--jam-row-gap); min-width: 0; }
.jam-row__top { display: flex; justify-content: space-between; gap: 0.6rem; align-items: center; }
/* A sender's name is one line. It used to be allowed to wrap, and a
   three-word company name ("TradeFair Expo Team") then made its row
   half as tall again as the rows around it — a list of different
   heights, for a name nobody reads past the first few words of. */
.jam-row__who { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* A notification's first line is a sentence, not a name: it says who
   did what, and it is the only place that says it, so it wraps. */
.jam-notifs .jam-row__who { white-space: normal; overflow: visible; }
.jam-row__meta { display: inline-flex; align-items: center; gap: 0.4rem; flex: none; }
/* The preview asks for a little under half the line and grows into
   whatever the labels leave. Labels that want more than the rest of it
   drop to a line of their own and use the whole width there, rather
   than stacking themselves one to a line down the side of a squeezed
   preview. */
.jam-row__foot { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 0.35rem 0.5rem; min-width: 0; }
.jam-row__snippet { flex: 1 1 45%; }
.jam-row__tags { display: inline-flex; flex: 0 1 auto; flex-wrap: wrap; justify-content: flex-end; align-items: center; gap: 0.4rem; }
.jam-row__tags:empty { display: none; }
/* The row's meta cluster and its labels read as one weight: the bell,
   the mailbox and the label are the same height and the same type, the
   avatar a touch larger because a face earns it. The label keeps its
   colour and the mailbox its neutral, but neither shouts over the
   other. */
.jam-row__tags .jam-chip { box-sizing: border-box; display: inline-flex; flex: none; align-items: center; gap: 0.3rem; font-size: var(--rst-fs-sm); font-weight: 500; line-height: 1.35; padding: 0.15rem 0.6rem; border: 1px solid transparent; border-radius: 999px; max-width: 100%; white-space: nowrap; }
.jam-row__tags .jam-chip .jam-mi { width: 13px; height: 13px; flex: none; }
.jam-row__meta .jam-row__follow { color: var(--rst-text-muted); }
.jam-row__meta .jam-row__follow .jam-mi { width: 15px; height: 15px; }
.jam-row__meta .jam-av--md { width: 1.65rem; height: 1.65rem; font-size: 0.62rem; }
/* ── The row on a wide screen ───────────────────────────────────────
   Same five pieces, one line fewer to fit them in: the labels come up
   off the preview and sit beside the name, where there is room for
   them. The two wrappers stop being boxes so all five become cells of
   one grid — the only way to move a piece between lines without
   writing the row out twice. */
@media (min-width: 721px) {
  .jam-row__body { grid-template-columns: minmax(0, 1fr) auto auto; column-gap: 0.5rem;
    grid-template-areas: "who tags meta" "subject subject subject" "snippet snippet snippet"; }
  .jam-row__top, .jam-row__foot { display: contents; }
  .jam-row__who { grid-area: who; }
  .jam-row__tags { grid-area: tags; }
  .jam-row__meta { grid-area: meta; }
  .jam-row__subject { grid-area: subject; }
  .jam-row__snippet { grid-area: snippet; }
}
/* Row type: three roles at three sizes. The subject is the only full-size line;
   who and snippet step down a size and a colour each, and unread lifts every
   role by one step (weight for the subject, colour for the other two). */
.jam-row__who { font-size: var(--rst-fs-sm); font-weight: 500; line-height: var(--jam-row-lh); color: var(--rst-text-muted); }
.jam-row__when { font-size: var(--rst-fs-sm); color: var(--rst-text-faint); white-space: nowrap; font-variant-numeric: tabular-nums; min-width: 3rem; text-align: right; }
.jam-row__subject { font-family: var(--rst-font); font-size: var(--rst-fs-base); font-weight: 600; line-height: var(--jam-row-lh); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.jam-row__subject .jam-count { display: inline-block; font-size: var(--rst-fs-sm); font-weight: 700; color: var(--rst-text); background: var(--rst-surface-2); border: 1px solid var(--rst-line); border-radius: 999px; padding: 0 0.4rem; line-height: 1.25rem; min-width: 1.25rem; text-align: center; vertical-align: 0.1em; margin-left: 0.3rem; }
.jam-row__snippet { font-size: var(--rst-fs-sm); line-height: var(--jam-row-lh); min-height: calc(var(--rst-fs-sm) * var(--jam-row-lh)); color: var(--rst-text-faint); max-width: 72ch; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* The search's own highlight, in the excerpt and in the subject above
   it — one rule, because they are one mechanism. Background only: the
   subject is already bold, and a second weight on top of it would make
   the marked words the only thing on the row anyone reads. */
.jam-row__snippet mark, .jam-row__subject mark { background: var(--rst-accent-soft); color: inherit; padding: 0 0.1em; border-radius: 3px; }
.jam-row--unread .jam-row__who { color: var(--rst-text); font-weight: 600; }
.jam-row--unread .jam-row__subject { font-weight: 700; }
.jam-row--unread .jam-row__snippet { color: var(--rst-text-muted); }


.jam-draft-mark { font-size: var(--rst-fs-sm); text-transform: uppercase; letter-spacing: 0.06em; color: var(--rst-tone-warning-fg); background: var(--rst-tone-warning-bg); padding: 0.05rem 0.4rem; border-radius: 4px; vertical-align: middle; font-family: var(--rst-font); }

.jam-av { display: inline-grid; place-items: center; width: 2.5rem; height: 2.5rem; border-radius: 50%; background: var(--jam-av-bg); color: var(--jam-av-ink); font-weight: 600; font-size: 0.8rem; letter-spacing: 0.02em; flex: none; }
.jam-av--out { background: var(--rst-accent-soft); color: var(--rst-accent-strong); }
.jam-av--sm { width: 1.5rem; height: 1.5rem; font-size: 0.58rem; }
.jam-av--md { width: 1.9rem; height: 1.9rem; font-size: 0.68rem; }
.jam-av img { width: 100%; height: 100%; border-radius: 50%; object-fit: cover; display: block; }
.jam-av--lg { width: 3.5rem; height: 3.5rem; font-size: 1.1rem; }
/* An API token has no face. It gets a square, not a circle, and a
   plain "API" tag beside its name: the shape says at a glance that a
   program did this, and the tag says it in words for anyone the shape
   does not reach (state is never colour or shape alone). */
.jam-av--token { border-radius: var(--rst-radius-sm, 0.25rem); background: var(--rst-surface-2); color: var(--rst-text-faint); }
.jam-av--token svg { width: 60%; height: 60%; }
.jam-tokenmark { display: inline-block; padding: 0 0.3em; border: 1px solid var(--rst-border); border-radius: var(--rst-radius-sm, 0.25rem); font-size: 0.62rem; font-weight: 600; letter-spacing: 0.06em; color: var(--rst-text-faint); vertical-align: 0.1em; }
/* The mailbox checkboxes under a token's visibility radio. */
.jam-token-boxes { margin-top: 0.6rem; padding-left: 0.2rem; }
/* An away member's avatar goes quiet — grey face, faint initials — and
   wears a small amber badge with the status's icon at its corner. */
.jam-av--away { position: relative; overflow: visible; background: var(--rst-surface-2); color: var(--rst-text-faint); }
.jam-av--away img { filter: grayscale(1); opacity: 0.55; }
.jam-av__badge { position: absolute; right: -12%; bottom: -12%; width: 58%; height: 58%; min-width: 0.85rem; min-height: 0.85rem; border-radius: 50%; background: var(--rst-tone-warning-bg); color: var(--rst-tone-warning-fg); display: grid; place-items: center; box-shadow: 0 0 0 1.5px var(--rst-surface); }
.jam-av__badge svg { width: 72%; height: 72%; }

/* Who you are here, at the top of your profile: the face, the address,
   the role, and where the face came from. The face is beside the words
   about it rather than above them — it is the subject of the sentence,
   not an illustration over it — and it drops to its own line only when
   the column is too narrow for both, which on a phone it always is.
   No rules around it: the section heading above already says where
   this block starts, and a hairline that groups nothing is the thing
   the settings pages keep growing by accident. */
/* Never wrapped: a face that drops onto its own line above the name
   is a circle floating in a gap, and on a phone it is the first thing
   on the page. It stays beside the words the way a contact's header
   does, and .jam-av--xl already shrinks under 720px. */
.jam-idrow { display: flex; align-items: flex-start; gap: 1.1rem; margin: 1.35rem 0 0; }
.jam-idrow__face { flex: none; }
/* min-width: 0 so a long sentence wraps rather than widening the page
   — the flex default is min-content, which is the whole line. */
.jam-idrow__id { flex: 1 1 auto; min-width: 0; }
.jam-idrow__who { display: flex; flex-wrap: wrap; align-items: center; gap: 0.45rem; margin: 0; font-size: var(--rst-fs-sm); color: var(--rst-text-muted); }
.jam-idrow__who .rst-mono { overflow-wrap: anywhere; }
/* What the face is and how to change it are one line: shut, the fold
   is a button at the end of a sentence. */
.jam-idrow__pic { display: flex; flex-wrap: wrap; align-items: center; column-gap: 0.6rem; row-gap: 0.3rem; margin-top: 0.6rem; }
/* A measure on the note, not on the block: the button after it wants
   to sit by the words, not out at the column's edge. */
.jam-idrow__note { margin: 0; font-size: var(--rst-fs-sm); color: var(--rst-text-muted); max-width: 52ch; }
.jam-idrow__note a { color: inherit; }
/* Open, the editor takes a line of its own — beside the sentence it
   would be no wider than its own button. The basis has to stay 100%
   and uncapped: a max-width here lets the item share the line again,
   which strands the sentence beside a tall box. The cap goes on the
   box instead — it holds one file field and one button, and a border
   three times wider than its contents reads as a mistake. */
.jam-idrow__pic > details[open] { flex: 1 1 100%; }
.jam-idrow .jam-setlist__editor { max-width: 26rem; }
.jam-idrow__drop { margin: 0.9rem 0 0; }
/* A file input cannot be typed into and has nothing to show but its
   own button and a filename, so it is capped rather than taking the
   editor's whole width the way a text field should.
   min(100%, 20rem) and not fit-content: a file input's intrinsic
   width is about 290px and it is a floor, so on a phone fit-content
   pushed the whole editor 44px off the side of the screen — a
   percentage contributes nothing to min-content, so this shrinks.
   Written through .jam-setlist__editor because that box's own rule
   sets every input in it to 100%, which is right for the text fields
   an editor usually holds and outranks a plainer selector here. */
.jam-idrow .jam-setlist__editor input[type="file"] { width: min(100%, 20rem); }
/* The fold's editor is already a bordered box; against the page's own
   margin it needs no second indent. */
.jam-idrow .jam-setlist__add { margin-top: 0; }

/* A form with more than one subject carries its own section breaks.
   rst-form is a flex column with one gap between every child and it
   zeroes their block margins, so .jam-section's own margin-top never
   lands — which is how seven unrelated settings end up evenly spaced
   and reading as one list. Padding is what survives.
   Space alone was not enough: a heading in the same column as the
   fields, with only air above it, still reads as one long form. The
   hairline is what makes a section a section — the first needs none,
   because the page header is already its edge. */
.jam-settings [rst-form] > .jam-section { padding-top: var(--rst-sp-5); border-top: 1px solid var(--rst-line); }
.jam-settings [rst-form] > .jam-section--first { padding-top: 0; border-top: 0; }
.jam-settings [rst-form] > .jam-section > :last-child { margin-bottom: 0; }
/* The heading owns the space under it; a lede closes the gap again. */
.jam-settings [rst-form] > .jam-section > h2 { margin-bottom: 0.9rem; }
.jam-settings [rst-form] > .jam-section > h2 + .jam-lede { margin-top: -0.55rem; }

/* A menu picks labels, it doesn't wear them: a swatch — the label's
   colour as a soft fill inside its own saturated edge — beside plain
   text at the item size, Jelly's way. The chip stays for the places a
   label is worn (rows, facts). */
.jam-swatch { display: inline-block; flex: none; width: 1.05rem; height: 1.05rem; border-radius: 5px; background: color-mix(in oklab, var(--jam-label, var(--rst-tone-neutral-bg)) 40%, var(--rst-surface)); border: 1.5px solid var(--jam-label, var(--rst-tone-neutral-bg)); box-sizing: border-box; }
.jam-chip { display: inline-block; font-size: var(--rst-fs-sm); padding: 0.1rem 0.55rem; border-radius: 999px; background: var(--jam-label, var(--rst-tone-neutral-bg)); color: var(--jam-label-ink, var(--rst-tone-neutral-fg)); border: none; font-weight: 500; line-height: 1.5; font-family: var(--rst-font); }
/* After .jam-chip, not before it: the two weigh the same, and written
   first this lost its display to the plain chip's inline-block — so a
   mailbox chip in the facts was a block with its drawing sat on the
   text's baseline, a few pixels above the middle of the word beside it
   (issue #114). A row's chips never showed it, because .jam-row__tags
   says inline-flex again at a heavier weight. */
.jam-chip--box { display: inline-flex; align-items: center; gap: 0.3rem; }
.jam-chip--box .jam-mi { width: 12px; height: 12px; flex: none; }
/* A chip that takes the label off is a control, so it is at least
   24px tall wherever it is drawn — the row's chips are not controls
   and keep their own size. */
.jam-chip--btn { cursor: pointer; min-height: 24px; }
[data-colour="apricot"] { --jam-label: hsl(20 80% 72%); --jam-label-ink: hsl(20 88% 17%); }
[data-colour="gooseberry"] { --jam-label: hsl(85 80% 68%); --jam-label-ink: hsl(85 88% 14%); }
[data-colour="grape"] { --jam-label: hsl(255 80% 76%); --jam-label-ink: hsl(255 88% 17%); }
[data-colour="blueberry"] { --jam-label: hsl(205 80% 72%); --jam-label-ink: hsl(205 88% 15%); }
[data-colour="cherry"] { --jam-label: hsl(2 80% 74%); --jam-label-ink: hsl(2 88% 17%); }
[data-colour="lime"] { --jam-label: hsl(105 75% 66%); --jam-label-ink: hsl(105 88% 14%); }
[data-colour="lemon"] { --jam-label: hsl(45 85% 66%); --jam-label-ink: hsl(45 88% 14%); }
[data-colour="peach"] { --jam-label: hsl(30 85% 74%); --jam-label-ink: hsl(30 88% 17%); }
[data-colour="plum"] { --jam-label: hsl(275 80% 76%); --jam-label-ink: hsl(275 88% 17%); }
[data-colour="mint"] { --jam-label: hsl(135 75% 68%); --jam-label-ink: hsl(135 88% 14%); }
[data-colour="fig"] { --jam-label: hsl(320 55% 72%); --jam-label-ink: hsl(320 70% 17%); }
[data-colour="raspberry"] { --jam-label: hsl(342 80% 72%); --jam-label-ink: hsl(342 88% 17%); }
[data-colour="olive"] { --jam-label: hsl(70 50% 62%); --jam-label-ink: hsl(70 70% 14%); }
[data-colour="juniper"] { --jam-label: hsl(180 80% 68%); --jam-label-ink: hsl(180 88% 14%); }
[data-colour="huckleberry"] { --jam-label: hsl(225 80% 74%); --jam-label-ink: hsl(225 88% 17%); }

/* ── The conversation ──────────────────────────────────────────────── */
/* The top padding belongs to the head, not to the box around it:
   the head is sticky, so the air above it has to travel with it. */
.jam-conv { --jam-conv-pad-x: 1.5rem; display: grid; grid-template-columns: minmax(0, 1fr) 15rem; gap: 3rem; max-width: 72rem; margin: 0 auto; padding: 0 var(--jam-conv-pad-x) 5rem; }
@media (max-width: 860px) { .jam-conv { grid-template-columns: 1fr; gap: 1.5rem; } }
.jam-thread { min-width: 0; }
.jam-crumb { margin: 0 0 0.75rem; font-size: var(--rst-fs-sm); }
.jam-crumb a { text-decoration: none; color: var(--rst-text-faint); }
.jam-crumb a:hover { color: var(--rst-accent-strong); }
/* Condensed, the subject stops being a wrapped paragraph and becomes
   one line that uses the whole width, ending in an ellipsis. It has to:
   text-wrap balance splits the text evenly across its lines, so the
   first line of a two-line subject carries only half of it, and
   clamping to that line showed half a subject on a line with room for
   all of it.

   white-space cannot be interpolated, so the flip is discrete — and it
   is timed with step-end to land at 0.8, the point where the clamp is
   already exactly one line high. The height does not move across it;
   only the words on the line do. */
@keyframes jam-subj-line { 0% { white-space: normal; } 80%, 100% { white-space: nowrap; } }
.jam-thread__subject { font-family: var(--jam-serif-head); font-weight: 600; font-size: var(--jam-head-size); margin: 0; line-height: var(--jam-head-leading); text-wrap: balance; letter-spacing: -0.015em; min-width: 0; overflow: hidden; text-overflow: ellipsis; animation: jam-subj-line step-end both; animation-timeline: --jam-conv-scroll; animation-range: 0px 11rem; }
/* The gutter the close keeps for itself at the top right: stuck, the
   heading rides at exactly its height, so a long subject would run
   under the one word that gets you out. */
/* The flip rides here, not on the subject: white-space inherits, and
   scroll(nearest) resolves to the nearest scroll container — which from
   inside the summary is the summary's own overflow box, a thing that
   never scrolls. Read from the details, it finds the pane. */
.jam-thread__title { padding-right: 3.5rem; }
/* A subject that wraps is the whole difficulty: shrinking its type
   re-wraps it, and a line appearing or disappearing is a step change in
   the head's height in the middle of a gesture. So the summary carries
   a height of its own, clamped smoothly from "as many lines as it
   likes" down to exactly one stuck line. Past the point where the clamp
   is the shorter of the two, the box height is a straight line in
   --jam-stuck and the re-wrapping underneath it is invisible. The
   subject is never lost: it is back in full the moment you scroll up,
   and the summary's title attribute has it throughout. */
/* A clamp cuts a line of type in half, which reads as a bug rather than
   as a collapse. So the bottom of the summary fades while the clamp is
   working — up over the first of the travel, down again as the subject
   reaches its single line, and gone at both ends, where there is
   nothing being cut and a fade would only eat the descenders. */
.jam-thread__title-summary { margin-bottom: 0.55rem; overflow: hidden; max-height: max(var(--jam-head-leading), calc(11rem - 11rem * var(--jam-stuck, 0))); --jam-subj-clip: clamp(0, min(calc(var(--jam-stuck, 0) / 0.15), calc((0.8 - var(--jam-stuck, 0)) / 0.15)), 1); -webkit-mask-image: linear-gradient(to bottom, #000 calc(100% - 1.1rem * var(--jam-subj-clip)), transparent); mask-image: linear-gradient(to bottom, #000 calc(100% - 1.1rem * var(--jam-subj-clip)), transparent); }
/* Renaming in place: the heading is the summary; a pencil appears
   beside it under the pointer; open, an input set in the heading's
   own type takes its place. */
.jam-thread__title-summary { list-style: none; cursor: text; display: flex; align-items: baseline; gap: 0.5rem; }
.jam-thread__title-summary::-webkit-details-marker { display: none; }
/* align-self centres the span itself on the row; without display:flex
   here too, the span's own box is as tall as its line height, and the
   icon — an inline replaced element, baseline-anchored within that
   box — ends up centred on the wrong box and sits high. Centring the
   icon inside its own tight box is a box-alignment fix, not a
   baseline one, so it holds at every size and every heading font. */
.jam-thread__pencil { display: inline-flex; align-items: center; color: var(--rst-text-faint); opacity: 0; transition: opacity 120ms; flex: none; align-self: center; }
.jam-thread__title-summary:hover .jam-thread__pencil, .jam-thread__title-summary:focus-visible .jam-thread__pencil { opacity: 1; }
.jam-thread__title[open] > .jam-thread__title-summary { display: none; }
.jam-thread__rename { margin: 0 0 0.5rem; }
.jam-thread__subject--input { display: block; width: 100%; box-sizing: border-box; margin: 0; padding: 0 0 0.15rem; border: 0; border-bottom: 2px solid var(--rst-accent); border-radius: 0; background: transparent; color: var(--rst-text); outline: none; }
.jam-thread__rename-foot { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem; margin-top: 0.6rem; }
.jam-thread__rename-foot [rst-btn] { font-size: var(--rst-fs-sm); padding: 0.32rem 0.75rem; }
.jam-thread__rename-foot small { font-size: var(--rst-fs-xs); }

/* An inline save is not a primary action, so it does not wear the
   primary. It speaks in the action row's own thin-ink voice — the same
   language as Archive, Snooze and Label one line below it — which
   leaves strawberry meaning one thing per screen. Cancel is quieter
   still, and the help sentence drops to its own line rather than
   competing with the buttons for the same row. */
.jam-inline-save {
  background: transparent; border: 1px solid var(--rst-text); border-radius: var(--rst-radius-sm);
  color: var(--rst-text); font: inherit; font-size: var(--rst-fs-sm); font-weight: 600;
  padding: 0.28rem 0.7rem; line-height: 1.4; cursor: pointer;
}
.jam-inline-save:hover { border-color: var(--rst-accent); color: var(--rst-accent); }
.jam-inline-cancel {
  background: none; border: 0; padding: 0.28rem 0.1rem; cursor: pointer;
  color: var(--rst-text-muted); font: inherit; font-size: var(--rst-fs-sm); font-weight: 500;
}
.jam-inline-cancel:hover { color: var(--rst-text); }
/* Cancel is sometimes a link — leaving a row's editor is a navigation
   when ?edit= is what opened it — and a link that looks like a link
   beside a button reads as a different kind of control. */
a.jam-inline-cancel { text-decoration: none; display: inline-flex; align-items: center; }
.jam-inline-help { display: block; font-size: var(--rst-fs-xs); color: var(--rst-text-faint); margin: 0.45rem 0 0; }
/* Stuck, the facts stop wrapping: the max-height only bites at the
   very end of the range, so a second line of labels is folded away
   rather than clipped halfway. Scrolling back gives them back.

   It folds to exactly one line, and a line is measured, not guessed:
   --jam-fact-h is the tallest thing a line of facts holds — a mailbox
   chip, its type and padding and the hairline round it, or the 24px a
   removable label keeps for a finger. It used to stop at 2rem, which
   is a line and a few pixels, and the few pixels were the top edge of
   the second line of labels, showing under the head once it had
   condensed (issue #115). It also arrives a little before the end of
   the range rather than exactly at it: a pane that stops a hair short
   leaves --jam-stuck at 0.99-something, and a clip that lands only at
   1 left the second line's top in view there too. */
.jam-thread__facts { --jam-fact-h: max(24px, calc(var(--rst-fs-sm) * 1.5 + 0.2rem + 2px)); display: flex; flex-wrap: wrap; gap: calc(0.5rem - 0.2rem * var(--jam-stuck, 0)) calc(0.9rem - 0.3rem * var(--jam-stuck, 0)); align-items: center; font-size: var(--rst-fs-sm); color: var(--rst-text-muted); margin-bottom: calc(0.9rem - 0.1rem * var(--jam-stuck, 0)); max-height: max(var(--jam-fact-h), calc(30rem - (30rem - var(--jam-fact-h)) * var(--jam-stuck, 0) * 1.03)); overflow: hidden; }
/* A label here is a chip inside its own little form, and .jam-inline
   keeps a bottom margin for sitting in prose. In a centred row that
   margin is half of it lifting the chip: every label stood a couple of
   pixels above the mailbox chips and the words beside it (issue #114).
   The action bar below says the same thing for the same reason. */
.jam-thread__facts > .jam-inline { margin: 0; }
.jam-assignees { display: inline-flex; align-items: center; gap: 0.35rem; color: var(--rst-text); }
/* The action row: who has it (one pill), then thin ink buttons with a
   drawn icon each, the rest behind More. Menus share the row menu's
   list styling. */
.jam-actions-bar { display: flex; flex-wrap: wrap; gap: 0.3rem; align-items: center; padding: calc(0.5rem - 0.15rem * var(--jam-stuck, 0)) 0; border-top: 1px solid var(--rst-line); border-bottom: 1px solid var(--rst-line); }
.jam-actions-bar__gap { flex: 1; }
.jam-actions-bar__rule { width: 1px; height: 1.4rem; background: var(--rst-line); margin: 0 0.35rem; }
.jam-actions-bar > .jam-pop, .jam-actions-bar > .jam-inline, .jam-actions-bar > form { display: inline-flex; align-items: center; }
/* .jam-inline keeps a 0.25rem bottom margin for sitting in prose. In
   a centred bar that margin lifted every button 2px above the rule
   and the danger link beside it; the lookbook measured it. */
.jam-actions-bar > .jam-inline { margin: 0; }
/* Every summary that opens a .jam-pop and wants to be a flex or grid box
   has to be re-stated here. `.jam-pop summary` sets display: inline-block
   further down (0,1,1) and a lone class (0,1,0) does not outrank it, so a
   `display: inline-flex` written beside the rest of the button's own rules
   loses — silently, because the button still looks like a button. It is
   the gap that goes missing, or align-items, and the symptom reads as a
   spacing bug rather than a display one: the colour button stacked its
   swatch above its chevron, and the role picker's chevron sat 1px from
   its text with `gap: 0.5rem` computing quite correctly all the while.
   Add the class here at the same time you write the flex rule. */
.jam-pop > summary.jam-act-btn, .jam-pop > summary.jam-assign__pill, .jam-pop > summary.jam-fbar__pill,
.jam-pop > summary.jam-colourmenu__btn, .jam-pop > summary.jam-rowpick__btn,
.jam-pop > summary.jam-reaction-add { display: inline-flex; }
.jam-pop > summary.jam-rowdots__btn { display: inline-grid; }
/* A busy button that carries an icon shows the spinner in the icon's
   place, at the icon's size, so the buttons beside it never shift.
   The shim puts the spinner in as the first child, just before the
   icon; rastrillo's own icons are already 1em, the spinner's size. */
button[aria-busy="true"] > [rst-spin] + .jam-mi, button[aria-busy="true"] > [rst-spin] + .icon { display: none; }
button[aria-busy="true"] > [rst-spin]:has(+ .jam-mi) { box-sizing: border-box; width: 16px; height: 16px; flex: none; margin: 0; vertical-align: middle; }
.jam-row__follow button[aria-busy="true"] > [rst-spin]:has(+ .jam-mi) { width: 13px; height: 13px; }
.jam-act-btn { display: inline-flex; align-items: center; gap: calc(0.5rem - 0.15rem * var(--jam-stuck, 0)); padding: calc(0.38rem - 0.12rem * var(--jam-stuck, 0)) calc(0.75rem - 0.2rem * var(--jam-stuck, 0)) calc(0.38rem - 0.12rem * var(--jam-stuck, 0)) calc(0.6rem - 0.16rem * var(--jam-stuck, 0)); vertical-align: middle; border: 1px solid transparent; border-radius: 999px; background: none; font: inherit; font-size: var(--rst-fs-sm); font-weight: 500; color: var(--rst-text); cursor: pointer; list-style: none; line-height: 1.2; white-space: nowrap; }
.jam-act-btn::-webkit-details-marker { display: none; }
.jam-act-btn .jam-mi { color: var(--rst-text-muted); }
.jam-act-btn:hover { background: var(--rst-surface-2); border-color: var(--rst-line); }
.jam-act-btn:hover .jam-mi { color: var(--rst-text); }
.jam-pop[open] > .jam-act-btn { background: var(--rst-accent-soft); color: var(--rst-accent-strong); }
.jam-pop[open] > .jam-act-btn .jam-mi { color: var(--rst-accent-strong); }
.jam-act-btn:focus-visible, .jam-assign__pill:focus-visible { outline: 2px solid var(--rst-accent); outline-offset: 2px; }
.jam-chev { flex: none; color: var(--rst-text-faint); margin-left: -0.15rem; }
/* The assignment pill: faces and names when someone has it; a soft
   strawberry pill saying so when nobody does. */
.jam-assign__pill { display: inline-flex; align-items: center; gap: 0.45rem; padding: 0.25rem 0.65rem 0.25rem 0.3rem; border-radius: 999px; border: 1px solid var(--rst-line); background: var(--rst-surface); font-size: var(--rst-fs-sm); font-weight: 600; color: var(--rst-text); cursor: pointer; list-style: none; line-height: 1.2; }
.jam-assign__pill::-webkit-details-marker { display: none; }
.jam-assign__pill:hover { border-color: var(--rst-line-strong); background: var(--rst-surface-2); }
/* The pill wears the accent only when the conversation is yours; other
   people's assignments and no assignment at all read in the default colour. */
.jam-assign[data-unassigned] .jam-assign__pill { padding-left: 0.6rem; }
.jam-assign[data-mine] .jam-assign__pill { background: var(--rst-accent-soft); color: var(--rst-accent-strong); border-color: transparent; }
.jam-assign[data-mine] .jam-assign__pill .jam-mi, .jam-assign[data-mine] .jam-chev { color: var(--rst-accent-strong); }
.jam-assign[open] .jam-assign__pill { border-color: var(--rst-accent); }
.jam-assign .jam-assignees { gap: 0.2rem; }
.jam-assign .jam-assignees .jam-av { box-shadow: 0 0 0 2px var(--rst-surface); }
.jam-assign .jam-assignees .jam-av + .jam-av { margin-left: -0.5rem; }
/* Stuck, the pill keeps the faces and lets the names go: the avatars
   already say who has it, and the row wins back a third of its width. */
.jam-assign__names { margin-left: calc(0.25rem * (1 - var(--jam-stuck, 0))); overflow: hidden; white-space: nowrap; max-width: calc(24rem * (1 - var(--jam-stuck, 0))); opacity: calc(1 - var(--jam-stuck, 0)); }
.jam-menu-list__primary { color: var(--rst-accent-strong); font-weight: 600; }
.jam-menu-list__primary .jam-mi { color: var(--rst-accent-strong); }
.jam-menu-list__on { margin-left: auto; color: var(--rst-accent-strong); font-weight: 600; }
.jam-menu-list kbd { margin-left: auto; font: inherit; font-size: var(--rst-fs-xs); color: var(--rst-text-faint); border: 1px solid var(--rst-line); border-bottom-width: 2px; border-radius: 4px; padding: 0 0.5rem; }
.jam-menu-list__row { display: flex; gap: 0.4rem; align-items: center; padding: 0.3rem 0.6rem; }
.jam-menu-list__row input { flex: 1; min-width: 0; font: inherit; font-size: var(--rst-fs-sm); padding: 0.3rem 0.5rem; border: 1px solid var(--rst-line); border-radius: 6px; background: var(--rst-surface); color: inherit; }
.jam-menu-list__row [rst-btn] { font-size: var(--rst-fs-sm); padding: 0.3rem 0.7rem; }
/* A menu row with two doors: the check (stays open) and the name
   (closes). The check draws as a box; on, it fills with the accent. */
.jam-mrow { display: flex; align-items: center; }
/* The row is the form: both doors post the same action, so a second
   form — and a second copy of every hidden field — was pure weight on
   a page that renders one of these per conversation. */
.jam-mrow__check { appearance: none; border: 0; background: none; padding: 0.4rem 0.35rem 0.4rem 0.6rem; margin: 0; cursor: pointer; display: grid; place-items: center; border-radius: 6px; }
.jam-mrow__check::before { content: ""; box-sizing: border-box; width: 1.05rem; height: 1.05rem; border: 1.5px solid var(--rst-line-strong); border-radius: 4px; background: var(--rst-surface); }
.jam-mrow__check:hover::before { border-color: var(--rst-accent); }
.jam-mrow__check.is-on::before { border-color: var(--rst-accent); background: var(--rst-accent) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath d='M2.5 6.5l2.4 2.4L9.5 3.6' fill='none' stroke='white' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center no-repeat; }
.jam-mrow__check.is-some::before { border-color: var(--rst-accent); background: var(--rst-accent) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath d='M3 6h6' fill='none' stroke='white' stroke-width='1.8' stroke-linecap='round'/%3E%3C/svg%3E") center no-repeat; }
/* In the label menu the swatch IS the checkbox: the label's colour
   at rest, its tick drawn in the label's own ink when applied. */
.jam-mrow__check--swatch::before { border-color: var(--jam-label, var(--rst-line-strong)); background: color-mix(in oklab, var(--jam-label, var(--rst-surface)) 40%, var(--rst-surface)); border-radius: 5px; }
.jam-mrow__check--swatch:hover::before { border-color: var(--jam-label-ink, var(--rst-accent)); }
.jam-mrow__check--swatch.is-on::before { border-color: var(--jam-label-ink); background: var(--jam-label) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath d='M2.5 6.5l2.4 2.4L9.5 3.6' fill='none' stroke='%231c1c20' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center no-repeat; }
.jam-mrow__check--swatch.is-some::before { border-color: var(--jam-label-ink); background: var(--jam-label) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath d='M3 6h6' fill='none' stroke='%231c1c20' stroke-width='1.8' stroke-linecap='round'/%3E%3C/svg%3E") center no-repeat; }
.jam-mrow__check.is-refused::before { border-color: var(--rst-danger, #c62828); background: var(--rst-surface); }
.jam-mrow__check.is-busy::before { border: 2px solid var(--rst-line); border-top-color: var(--rst-accent); border-radius: 50%; background: none; animation: jam-spin 0.6s linear infinite; }
/* Same box either way: border-box sizing keeps the busy state from
   nudging the row sideways when the border thickens. */
@keyframes jam-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .jam-mrow__check.is-busy::before { animation: none; border-style: dotted; } }
.jam-mrow__name { flex: 1; min-width: 0; display: flex; align-items: center; gap: 0.5rem; border: 0; background: none; font: inherit; font-size: var(--rst-fs-sm); color: var(--rst-text); text-align: left; padding: 0.4rem 0.65rem 0.4rem 0.35rem; margin: 0; border-radius: 6px; cursor: pointer; }
.jam-mrow__name:hover { background: var(--rst-surface-2); }
.jam-mrow__count { margin-left: auto; color: var(--rst-text-faint); font-size: var(--rst-fs-xs); }
.jam-menu-list__label { display: block; padding: 0.3rem 0.6rem 0; font-size: var(--rst-fs-xs); color: var(--rst-text-muted); text-transform: uppercase; letter-spacing: 0.04em; }
.jam-menu-list__empty { margin: 0; padding: 0.4rem 0.6rem; color: var(--rst-text-muted); font-size: var(--rst-fs-sm); white-space: normal; }
.jam-menu-list .jam-rename small { display: block; padding: 0 0.6rem 0.3rem; white-space: normal; max-width: 18rem; }
.jam-menu-list .jam-av--sm { flex: none; }
/* ── The menu item, once ────────────────────────────────────────────
   Three panels hold menus — the account menu (.jam-menu__panel), the
   popover column (.jam-pop__body--col) and the row and action menus
   (.jam-menu-list) — and each of them used to write its own item from
   scratch. That is how two of the three came to lay their padding
   outside the panel: the browser's own stylesheet gives <button>
   border-box and leaves <a> at content-box, so one width: 100% meant
   two different boxes in the same list, and the longest label — which
   is the one that sets the panel's width — hung its hover over the
   edge. Fixing it in one panel fixed it in one panel.

   So the mechanics are here, once. A panel says only what is
   different about it, and says it in the variables; a fourth panel
   joins by adding its selector to the list and setting them.
   TestBrowserMenuItemsFitTheirMenu walks every menu on the screen
   rather than the ones it was told about, for the same reason. */
.jam-menu__panel { --jam-item-pad: 0.45rem 0.6rem; --jam-item-radius: 8px; --jam-item-gap: 0.55rem; --jam-item-size: var(--rst-fs-base); --jam-item-weight: inherit; }
.jam-pop__body--col { --jam-item-pad: 0.4rem 0.65rem; --jam-item-radius: 6px; --jam-item-gap: 0.55rem; --jam-item-size: var(--rst-fs-sm); --jam-item-weight: 450; }
.jam-menu-list { --jam-item-pad: 0.42rem 0.6rem; --jam-item-radius: 7px; --jam-item-gap: 0.65rem; --jam-item-size: var(--rst-fs-base); --jam-item-weight: inherit; }
.jam-menu__panel a, .jam-menu__panel button, .jam-menu__panel .jam-menu__subsum,
.jam-pop__body--col [rst-btn],
.jam-menu-list a, .jam-menu-list button, .jam-menu-list label, .jam-menu-list .jam-sub > summary {
  box-sizing: border-box; display: flex; align-items: center; justify-content: flex-start; gap: var(--jam-item-gap);
  width: 100%; margin: 0; padding: var(--jam-item-pad);
  border: 0; border-radius: var(--jam-item-radius); background: none; box-shadow: none;
  font: inherit; font-size: var(--jam-item-size); font-weight: var(--jam-item-weight);
  color: var(--rst-text); text-align: left; text-decoration: none; cursor: pointer;
}
.jam-menu__panel a:hover, .jam-menu__panel button:hover, .jam-menu__panel .jam-menu__subsum:hover,
.jam-pop__body--col [rst-btn]:hover,
.jam-menu-list a:hover, .jam-menu-list button:hover, .jam-menu-list label:hover, .jam-menu-list .jam-sub > summary:hover { background: var(--rst-surface-2); color: var(--rst-text); }
/* A two-door row keeps its doors on one line: the shared menu-item
   rule above makes every button a full-width block, and these put the
   check back beside the name wherever the row renders — the header
   menus and the fetched flyouts alike. */
.jam-menu-list .jam-mrow { display: flex; align-items: center; padding: 0; }
.jam-menu-list .jam-mrow > .jam-mrow__check { display: grid; width: auto; flex: none; }
.jam-menu-list .jam-mrow > .jam-mrow__name { display: flex; width: auto; flex: 1; min-width: 0; }

.jam-pop { position: relative; display: inline-block; }
/* Flipped placement (scripts measure on open): above the button, or
   hugging the other side, whenever the default would be cut off. */
[data-flip-y] > .jam-pop__body { top: auto; bottom: calc(100% + 0.3rem); margin-top: 0; }
[data-flip-y] > .jam-menu__panel { top: auto; bottom: calc(100% + 0.4rem); }
[data-flip-y] > .rst-row-menu__panel, [data-flip-y] > [rst-row-menu-panel],
[data-flip-y] > .rst-dropdown__menu, [data-flip-y] > [rst-dropdown-menu] { top: auto; bottom: calc(100% + 4px); }
[data-flip-x] > .jam-pop__body { left: auto; right: 0; }
.jam-pop--right[data-flip-x] > .jam-pop__body { right: auto; left: 0; }
/* Menus sit above everything. The cards are stacking contexts (each
   message carries z-index so its sheet shadow layers), which traps a
   menu's own z-index inside its card — so while a menu is open, the
   card holding it lifts above its siblings. The row menus already do
   this; this is the same rule for the whole pop pattern. */
.jam-pop[open] { z-index: 31; }
.jam-msg:has(.jam-pop[open]), .jam-note:has(.jam-pop[open]),
.jam-entries > :has(.jam-pop[open]), .jam-thread__folds > :has(.jam-pop[open]) { position: relative; z-index: 30; }
.jam-pop summary { list-style: none; cursor: pointer; display: inline-block; }
.jam-pop summary::-webkit-details-marker { display: none; }
.jam-pop__body { position: absolute; z-index: 30; top: 100%; left: 0; margin-top: 0.3rem; background: var(--rst-surface); border: 1px solid var(--rst-line); border-radius: 10px; padding: 0.6rem; display: flex; gap: 0.5rem; align-items: center; box-shadow: var(--rst-shadow-pop); white-space: nowrap; }
.jam-pop--right .jam-pop__body { left: auto; right: 0; }
.jam-pop__body--col { flex-direction: column; align-items: stretch; gap: 0.1rem; padding: 0.35rem; }
.jam-pop__body--col form { display: contents; }
/* Buttons in a menu are menu items, not buttons: no pill, no border,
   quiet hover — whatever element they happen to be. */

/* The timeline: a spine on the left; every entry hangs off it with a
   node — the sender's or author's face, the actor's for activity. */
/* One axis for every face: the spine runs under the centre of a
   message's avatar (1.5rem in, 2.5rem wide → 2.75rem), and the notes'
   and activity's faces sit on it. */
.jam-entries { list-style: none; padding: 0 0 0 3.75rem; margin: 1.5rem 0 0; display: grid; gap: 1rem; position: relative; }
/* Grid items may shrink: a collapsed head's nowrap snippet must
   ellipsise inside the pane, never widen every card in the column. */
.jam-entries > * { min-width: 0; }
.jam-entries::before { content: ""; position: absolute; left: calc(2rem - 1px); top: 1.2rem; bottom: 0; width: 2px; background: var(--rst-line); border-radius: 1px; }
.jam-msg, .jam-note, .jam-act { position: relative; }
/* A message sits on top of the line, edge to edge, its face inside.
   That bleed only cancels .jam-entries's own padding, so it is scoped
   to messages there — the sending row is a .jam-msg too, but it lives
   in .jam-thread__folds, which carries no such padding to cancel. */
.jam-msg { z-index: 1; }
.jam-entries .jam-msg { margin-left: -3.75rem; }
.jam-node { position: absolute; left: -3rem; top: 0.9rem; width: 2.5rem; display: flex; justify-content: center; }
.jam-node .jam-av { width: 1.9rem; height: 1.9rem; font-size: 0.68rem; }
.jam-node .jam-av { box-shadow: 0 0 0 3px var(--rst-bg); }
.jam-node--note { top: 0.65rem; }
.jam-node--act { top: -0.1rem; }
.jam-node__dot { width: 0.6rem; height: 0.6rem; border-radius: 50%; background: var(--rst-line-strong); box-shadow: 0 0 0 3px var(--rst-bg); margin-top: 0.45rem; }
@media (prefers-reduced-motion: no-preference) {
  .jam-msg, .jam-note { animation: jam-rise 0.5s cubic-bezier(0.16, 1, 0.3, 1) both; }
  @keyframes jam-rise { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }
}
.jam-msg { background: var(--rst-surface); border: 1px solid var(--rst-line); border-radius: 12px; padding: 0.75rem 1.5rem 1.4rem; box-shadow: var(--jam-shadow-sheet); }
.jam-msg--out { background: var(--jam-out); }
/* The head reaches 0.75rem into the rail so the sender's avatar sits
   on it (centre 2rem, like the spine and the nodes); the body's left
   edge stays where it was. */
.jam-msg__head { display: grid; grid-template-columns: 2.5rem minmax(0, 1fr) auto; gap: 0.85rem; align-items: start; margin-bottom: 1rem; margin-left: -0.75rem; }
.jam-msg__head > .jam-av { justify-self: center; }
.jam-msg__from { display: grid; gap: 0.1rem; min-width: 0; font-size: var(--rst-fs-sm); }
.jam-msg__from strong { font-size: var(--rst-fs-base); color: var(--rst-text); }
.jam-msg__addr { color: var(--rst-text-faint); }
.jam-msg__to { color: var(--rst-text-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.jam-msg__when { font-size: var(--rst-fs-sm); color: var(--rst-text-faint); white-space: nowrap; font-variant-numeric: tabular-nums; padding-top: 0.2rem; }
/* A reply in its undo window: an outbound bubble that says it is on
   its way, with a way to stop it. */
.jam-msg--sending { opacity: 0.92; }
.jam-msg__sending-bar { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem 0.9rem; margin-bottom: 0.6rem; font-size: var(--rst-fs-sm); color: var(--rst-text-muted); }
.jam-sending { display: inline-flex; align-items: center; gap: 0.4rem; font-weight: 600; color: var(--jam-note-ink); }
.jam-sending__dot { width: 0.5rem; height: 0.5rem; border-radius: 50%; background: var(--jam-note-ink); animation: jam-sending-pulse 1s ease-in-out infinite; }
@keyframes jam-sending-pulse { 0%,100% { opacity: 0.35; } 50% { opacity: 1; } }
@media (prefers-reduced-motion: reduce) { .jam-sending__dot { animation: none; } }
.jam-sending__undo { margin-left: auto; }
.jam-sending__undo button { border: 1px solid var(--rst-line-strong); background: var(--rst-surface); border-radius: 999px; padding: 0.15rem 0.7rem; font: inherit; font-size: var(--rst-fs-sm); color: var(--rst-text); cursor: pointer; }
.jam-sending__undo button:hover { border-color: var(--jam-note-ink); }
/* Over the text part: which version is on screen, and the way back.
   A label, not a notice — it is the state of the thing under it. */
.jam-msg__plainbar { display: flex; flex-wrap: wrap; gap: 0.25rem 0.75rem; align-items: baseline; margin: 0 0 0.6rem; font-family: var(--rst-font); font-size: var(--rst-fs-sm); color: var(--rst-text-faint); }
.jam-msg__plain { margin: 0; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: var(--rst-fs-sm); line-height: 1.55; white-space: pre-wrap; overflow-wrap: anywhere; color: var(--rst-text); }
/* A message is the one thing on the screen whose markup a stranger
   wrote, and some of that markup carries a width of its own: the 600px
   table every marketing email is built from, a min-width on a wrapper,
   a picture sized for a desktop. The card is where that width stops.
   The body keeps the reading measure — 68ch, which is what prose is
   for — and anything that would rather be wider than that is allowed
   to be, out to the edge of the card, where there is usually room for
   it and where a scrollbar next to half a card of white space would be
   an odd thing to hand somebody. Past the card, .jam-msg__wide takes
   over as a scroller: the message drags sideways inside its own box
   and the page never moves, which is the same bargain the rest of the
   app strikes (a table or a code block may exceed the width, inside
   its own scroller; the page does not). overscroll-behavior keeps that
   drag off the page behind it and off the browser's back gesture.
   contain closes the box: it makes the wrapper the containing block
   for anything the message positions fixed, and clips what a negative
   margin would hang outside, so a stranger's CSS cannot put anything
   over the app. None of this is the sanitiser's job — taking the width
   out at the door would leave every message already in the database as
   wide as it arrived, and the widths are how an email looks like
   itself. */
.jam-msg__wide { overflow-x: auto; overscroll-behavior-x: contain; contain: layout paint; }
.jam-msg__body { font-family: var(--jam-serif); font-size: 1.02rem; line-height: 1.6; max-width: 68ch; overflow-wrap: anywhere; color: var(--rst-text); }
/* What can be narrower is: max-width beats an inline width: whatever
   the sender wrote, so a fluid table reflows to the measure instead of
   being dragged. Only what cannot reflow reaches the scroller. */
.jam-msg__body table, .jam-msg__body div, .jam-msg__body pre, .jam-msg__body blockquote { max-width: 100%; }
.jam-msg__body p { margin: 0 0 0.85em; }
.jam-msg__body p:last-child { margin-bottom: 0; }
.jam-msg__body img { max-width: 100%; height: auto; }
.jam-msg__body a { color: var(--rst-accent-strong); }
.jam-msg__body blockquote { border-left: 1px solid var(--rst-line-strong); margin: 0.6em 0; padding-left: 0.9em; color: var(--rst-text-muted); }
.jam-msg__body pre { font-size: 0.85rem; overflow-x: auto; }

/* ── Quoted text ───────────────────────────────────────────────────── */
/* The history a reply carries under it, folded behind an ellipsis
   pill. A <details> opens without JavaScript and takes the keyboard
   for free, so there is nothing here but the look of the thing. */
.jam-quote { margin-top: 0.6em; }
.jam-quote__toggle { list-style: none; display: inline-block; cursor: pointer; padding: 0 0.55rem; border-radius: var(--rst-radius-pill); background: var(--rst-line); color: var(--rst-text-faint); font-family: var(--rst-font); font-size: var(--rst-fs-sm); font-weight: 600; line-height: 1.6; letter-spacing: 0.08em; }
.jam-quote__toggle::-webkit-details-marker { display: none; }
.jam-quote__toggle:hover { background: var(--rst-accent-soft); color: var(--rst-accent); }
.jam-quote__toggle:focus-visible { outline: 2px solid var(--rst-accent); outline-offset: 2px; }
.jam-quote__body { margin-top: 0.6em; padding-left: 0.9rem; border-left: 1px solid var(--rst-line); color: var(--rst-text-muted); }
.jam-quote__body blockquote { border-left-color: var(--rst-line); }

.jam-shelf { list-style: none; padding: 0.8rem 0 0; margin: 1rem 0 0; border-top: 1px dashed var(--rst-line); display: flex; flex-wrap: wrap; gap: 0.5rem 1.25rem; font-size: var(--rst-fs-sm); font-family: var(--rst-font); }
.jam-shelf a { text-decoration: none; font-weight: 500; }

.jam-note { position: relative; background: var(--jam-note); border: 1px solid var(--jam-note-line); border-radius: 10px; padding: 0.55rem 0.9rem 0.6rem; margin-left: 0; color: var(--jam-note-ink); }
.jam-note:has(.jam-note__fold:not([open])) { padding-top: 0.35rem; padding-bottom: 0.35rem; }
.jam-note__menu { position: absolute; top: 0.3rem; right: 0.4rem; }
.jam-note__menu .jam-more-btn { color: var(--jam-note-ink); opacity: 0.7; }
.jam-note__menu[open] .jam-more-btn, .jam-note__menu .jam-more-btn:hover { opacity: 1; }
.jam-note__head { padding-right: 2rem; }
.jam-note__permalink { color: inherit; text-decoration: none; }
.jam-note__permalink:hover time { text-decoration: underline; text-underline-offset: 2px; }
.jam-pop__body--col .jam-note__delete { color: var(--rst-danger, #c62828); }
/* The menu owns editing now; the editwrap's own summary is only the
   scriptless way in. */
.js .jam-note__editwrap > summary { display: none; }
.jam-note__head { display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.45rem; font-size: var(--rst-fs-sm); min-width: 0; }
.jam-note__head strong { color: inherit; }
.jam-note__tag { font-size: 0.68rem; text-transform: uppercase; letter-spacing: 0.1em; font-weight: 600; opacity: 0.75; margin-left: 0.25rem; }
.jam-note__head time { margin-left: auto; color: inherit; opacity: 0.7; }
.jam-note__body { font-size: var(--rst-fs-base); line-height: 1.55; }
/* An imported note whose author is not a member here says so on its
   first line — quieter than the note, and never mistaken for it. */
.jam-note__origin { font-size: var(--rst-fs-sm); opacity: 0.75; margin: 0 0 0.5em; }
.jam-note__body p { margin: 0 0 0.5em; }
.jam-note__body p:last-child { margin-bottom: 0; }
.jam-note .jam-av { background: var(--jam-note-line); color: var(--jam-note-ink); }
.jam-note__tag { color: var(--jam-note-ink); }
.jam-mention { font-weight: 600; color: var(--rst-accent-strong); }
.jam-note__edited { font-size: var(--rst-fs-sm); color: var(--jam-note-ink); opacity: 0.7; font-style: italic; }
.jam-note__editwrap { margin-top: 0.5rem; }
.jam-note__editbtn { list-style: none; cursor: pointer; display: inline-flex; align-items: center; gap: 0.3rem; font-size: var(--rst-fs-sm); color: var(--jam-note-ink); opacity: 0.75; }
.jam-note__editbtn::-webkit-details-marker { display: none; }
.jam-note__editbtn:hover { opacity: 1; text-decoration: underline; }
.jam-note__editwrap[open] > .jam-note__editbtn { display: none; }
.jam-note__body:has(+ .jam-note__editwrap[open]) { display: none; }
.jam-note .jam-mention { color: inherit; text-decoration: underline; text-decoration-color: var(--jam-note-line); text-underline-offset: 0.15em; }

/* A note's small controls, on one line under the body: "Copy link"
   always, "Edit" while it is still the author's to change. Opening the
   editor takes the whole row, and the copy link steps aside. */
.jam-note__tools { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.15rem 1rem; margin-top: 0.5rem; }
.jam-note__tools > .jam-note__editwrap { margin-top: 0; }
a.jam-note__editbtn { text-decoration: none; }
.jam-note__tools:has(.jam-note__editwrap[open]) { display: block; }
.jam-note__tools:has(.jam-note__editwrap[open]) > a.jam-note__editbtn { display: none; }
.jam-note__body:has(+ .jam-note__tools .jam-note__editwrap[open]) { display: none; }

/* A note's reactions: one chip per team member who reacted, in the
   order they did, and a quiet "add a reaction" popover after them. */
.jam-note__reactions { display: flex; flex-wrap: wrap; align-items: center; gap: 0.35rem; margin-top: 0.6rem; }
.jam-reaction { display: inline-flex; align-items: center; gap: 0.3rem; font-size: var(--rst-fs-sm); line-height: 1; padding: 0.2rem 0.5rem 0.2rem 0.2rem; border-radius: 999px; background: var(--rst-surface); border: 1px solid var(--jam-note-line); }
form.jam-reaction { background: none; border: 0; padding: 0; margin: 0; }
.jam-reaction--mine button { display: inline-flex; align-items: center; gap: 0.3rem; font: inherit; line-height: 1; padding: 0.2rem 0.5rem 0.2rem 0.2rem; border-radius: 999px; background: var(--rst-accent-soft); border: 1px solid var(--rst-accent-strong); color: inherit; cursor: pointer; }
.jam-reaction--mine button:hover { background: var(--rst-line); }
.jam-reaction .jam-av, .jam-reaction--mine .jam-av { box-shadow: none; }
.jam-reaction-add { list-style: none; cursor: pointer; display: inline-flex; align-items: center; padding: 0.25rem 0.4rem; border-radius: 999px; opacity: 0.6; }
.jam-reaction-add::-webkit-details-marker { display: none; }
.jam-reaction-add:hover, .jam-reaction-pop[open] > .jam-reaction-add { opacity: 1; background: var(--rst-surface); }

/* The reaction picker: search plus Jelly-style named categories, built
   on the same filter-as-you-type list the assign and label menus use
   (jam-menu-list__filter/__scroll/__none in shortcuts.js) so search,
   viewport clamping and Enter-to-pick all come for free. Emoji are
   plain characters — no artwork, no CDN — so the set can be generous. */
.jam-reaction-palette { width: 17rem; max-width: calc(100vw - 2.5rem); }
.jam-reaction-palette .jam-menu-list__scroll { display: flex; flex-wrap: wrap; align-content: flex-start; gap: 0.05rem; }
.jam-reaction-palette .jam-menu-list__scroll form { display: contents; }
.jam-reaction-cat { flex: 1 0 100%; margin: 0.55rem 0 0.15rem; padding: 0 0.15rem; font-size: var(--rst-fs-xs); font-weight: 600; color: var(--rst-text-muted); text-transform: uppercase; letter-spacing: 0.04em; }
.jam-reaction-cat:first-child { margin-top: 0; }
.jam-reaction-palette button { font-size: 1.15rem; line-height: 1; padding: 0.3rem; border-radius: 6px; background: none; border: 0; cursor: pointer; }
.jam-reaction-palette button:hover { background: var(--rst-accent-soft); }
/* Visually hidden, not aria-hidden: a picker button's accessible name
   (and what the search box matches) beside the glyph. */
.jam-vh { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; }

/* ── Following a link to one message or note ────────────────────────
   The sheet takes a soft accent tint that fades away over a couple of
   seconds, so the eye lands on the thing the link named and the page
   then looks like any other. Scripts off there is nothing to fade it,
   so :target holds the tint for as long as the fragment is in the
   URL — and with scripts on, that same rule would never let go, which
   is why it is scoped to html:not(.js). Under reduced motion the
   script keeps the class instead of removing it: the tint stays. */
/* An outline rather than a box-shadow: a sheet already has one, and
   the tint must not flatten it for the two seconds it is up. */
.jam-msg, .jam-note { outline: 2px solid transparent; outline-offset: 2px; transition: outline-color 700ms ease-out, background-color 700ms ease-out; }
.jam-entry--flash,
html:not(.js) .jam-msg:target, html:not(.js) .jam-note:target,
html:not(.js) .jam-entry--focus { outline-color: var(--rst-accent); transition: none; }
.jam-msg.jam-entry--flash, html:not(.js) .jam-msg:target, html:not(.js) .jam-msg.jam-entry--focus { background-color: var(--rst-accent-soft); }
@media (prefers-reduced-motion: reduce) { .jam-msg, .jam-note { transition: none; } }
.jam-act { font-size: var(--rst-fs-sm); color: var(--rst-text-muted); padding: 0.15rem 0 0.15rem 0; min-height: 1.5rem; }
.jam-act__who { font-weight: 600; color: var(--rst-text-muted); }

.jam-compose-note { display: grid; gap: 0.5rem; margin-top: 0.75rem; }
/* The two forms fold closed: a pill each, on the spine. */
.jam-thread__folds { position: relative; }
.jam-thread__folds::before { content: ""; position: absolute; left: calc(2rem - 1px); top: -1rem; bottom: 1.6rem; width: 2px; background: var(--rst-line); border-radius: 1px; }
.jam-thread__folds > * { position: relative; }

/* Presence: who else has this conversation open. Quiet by design —
   small muted text under the things you would write with, an avatar
   each, and a word for what they are doing. The one time it raises
   its voice is a clash: you are writing a reply and so is somebody
   else, which is the whole reason the bar exists. */
/* The last item on the timeline: faces on the rail like any other
   node, words beside them. */
.jam-presence { display: none; position: relative; flex-wrap: wrap; align-items: center; gap: 0.5rem; margin: 1rem 0 0 3.75rem; min-height: 1.9rem; font-size: var(--rst-fs-sm); color: var(--rst-text-muted); }
/* The first face sits on the spine exactly where a lone one would
   (0.3rem is the slack a 1.9rem avatar leaves in the 2.5rem node);
   the rest of the stack flows rightwards, and the words step aside
   when it does. */
/* The faces sit in flow, pulled left so the first lands on the spine:
   however many there are, the words start after the last face rather
   than underneath it. */
.jam-presence .jam-node { position: static; width: auto; min-width: 2.5rem; justify-content: flex-start; margin-left: -3rem; padding-left: 0.3rem; }
.jam-presence:has(.jam-av) { display: flex; }
.jam-presence__faces { display: inline-flex; }
.jam-presence__faces .jam-av { margin-left: -0.55rem; }
.jam-presence__faces .jam-av:first-child { margin-left: 0; }
.jam-presence__says { display: inline-flex; flex-wrap: wrap; align-items: center; gap: 0.4rem; }
.jam-presence__group { display: inline-flex; align-items: center; gap: 0.35rem; }
.jam-presence__group .jam-mi { color: var(--rst-text-faint); }
.jam-presence__sep { color: var(--rst-text-faint); }
.jam-presence__clash { padding: 0.15rem 0.5rem; border-radius: 999px; background: var(--rst-tone-warning-bg); color: var(--rst-tone-warning-fg); font-weight: 500; }
.jam-presence__clash .jam-mi { color: inherit; }
.jam-fold [rst-field-label] { color: var(--rst-text-muted); font-size: var(--rst-fs-sm); }
.jam-fold { position: relative; margin: 1rem 0 0 3.75rem; }
.jam-fold summary { list-style: none; cursor: pointer; display: flex; align-items: center; margin-left: -0.15rem; }
.jam-fold summary::-webkit-details-marker { display: none; }
.jam-fold__pill { display: inline-flex; align-items: center; gap: 0.5rem; padding: 0.5rem 0.9rem; border-radius: 999px; background: var(--rst-surface-2); color: var(--rst-text-muted); font-weight: 500; border: 1px solid var(--rst-line); }
.jam-fold__pill kbd { font: inherit; font-size: 0.7rem; border: 1px solid var(--rst-line); border-bottom-width: 2px; border-radius: 4px; padding: 0 0.5rem; color: var(--rst-text-faint); }
.jam-fold__pill--primary { background: var(--rst-accent); color: var(--rst-on-accent); border-color: var(--rst-accent); font-weight: 600; font-size: 1rem; padding: 0.6rem 1.2rem; }
.jam-fold__pill--primary kbd { color: var(--rst-on-accent); border-color: rgba(255, 255, 255, 0.5); }
/* Open, the pill gives way to the form (scripts on; without them the
   pill stays as the only way to close). */
.js .jam-fold[open] > summary { display: none; }
.jam-fold[open] > summary { margin-bottom: 0.6rem; }
.jam-fold__actions { display: flex; gap: 0.5rem; align-items: center; }
.jam-entry--cursor { outline: 2px solid var(--jam-note-mid); outline-offset: 3px; border-radius: 12px; }
.jam-fold--reply { margin-left: 0; }
.jam-fold--reply .jam-compose { margin-top: 0; }
.jam-fold--note .jam-node { top: 0.15rem; }
.jam-compose-note [rst-field-label] { color: var(--jam-note-ink); }

.jam-compose-note textarea { width: 100%; box-sizing: border-box; padding: 0.7rem 0.9rem; border: 1px solid var(--jam-note-line); background: var(--jam-note); color: var(--jam-note-ink); border-radius: 10px; font: inherit; line-height: 1.5; }
.jam-compose-note textarea::placeholder { color: var(--jam-note-ink); opacity: 0.55; }

.jam-contact__name { font-weight: 600; font-size: 1.15rem; margin: 0.6rem 0 0.1rem; }
/* Name and note edit in place in the pane, on the same contract as the
   conversation's subject: the reading is the summary, the form stands
   in its place. */
.jam-contact__edit { min-width: 0; }
.jam-contact__edit-summary { list-style: none; cursor: text; display: flex; align-items: baseline; gap: 0.4rem; min-width: 0; }
.jam-contact__edit-summary::-webkit-details-marker { display: none; }
.jam-contact__edit-summary:hover .jam-thread__pencil, .jam-contact__edit-summary:focus-visible .jam-thread__pencil { opacity: 1; }
.jam-contact__edit[open] > .jam-contact__edit-summary { display: none; }
.jam-contact__edit-summary .jam-contact__name { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.jam-contact__name-input { display: block; width: 100%; box-sizing: border-box; margin: 0.1rem 0 0; padding: 0 0 0.1rem; border: 0; border-bottom: 2px solid var(--rst-accent); border-radius: 0; background: transparent; font: inherit; font-weight: 600; font-size: 1.15rem; color: var(--rst-text); outline: none; }
.jam-contact__form { margin: 0 0 0.2rem; }
.jam-contact__form[rst-form], .jam-contact__form [rst-form] { max-inline-size: none; }
.jam-contact__form textarea { box-sizing: border-box; width: 100%; max-width: 100%; }
.jam-contact__form-foot { display: flex; flex-wrap: wrap; align-items: center; gap: 0.4rem; margin-top: 0.5rem; }
.jam-contact__form-foot [rst-btn] { font-size: var(--rst-fs-sm); padding: 0.3rem 0.7rem; }
.jam-contact__email { margin: 0 0 0.5rem; }
/* A note is prose the team wrote: its line breaks are its own. */
.jam-contact__note { display: block; min-width: 0; white-space: pre-wrap; font-size: var(--rst-fs-sm); color: var(--rst-text-muted); }
.jam-contact__note--none { color: var(--rst-text-faint); font-style: italic; }
.jam-contact__page { margin: 0.7rem 0 0; font-size: var(--rst-fs-sm); }
.jam-contact__page a { display: inline-flex; align-items: center; gap: 0.1rem; color: var(--rst-text-muted); text-decoration: none; }
.jam-contact__page a:hover { color: var(--rst-accent-strong); }
.jam-contact p { margin: 0 0 0.35rem; font-size: var(--rst-fs-sm); color: var(--rst-text-muted); }
.jam-contact__sub { font-size: var(--rst-fs-xs); text-transform: uppercase; letter-spacing: 0.08em; color: var(--rst-text-faint); font-weight: 600; margin: 1.1rem 0 0.4rem; padding-left: 0.5rem; }
.jam-contact__convs { list-style: none; padding: 0; margin: 0; }
.jam-contact__convs li { display: grid; gap: 0.1rem; padding: 0.45rem 0.5rem; border-radius: 6px; font-size: var(--rst-fs-sm); }
.jam-contact__convs li.is-current { background: var(--rst-accent-soft); }
.jam-contact__convs a { text-decoration: none; color: var(--rst-text); font-size: 0.95rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.jam-contact__convs li:hover { background: var(--rst-surface-2); }
.jam-contact__convs time { font-size: var(--rst-fs-sm); color: var(--rst-text-faint); }

/* ── Compose ───────────────────────────────────────────────────────── */
.jam-compose { margin-top: 1.5rem; background: var(--rst-surface); border: 1px solid var(--rst-line); border-radius: 12px; padding: 1.1rem 1.5rem 1.2rem; box-shadow: var(--jam-shadow-sheet); }
.jam-compose__prov { font-size: var(--rst-fs-sm); margin: 0 0 0.6rem; }
/* The read-only draft: the same sheet, the fields as a quiet list, the
   words as they would read in the thread, and one button. */
.jam-compose__preview-fields { margin: 0; padding: 0; }
.jam-compose__preview-fields > div { display: grid; grid-template-columns: 4rem minmax(0, 1fr); gap: 0.5rem; padding: 0.4rem 0; border-bottom: 1px solid var(--rst-line); }
.jam-compose__preview-fields dt { font-size: var(--rst-fs-sm); color: var(--rst-text-faint); }
.jam-compose__preview-fields dd { margin: 0; overflow-wrap: anywhere; }
.jam-compose__preview-body { padding: 0.75rem 0 0.25rem; }
.jam-compose--preview .jam-compose__foot { margin-top: 0.75rem; }
/* Why there is no Discard button under a draft somebody else is still
   in: the one line that says who to go and ask (#57). */
.jam-compose__coordinate { font-size: var(--rst-fs-sm); margin: 0.6rem 0 0; }
/* The editor that lost its lock: the words stay for copying, but
   nothing about them can be sent. */
.jam-compose__lost { margin: 0 0 0.75rem; }
/* Cc and Bcc start hidden; a text button on the To row reveals each.
   Without scripts the buttons are gone and the rows show plainly, so
   nothing is unreachable. A row with content on arrival shows too. */
.jam-compose__recip-toggles { display: none; gap: 0.7rem; padding-left: 0.5rem; }
.js .jam-compose__recip-toggles { display: inline-flex; }
.jam-compose__recip-toggles button { border: 0; background: none; padding: 0; margin: 0; font: inherit; font-size: var(--rst-fs-sm); color: var(--rst-text-faint); cursor: pointer; }
.jam-compose__recip-toggles button:hover { color: var(--rst-text-muted); }
.js .jam-compose__row--recip:not([data-shown]) { display: none; }
.jam-compose__row { display: grid; grid-template-columns: 4rem minmax(0, 1fr); align-items: center; gap: 0.5rem; padding: 0.25rem 0; border-bottom: 1px solid var(--rst-line); }
.jam-compose__row--to { grid-template-columns: 4rem minmax(0, 1fr) auto; }
.jam-compose__row label { font-size: var(--rst-fs-sm); color: var(--rst-text-faint); }
.jam-compose__row input, .jam-compose__row select { border: none; background: transparent; font: inherit; padding: 0.35rem 0; width: 100%; color: inherit; }
.jam-compose__row input:focus, .jam-compose__row select:focus { outline: none; }
.jam-compose__editor { margin-top: 0.5rem; }
/* ── The editor ─────────────────────────────────────────────────── */
/* One toolbar for every editor: drawn icons in groups, pressed state
   from the cursor's formatting, dialogs (link, saved replies) under
   it, the @mention list beside the caret. */
/* Every editor is one bordered box: toolbar, hairline, field. The note fold
   only recolours this shape. */
.jam-editor-wrap { --jam-status-w: 5rem; position: relative; display: block; width: 100%; min-width: 0; border: 1px solid var(--rst-line); border-radius: 10px; background: var(--rst-surface); }
.jam-editor-wrap:focus-within { border-color: var(--rst-accent); box-shadow: 0 0 0 3px var(--rst-accent-soft); }
.jam-editor-dialog[hidden], .jam-mentions[hidden], .jam-mentions li[hidden] { display: none; }
.jam-editor-wrap textarea { width: 100%; box-sizing: border-box; border: 0; border-radius: 10px; background: transparent; padding: 0.6rem 0.75rem; font: inherit; outline: none; resize: vertical; }
[data-editor-files].is-enhanced input[type="file"] { position: absolute; width: 1px; height: 1px; opacity: 0; overflow: hidden; }
.jam-toolbar { position: relative; display: flex; flex-wrap: wrap; gap: 0.15rem 0.5rem; align-items: center; padding: 0.35rem calc(0.6rem + var(--jam-status-w, 5rem)) 0.35rem 0.6rem; border-bottom: 1px solid var(--rst-line); border-radius: 10px 10px 0 0; }
.jam-toolbar__group { display: inline-flex; gap: 0.1rem; padding-right: 0.5rem; border-right: 1px solid var(--rst-line); }
.jam-toolbar__group:last-of-type { border-right: 0; }
.jam-toolbar [data-tool] { -webkit-appearance: none; appearance: none; display: grid; place-items: center; width: 1.85rem; height: 1.85rem; border: 1px solid transparent; border-radius: 6px; background: none; color: var(--rst-text-muted); cursor: pointer; padding: 0; margin: 0; font: inherit; line-height: 1; }
.jam-toolbar [data-tool]:hover { color: var(--rst-text); background: var(--rst-surface-2); }
.jam-toolbar [data-tool][aria-pressed="true"] { color: var(--rst-accent-strong); background: var(--rst-accent-soft); }
.jam-toolbar [data-tool]:focus-visible { outline: 2px solid var(--rst-accent); outline-offset: 1px; }
.jam-toolbar__phone { display: none !important; }
/* The save state has a reserved place at the right of the toolbar
   rather than a place in the queue. It used to be an ordinary flex
   item with no size of its own: empty for all but a second or two at
   a time, then suddenly seventy-odd pixels wide the moment a draft
   saved — which on a narrow toolbar pushed itself onto a second line
   and shoved the message and the Send button down, mid-sentence. So
   it comes out of the flow entirely, and the toolbar keeps
   --jam-status-w of right padding for it whether it is saying
   anything or not: the tools wrap the same way in both states, and
   the words land in space that was already set aside. Right-anchored
   and free to grow leftwards, so a longer message is readable rather
   than clipped — but it should not have to. TestBrowserComposeDoesNotMove
   reads the messages out of editor.js, so one too long for the slot
   fails there rather than quietly moving the composer again. */
.jam-toolbar__status { position: absolute; top: 0; bottom: 0; right: 0.6rem; display: flex; align-items: center; justify-content: flex-end; min-width: var(--jam-status-w, 5rem); white-space: nowrap; font-size: var(--rst-fs-xs); color: var(--rst-text-faint); }
/* A dialog (the link form, the saved-reply list) floats over the
   text where it would otherwise sit, below the toolbar: top is left
   auto, so the static position places it, and nothing below moves. */
.jam-editor-dialog { position: absolute; z-index: 8; left: 0.4rem; right: 0.4rem; display: flex; gap: 0.4rem; align-items: center; margin: 0.2rem 0 0.5rem; padding: 0.5rem 0.6rem; border: 1px solid var(--rst-line); border-radius: 9px; background: var(--rst-surface); box-shadow: var(--rst-shadow-pop); }
.jam-editor-dialog input[type="url"] { flex: 1; min-width: 0; font: inherit; font-size: var(--rst-fs-sm); padding: 0.35rem 0.6rem; border: 1px solid var(--rst-line); border-radius: 6px; background: var(--rst-surface); color: inherit; }
.jam-editor-dialog [rst-btn] { font-size: var(--rst-fs-sm); padding: 0.32rem 0.7rem; }
.jam-editor-dialog--list { display: grid; gap: 0.1rem; padding: 0.35rem; max-height: 16rem; overflow: auto; }
.jam-editor-dialog--list button, .jam-editor-dialog--list li { display: flex; align-items: center; gap: 0.6rem; width: 100%; padding: 0.4rem 0.6rem; border: 0; border-radius: 6px; background: none; font: inherit; font-size: var(--rst-fs-sm); color: var(--rst-text); text-align: left; cursor: pointer; }
.jam-editor-dialog--list button:hover, .jam-editor-dialog--list li:hover, .jam-editor-dialog--list li[aria-selected="true"] { background: var(--rst-surface-2); }
.jam-editor-dialog--list button strong { font-weight: 600; }
.jam-editor-dialog--list button[data-saved-reply] { display: grid; gap: 0.05rem; min-width: 0; }
.jam-editor-dialog--list button[data-saved-reply] strong .jam-muted { font-weight: 400; margin-left: 0.35rem; }
.jam-editor-dialog__snip { font-size: var(--rst-fs-xs); color: var(--rst-text-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.jam-setlist__meta-lead { color: var(--rst-text); }
.jam-editor-dialog--list .jam-muted { font-size: var(--rst-fs-xs); }
/* The @mention list floats over the note at the caret rather than
   sitting in the flow, so opening it never shifts the content. */
.jam-mentions { list-style: none; position: absolute; z-index: 8; margin: 0; min-width: 16rem; max-width: calc(100% - 0.5rem); max-height: 14rem; overflow: auto; }
/* Heights, by kind: a couple of lines more than the text usually
   needs, so writing never starts in a slot. The reply is the page's
   business; a note is short; the rest (signature, autoresponder, a
   saved reply) sit between. */
.jam-editor { position: relative; width: 100%; box-sizing: border-box; min-height: 9rem; padding: 0.6rem 0.75rem; border: 0; border-radius: 0 0 10px 10px; background: transparent; font-size: 1rem; line-height: 1.55; outline: none; overflow-wrap: anywhere; }
.jam-editor:empty::before, .jam-editor[data-placeholder]:has(> div:only-child > br:only-child)::before { content: attr(data-placeholder); color: var(--rst-text-faint); position: absolute; pointer-events: none; }
.jam-editor-wrap--reply .jam-editor { min-height: 12rem; font-family: var(--jam-serif); font-size: 1.02rem; line-height: 1.6; }
.jam-editor-wrap--note .jam-editor { min-height: 7.5rem; }
/* Writing a note looks like the note it becomes — and shaped like
   Jelly's comment form: the whole form on the blueberry card, the
   toolbar and field one bordered box on a lighter well, the author's
   avatar holding the rail while it is open. Placed after the base
   editor rules on purpose: same specificity, later order wins. */
.jam-fold--note .jam-compose-note { background: var(--jam-note); border-radius: 12px; padding: 0.85rem 1rem 1rem; gap: 0.65rem; margin-top: 0; }
/* The note fold recolours the shared one-box editor; no overflow:hidden —
   the @mention list floats out of the wrap. */
.jam-fold--note .jam-editor-wrap { border-color: var(--jam-note-line); background: var(--jam-note-well, var(--rst-surface)); }
.jam-fold--note .jam-editor-wrap:focus-within { border-color: var(--jam-note-ink); box-shadow: 0 0 0 3px var(--jam-note-line); }
.jam-fold--note .jam-toolbar { border-bottom-color: var(--jam-note-line); }
.jam-fold--note .jam-editor { color: var(--jam-note-ink); min-height: 7.5rem; }
.jam-fold--note .jam-editor::before { color: var(--jam-note-ink); opacity: 0.6; }
.js .jam-fold--note[open] > summary { display: block; margin: 0; height: 0; }
.jam-fold--note[open] .jam-fold__pill { display: none; }
.jam-editor a { color: var(--rst-accent-strong); }
.jam-editor pre { background: var(--rst-surface-2); border-radius: 6px; padding: 0.5rem 0.7rem; font-size: 0.92em; overflow: auto; }
.jam-editor .jam-signature { color: var(--rst-text-muted); }
.jam-editor img { max-width: 100%; height: auto; border-radius: 4px; cursor: default; }
.jam-editor img:focus { outline: 2px solid var(--rst-accent); outline-offset: 2px; }
.jam-editor-wrap .squire-image-resize-container { pointer-events: none; }
.jam-editor-wrap .squire-image-resize-container > * { pointer-events: auto; width: 10px; height: 10px; background: var(--rst-surface); border: 2px solid var(--rst-accent); border-radius: 2px; }
.jam-msg__body img, .jam-note__body img { max-width: 100%; height: auto; }
.jam-compose__filelist { font-size: var(--rst-fs-xs); color: var(--rst-text-muted); }
@media (max-width: 720px) {
  .jam-toolbar__phone { display: grid !important; }
  .jam-toolbar { gap: 0.1rem 0.25rem; }
  .jam-toolbar__group { padding-right: 0.25rem; gap: 0; }
  .jam-toolbar [data-tool] { width: 1.65rem; height: 1.65rem; }
  .jam-toolbar [data-tool] svg { width: 14px; height: 14px; }
  .jam-toolbar [data-tool="undo"], .jam-toolbar [data-tool="redo"] { display: none !important; }
  .jam-fold--note [rst-field-label] { color: var(--rst-text-muted); }
  .jam-thread__folds::before { left: calc(0.9rem - 1px); }
  .jam-editor { font-size: 0.95rem; }
}
.jam-toolbar--old { display: flex; gap: 0.25rem; align-items: center; padding: 0.35rem 0; }
.jam-toolbar button { border: 1px solid var(--rst-line); background: var(--rst-surface-2); border-radius: 6px; padding: 0.2rem 0.55rem; font: inherit; font-size: var(--rst-fs-sm); cursor: pointer; color: var(--rst-text-muted); }
.jam-toolbar button:hover { color: var(--rst-text); border-color: var(--rst-line-strong); }
.jam-toolbar button:disabled { opacity: 0.38; cursor: default; }
.jam-toolbar button:disabled:hover { color: inherit; border-color: var(--rst-line); }
.jam-toolbar [data-compose-status] { margin-left: auto; font-size: var(--rst-fs-xs); }
.jam-editor blockquote { border-left: 1px solid var(--rst-line-strong); margin: 0.6em 0; padding-left: 0.9em; color: var(--rst-text-muted); }
.jam-compose textarea { width: 100%; box-sizing: border-box; font: inherit; padding: 0.7rem; border: 1px solid var(--rst-line); border-radius: 8px; background: var(--rst-surface); color: inherit; }
.jam-compose__foot { display: flex; gap: 0.5rem; flex-wrap: wrap; padding-top: 0.7rem; border-top: 1px solid var(--rst-line); margin-top: 0.5rem; }
.jam-signature { color: var(--rst-text-muted); }

/* ── Keyboard help ─────────────────────────────────────────────────── */
.jam-help { position: fixed; right: 1.25rem; bottom: 1.25rem; z-index: 20; width: 18rem; background: var(--rst-surface); border: 1px solid var(--rst-line); border-radius: 12px; padding: 1rem 1.25rem; box-shadow: var(--rst-shadow-pop); }
.jam-help h2 { font-size: 1rem; margin: 0 0 0.5rem; }
.jam-help dl { display: grid; grid-template-columns: auto 1fr; gap: 0.25rem 0.75rem; margin: 0; font-size: var(--rst-fs-sm); }
.jam-help dt { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; color: var(--rst-accent-strong); }
.jam-help dd { margin: 0; }

/* ── Notifications ─────────────────────────────────────────────────── */
.jam-notifs .jam-row__link { grid-template-columns: 1.5rem minmax(0, 1fr); align-items: center; }

/* ── Narrow screens ────────────────────────────────────────────────── */
.jam-compose__row input, .jam-compose__row select { min-width: 0; }
@media (max-width: 900px) {
  .jam-top { flex-wrap: wrap; gap: 0.5rem 0.75rem; padding: 0.6rem 1rem; }
  /* The brand and the two menu buttons beside it are one group at the
     left, in the order they are written; the account menu takes the
     right of the same line and everything else drops below. */
  .jam-brand, .jam-brandbtn, .jam-views-btn { order: 1; }
  .jam-menu { margin-left: auto; order: 2; }
  .jam-nav { order: 3; flex: 1; overflow-x: auto; scrollbar-width: none; margin: 0; }
  .jam-top__compose { order: 4; }
  .jam-top .jam-search { order: 5; flex-basis: 100%; max-width: none; margin: 0; }
  /* The sidebar becomes a strip of views above the list, so the mail
     is the first thing on a phone. */
  .jam-side { display: flex; align-items: center; gap: 0.25rem; overflow-x: auto; scrollbar-width: none; margin: 0 -1rem; padding: 0 1rem; }
  .jam-side__list { display: flex; gap: 0.25rem; margin: 0; }
  .jam-side__list a { white-space: nowrap; }
  .jam-side__title { display: none; }
  .jam-nav a { white-space: nowrap; }
  .jam-conv { --jam-conv-pad-x: 1rem; padding-bottom: 4rem; }
  .jam-shell { padding: 1rem 1rem 4rem; }
  .jam-msg { padding: 1rem 1.1rem 1.1rem; }
  .jam-entries { padding-left: 2.6rem; }
  .jam-node { left: -2.6rem; }
  /* The spine follows the avatars: centres sit at 1.25rem here. */
  .jam-entries::before, .jam-thread__folds::before { left: calc(1.25rem - 1px); }
  .jam-entries .jam-msg { margin-left: -2.6rem; }
  .jam-fold { margin-left: 2.6rem; }
  .jam-fold--reply { margin-left: 0; }
  .jam-presence { margin-left: 2.6rem; }
  .jam-compose { padding: 1rem 1.1rem; }
}

/* ── Contacts ──────────────────────────────────────────────────────── */
/* ── Contacts ──────────────────────────────────────────────────────── */
/* The directory: a segmented Everyone/Blocked, a search, then rows —
   face, name and address, last heard from on the right. */
/* These pages have little intrinsic width; the page column is a flex
   item that would shrink to it, so the page itself is sized. */
.jam-page--wide { width: 52rem; max-width: 100%; }
/* ── Settings: a sidebar of pages beside the one being read ─────────
   The sidebar is the inbox's, in miniature: group titles in caps, the
   current page filled. On a phone it turns into a strip across the
   top that scrolls sideways, and settings.js scrolls the current item
   into view. */
.jam-page--settings { width: 68rem; max-width: 100%; }
.jam-setshell { display: grid; grid-template-columns: 11rem minmax(0, 1fr); gap: 2.75rem; align-items: start; }
/* A settings-shaped page with no sidebar — the set-up page a new member
   meets, a confirmation — keeps the settings measure without leaving the
   first grid column empty beside it. */
.jam-setshell--solo { grid-template-columns: minmax(0, 1fr); max-width: 44rem; }
.jam-setnav { position: sticky; top: 1rem; }
.jam-setnav__head { font-weight: 600; font-size: 1.25rem; margin: 0 0 0.25rem 0.6rem; }
.jam-setnav__title { font-size: var(--rst-fs-xs); text-transform: uppercase; letter-spacing: 0.1em; color: var(--rst-text-faint); font-weight: 600; margin: 1rem 0 0.3rem 0.6rem; }
.jam-setnav__list { list-style: none; padding: 0; margin: 0; }
.jam-setnav__list a { display: flex; align-items: center; padding: 0.3rem 0.6rem; border-radius: 8px; text-decoration: none; color: var(--rst-text-muted); font-size: var(--rst-fs-sm); }
.jam-setnav__list a:hover { color: var(--rst-text); background: var(--rst-surface-2); }
.jam-setnav__list a[aria-current="page"] { color: var(--rst-accent-strong); background: var(--rst-accent-soft); font-weight: 600; }
.jam-settings [rst-page-header] + .jam-section, .jam-settings [rst-page-header] + .jam-cols, .jam-section--first { margin-top: 0; }
@media (max-width: 720px) {
  .jam-setshell { grid-template-columns: 1fr; gap: 1rem; }
  .jam-setnav { position: static; display: flex; align-items: center; gap: 0.25rem; overflow-x: auto; scrollbar-width: none; padding-bottom: 0.6rem; border-bottom: 1px solid var(--rst-line); }
  .jam-setnav::-webkit-scrollbar { display: none; }
  .jam-setnav__head { display: none; }
  .jam-setnav__title { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; margin: 0; }
  .jam-setnav__list { display: flex; gap: 0.25rem; flex: none; }
  .jam-setnav__list:not(:first-of-type) { border-left: 1px solid var(--rst-line); padding-left: 0.5rem; margin-left: 0.25rem; }
  .jam-setnav__list a { padding: 0.3rem 0.7rem; border-radius: 999px; white-space: nowrap; }
}
.jam-dir, .jam-person { width: 100%; min-width: 0; }
.jam-dir__bar { display: flex; gap: 0.75rem; align-items: center; margin: 1rem 0 0.75rem; flex-wrap: wrap; }
.jam-seg { display: inline-flex; border: 1px solid var(--rst-line); border-radius: 999px; padding: 0.15rem; background: var(--rst-surface); }
.jam-seg a { padding: 0.3rem 0.85rem; border-radius: 999px; font-size: var(--rst-fs-sm); font-weight: 500; color: var(--rst-text-muted); text-decoration: none; }
.jam-seg a:hover { color: var(--rst-text); }
.jam-seg a[aria-current="page"] { background: var(--rst-accent-soft); color: var(--rst-accent-strong); }

.jam-dir__search { flex: 1; min-width: 14rem; display: flex; align-items: center; gap: 0.5rem; padding: 0 0.8rem; border: 1px solid var(--rst-line); border-radius: 999px; background: var(--rst-surface); color: var(--rst-text-faint); }
.jam-dir__search:focus-within { border-color: var(--rst-accent); color: var(--rst-text-muted); }
.jam-dir__search input { flex: 1; border: 0; background: none; padding: 0.5rem 0; font: inherit; font-size: var(--rst-fs-sm); color: var(--rst-text); outline: none; min-width: 0; }
.jam-dir__list { list-style: none; margin: 0; padding: 0; border: 1px solid var(--rst-line); border-radius: 12px; background: var(--rst-surface); overflow: hidden; }
.jam-dir__list li + li { border-top: 1px solid var(--rst-line); }
.jam-dir__row { display: flex; align-items: center; gap: 0.9rem; padding: 0.7rem 1rem; text-decoration: none; color: inherit; }
.jam-dir__row:hover { background: var(--rst-surface-2); }
.jam-dir__row:focus-visible { outline: 2px solid var(--rst-accent); outline-offset: -2px; }
.jam-dir__main { flex: 1; min-width: 0; display: grid; gap: 0.1rem; }
.jam-dir__main strong { font-weight: 600; }
.jam-dir__meta { color: var(--rst-text-muted); font-size: var(--rst-fs-sm); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.jam-dir__side { flex: none; display: inline-flex; align-items: center; gap: 0.6rem; }
.jam-dir__side time { font-size: var(--rst-fs-sm); color: var(--rst-text-faint); font-variant-numeric: tabular-nums; min-width: 3rem; text-align: right; }
/* One person: who they are beside what the team knows about them,
   then what the team can do, then their conversations across the
   whole width. */
.jam-person__crumb { margin: 0 0 0.75rem; font-size: var(--rst-fs-sm); }
.jam-person__crumb a { display: inline-flex; align-items: center; gap: 0.3rem; color: var(--rst-text-muted); text-decoration: none; }
.jam-person__crumb a:hover { color: var(--rst-accent-strong); }
.jam-person__head { display: flex; gap: 1.1rem; align-items: flex-start; margin: 0; min-width: 0; }
.jam-av--xl { width: 4.25rem; height: 4.25rem; font-size: 1.3rem; }
.jam-person__name { font-weight: 600; font-size: 1.9rem; line-height: 1.15; margin: 0 0 0.2rem; letter-spacing: -0.01em; text-wrap: balance; }
.jam-person__facts { margin: 0; font-size: var(--rst-fs-sm); display: flex; flex-wrap: wrap; align-items: center; gap: 0.4rem; }
.jam-person__facts a { color: var(--rst-text-muted); text-decoration: none; }
.jam-person__facts a:hover { color: var(--rst-accent-strong); }
.jam-person__actions [rst-btn~="primary"] { display: inline-flex; align-items: center; gap: 0.45rem; padding: 0.38rem 0.85rem 0.38rem 0.7rem; font-size: var(--rst-fs-sm); }
.jam-person__actions [rst-btn~="primary"] .jam-mi { color: inherit; }
.jam-person__top { display: grid; grid-template-columns: minmax(0, 1fr) 19rem; gap: 2rem; align-items: start; margin: 0 0 1.1rem; }
.jam-person__id { min-width: 0; }
.jam-person__title { min-width: 0; }
.jam-person__title-summary { list-style: none; cursor: text; display: flex; align-items: baseline; gap: 0.5rem; min-width: 0; }
.jam-person__title-summary::-webkit-details-marker { display: none; }
.jam-person__title-summary:hover .jam-thread__pencil, .jam-person__title-summary:focus-visible .jam-thread__pencil { opacity: 1; }
.jam-person__title[open] > .jam-person__title-summary { display: none; }
.jam-person__title-summary .jam-person__name { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.jam-person__rename { margin: 0 0 0.5rem; }
.jam-person__name--input { display: block; width: 100%; box-sizing: border-box; padding: 0 0 0.15rem; border: 0; border-bottom: 2px solid var(--rst-accent); border-radius: 0; background: transparent; color: var(--rst-text); outline: none; }
.jam-person__convs { margin-top: 1.6rem; }
.jam-person__h2 { font-weight: 600; font-size: 1.15rem; margin: 0 0 0.6rem; }
.jam-person__panel { border: 1px solid var(--rst-line); border-radius: 12px; background: var(--rst-surface); padding: 0 1.5rem; }
.jam-person__panel .jam-rows { margin: 0; }
.jam-person__card { border: 1px solid var(--rst-line); border-radius: 12px; background: var(--rst-surface); padding: 0.9rem 1rem 0.5rem; }
/* The note's read state, matching the pane's: the note is the summary,
   the pencil follows the words, and opening puts the form in its
   place. An empty note still gives you something to click. */
.jam-person__note-summary { list-style: none; cursor: text; display: flex; align-items: flex-start; gap: 0.4rem; }
.jam-person__note-summary::-webkit-details-marker { display: none; }
.jam-person__note-summary:hover .jam-thread__pencil, .jam-person__note-summary:focus-visible .jam-thread__pencil { opacity: 1; }
.jam-person__note[open] > .jam-person__note-summary { display: none; }
.jam-person__note-text { font-size: var(--rst-fs-sm); line-height: 1.5; min-width: 0; white-space: pre-wrap; }
.jam-person__note-text--none { color: var(--rst-text-faint); font-style: italic; }
.jam-person__note .jam-thread__pencil { align-self: center; }
.jam-person__card [rst-form] { max-inline-size: none; }
.jam-person__card input, .jam-person__card textarea { box-sizing: border-box; max-width: 100%; }
@media (max-width: 860px) { .jam-person__top { grid-template-columns: 1fr; gap: 1.5rem; } }
@media (max-width: 720px) { .jam-person__head { gap: 0.8rem; } .jam-av--xl { width: 3.25rem; height: 3.25rem; font-size: 1rem; } .jam-person__name { font-size: 1.5rem; } .jam-person__panel { padding: 0 1rem; } }
.jam-compose__row--files input { padding: 0.3rem 0; }
.jam-compose__row--files { border-bottom: 0; }
/* The row the editor sits under closes the address block; no hairline. */
.jam-compose__row:has(+ .jam-editor-wrap), .jam-compose__row:has(+ noscript + .jam-editor-wrap) { border-bottom: 0; }
.jam-rule-cond { display: grid; grid-template-columns: 1fr 1fr; gap: 0.75rem; }
.jam-caps { font-size: var(--rst-fs-xs); text-transform: uppercase; letter-spacing: 0.08em; color: var(--rst-text-faint); font-weight: 600; margin: 1rem 0 0.25rem; }
.jam-badge-auto { font-size: var(--rst-fs-sm); text-transform: uppercase; letter-spacing: 0.08em; background: var(--rst-tone-neutral-bg); color: var(--rst-tone-neutral-fg); padding: 0.05rem 0.4rem; border-radius: 4px; }

/* ── The filter bar ────────────────────────────────────────────────
   One line of filter controls that gives ground as it narrows: the
   lowest-priority control leaves the bar and comes back as a submenu
   inside "More". partial_filterbar.html says why it is built this way
   and what the template declares; this is the half that decides when.

   THE CONTAINER, not the viewport. The bar's width is its column's,
   not the window's: the activity page's column stops at 52rem however
   wide the screen is, and the three panes each set their own. A media
   query would be asking the wrong element how much room there is —
   which is how these controls came to sit on two lines on a 1440px
   screen with 130px going spare. So the bar is a container and the
   ladder below asks IT.

   THE LADDER. A control's data-drop is the rung it leaves on: 1 goes
   first, 3 last, and the rungs are cumulative — at the narrowest, all
   three have gone. 45rem / 38rem / 31rem are the bar's own width, and
   they were measured rather than guessed: a six-tab segment is 446px,
   a pill 100-150px, "More" 69px, so a segment and three pills need
   about 45rem and each pill that leaves buys back roughly 7rem. A bar
   whose controls are much wider than that wants its own numbers, and
   the honest cost of doing this without script is that somebody has to
   choose them.

   A control that is not at its default keeps its place at every rung
   (.is-applied) — see partial_filterbar.html. Its spare copy stays
   hidden, and "More" appears only while a spare of that rung is really
   there to show, which is what keeps it from opening on nothing.

   With no container-query support the whole ladder is skipped: every
   control renders on the bar, "More" stays hidden, and the bar wraps —
   exactly what it did before any of this. */
.jam-fbar { container: jam-fbar / inline-size; position: relative; z-index: 2; display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem; margin: 1rem 0 1.25rem; }
/* A container is a stacking context, so the bar's own menus are
   trapped inside it — and the day headings below scroll under a
   z-index of their own. While a menu is open the bar goes above them. */
.jam-fbar:has(details[open]) { z-index: 40; }
.jam-fbar__item { flex: none; }
/* The segment never wraps its tabs; on a bar too narrow for them it
   scrolls sideways instead — and it is the one thing on the bar that
   gives up width rather than a line. flex-wrap breaks a line before it
   shrinks anything, so a segment asking for its full width would take
   "More" down with it; a flex-basis of 0 makes it ask for its minimum
   instead and then grow back into whatever the pills leave, capped at
   the width its tabs actually want. The minimum is the floor under
   that: squeezed below it — a narrow bar with a filter applied, so the
   pill saying so cannot leave either — the segment takes a line of its
   own rather than shrinking to a couple of useless tabs. */
.jam-fbar .jam-seg { flex: 1 1 0; min-width: 9rem; max-width: max-content; overflow-x: auto; scrollbar-width: none; white-space: nowrap; }
.jam-fbar .jam-seg::-webkit-scrollbar { display: none; }
.jam-fbar .jam-seg a { flex: none; padding: 0.28rem 0.7rem; }
.jam-fbar__pill { display: inline-flex; align-items: center; gap: 0.35rem; box-sizing: border-box; font-size: var(--rst-fs-sm); color: var(--rst-text); border: 1px solid var(--rst-line); border-radius: 999px; padding: 0.28rem 0.6rem 0.28rem 0.75rem; background: var(--rst-surface); white-space: nowrap; }
.jam-fbar__pill:hover { border-color: var(--rst-line-strong); }
.jam-fbar__pill:focus-visible { outline: 2px solid var(--rst-accent); outline-offset: 2px; }
.jam-fbar__what { color: var(--rst-text-muted); }
.jam-fbar__value { font-weight: 500; }
.jam-fbar__chev { color: var(--rst-text-faint); flex: none; }
.jam-fbar [open] > .jam-fbar__pill { border-color: var(--rst-accent); }
/* An applied filter says so on the bar, in words as well as in tint:
   the pill reads "labelled Billing", not a coloured "labelled". */
.jam-fbar__item.is-applied > .jam-fbar__pill { border-color: transparent; background: var(--rst-accent-soft); color: var(--rst-accent-strong); }
.jam-fbar__item.is-applied > .jam-fbar__pill .jam-fbar__what,
.jam-fbar__item.is-applied > .jam-fbar__pill .jam-fbar__chev { color: inherit; opacity: 0.7; }
.jam-fbar__menu { min-width: 12rem; }
.jam-fbar__menu .jam-dot { flex: none; }
.jam-menu-list__on .jam-mi { color: var(--rst-accent-strong); }
/* The spare copies and the button that holds them: nothing until the
   ladder says otherwise. Both selectors outrank the .jam-pop and
   .jam-sub display further down the file, which would otherwise put
   the spares back. */
.jam-fbar > .jam-fbar__more { display: none; }
.jam-fbar__more .jam-fbar__spare { display: none; }
.jam-fbar__spare > summary .jam-fbar__value { margin-left: auto; color: var(--rst-text-muted); font-weight: inherit; }
.jam-fbar__spare > summary .jam-sub__chev { margin-left: 0.45rem; }
@container jam-fbar (max-width: 45rem) {
  .jam-fbar__item[data-drop="1"]:not(.is-applied) { display: none; }
  .jam-fbar__more .jam-fbar__spare[data-drop="1"]:not(.is-applied) { display: block; }
  .jam-fbar > .jam-fbar__more:has(.jam-fbar__spare[data-drop="1"]:not(.is-applied)) { display: inline-block; }
}
@container jam-fbar (max-width: 38rem) {
  .jam-fbar__item[data-drop="2"]:not(.is-applied) { display: none; }
  .jam-fbar__more .jam-fbar__spare[data-drop="2"]:not(.is-applied) { display: block; }
  .jam-fbar > .jam-fbar__more:has(.jam-fbar__spare[data-drop="2"]:not(.is-applied)) { display: inline-block; }
}
@container jam-fbar (max-width: 31rem) {
  .jam-fbar__item[data-drop="3"]:not(.is-applied) { display: none; }
  .jam-fbar__more .jam-fbar__spare[data-drop="3"]:not(.is-applied) { display: block; }
  .jam-fbar > .jam-fbar__more:has(.jam-fbar__spare[data-drop="3"]:not(.is-applied)) { display: inline-block; }
}
/* The dates. A pill like the others, whose menu is a form rather than
   a list — .jam-pop__body lays its children out in a nowrap row, which
   is right for a menu and wrong for two fields over a pair of buttons,
   so this one is a column and lets its contents wrap. */
.jam-fbar__rangeform { flex-direction: column; align-items: stretch; gap: 0.6rem; white-space: normal; padding: 0.75rem; max-inline-size: 22rem; }
.jam-fbar__rangeform [rst-field-range] { margin: 0; }
.jam-fbar__rangeform [rst-input] { width: 100%; }
.jam-fbar__rangefoot { display: flex; align-items: center; justify-content: space-between; gap: 0.75rem; }
/* Past the last rung the panel comes down to the width of the menus
   beside it, which makes its two date fields stack rather than sit
   side by side: 8rem is field-row's own floor under each of them, so
   a cap below two of those is what makes them wrap. The panel is then
   no wider than any other menu on the bar and sits at the edge the
   same way they do. */
@container jam-fbar (max-width: 31rem) {
  .jam-fbar__range > .jam-fbar__rangeform { max-inline-size: 13rem; }
}

/* ── Folded messages and notes (Jelly's collapse) ───────────────────── */
.jam-msg { position: relative; }
.jam-msg__fold > summary, .jam-note__fold > summary { list-style: none; cursor: pointer; }
.jam-msg__fold > summary::-webkit-details-marker, .jam-note__fold > summary::-webkit-details-marker { display: none; }
.jam-msg__snip, .jam-note__snip { display: none; color: var(--rst-text-muted); font-weight: 400; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.jam-msg__fold:not([open]) .jam-msg__to { display: none; }
.jam-msg__fold:not([open]) .jam-msg__snip { display: block; }
.jam-msg__fold:not([open]) .jam-msg__from > span:first-child { display: inline; }
.jam-note__fold:not([open]) .jam-note__snip { display: block; flex: 1; min-width: 0; margin-left: 0.4rem; }
.jam-msg:has(.jam-msg__fold:not([open])) { padding-top: 0.7rem; padding-bottom: 0.7rem; box-shadow: none; }
.jam-msg:has(.jam-msg__fold:not([open])) .jam-msg__head > .jam-av { width: 1.9rem; height: 1.9rem; font-size: 0.68rem; }
.jam-msg:has(.jam-msg__fold:not([open])) .jam-msg__head { align-items: center; }
/* A head's bottom margin is the gap to the body under it. Closed,
   there is no body, and the margin becomes dead space at the foot of
   the row: one line of text with 0.35rem above it and 0.8rem below,
   which is precisely what "not vertically centred" looks like. The
   message head has zeroed it since the collapse landed; the note head
   never did. One rule for both, so the next head to fold inherits the
   answer rather than the bug. */
.jam-msg:has(.jam-msg__fold:not([open])) .jam-msg__head,
.jam-note:has(.jam-note__fold:not([open])) .jam-note__head { margin-bottom: 0; }
.jam-msg__menu { position: absolute; top: 0.75rem; right: 0.75rem; }
.jam-msg:has(.jam-msg__fold:not([open])) .jam-msg__menu { top: 0.55rem; }
.jam-msg__head { padding-right: 2.4rem; }
/* "Show N earlier messages" is an entry like the rest: a node of three
   dots on the spine, and beside it a real button — the one place in the
   thread where a person has to reach for something, so it wears the
   app's button rather than a line of grey text.
   The node is positioned, and that is the whole of why the spine no
   longer draws across it: .jam-entries::before is a positioned
   pseudo-element, so it paints above every in-flow sibling however
   opaque their background. The dots used to be an ::before on the link
   — in flow, and therefore under the line. Every other entry marks the
   spine with an absolutely positioned .jam-node, and now so does this
   one. */
.jam-hidden { position: relative; display: flex; justify-content: flex-start; padding: 0.2rem 0 0.4rem; }
.jam-node--fold { top: 0.4rem; }
.jam-hidden__dots { display: grid; place-items: center; width: 1.5rem; height: 1.5rem; border-radius: 50%; background: var(--rst-surface); border: 1px solid var(--rst-line-strong); color: var(--rst-text-faint); box-shadow: 0 0 0 3px var(--rst-bg); }
.jam-hidden__btn { font-size: var(--rst-fs-sm); padding: 0.35rem 0.85rem 0.35rem 0.7rem; }
.jam-hidden__btn .jam-mi { color: var(--rst-text-faint); }
.jam-hidden__btn:hover .jam-mi { color: inherit; }
.jam-hidden:has(.jam-hidden__btn:hover) .jam-hidden__dots, .jam-hidden:has(.jam-hidden__btn:focus-visible) .jam-hidden__dots { border-color: var(--rst-accent); color: var(--rst-accent); }

/* ── Remote images held back ───────────────────────────────────────── */
.jam-msg__images { margin: 0.25rem 0 0.75rem; font-size: var(--rst-fs-sm); }
.jam-msg__body img[data-jam-src] { display: none; }

/* ── Gmail ─────────────────────────────────────────────────────────── */
/* Google's button, per its branding: white, a hairline, the G. */
/* Reference folded away: a details whose summary is a thin-ink button
   and whose body is whatever a person setting something up needs to
   read once — what this instance is registered as, what an import
   could not map. Not the add editor, which it resembles: nothing in
   it is a form. */
.jam-aside { margin-top: 2rem; }
.jam-aside > summary { list-style: none; }
.jam-aside > summary::-webkit-details-marker { display: none; }
.jam-aside[open] > summary { background: var(--rst-surface-2); border-color: var(--rst-line); }
.jam-aside > summary .jam-mi { transition: transform 120ms; }
.jam-aside[open] > summary .jam-mi { transform: rotate(90deg); }
@media (prefers-reduced-motion: reduce) { .jam-aside > summary .jam-mi { transition: none; } }
.jam-aside__body { margin-top: 0.5rem; padding: 0.25rem 0.6rem; }
.jam-aside__body > :first-child { margin-top: 0; }
/* An external button: a door to another service, drawn the way that
   service asks — white, a hairline, its own mark in its own colours,
   the one button on the site that does not take the house palette.
   Google's guidelines set the shape; Slack and Discord wear the same
   frame so the three read as one kind of thing. */
.jam-ext-btn { display: inline-flex; align-items: center; gap: 0.75rem; padding: 0.55rem 1rem 0.55rem 0.8rem; border: 1px solid #dadce0; border-radius: 6px; background: #fff; color: #1f1f1f; font-weight: 500; text-decoration: none; font-size: var(--rst-fs-base); box-shadow: 0 1px 2px rgba(0, 0, 0, 0.08); }
.jam-ext-btn:hover { background: #f8f9fa; box-shadow: 0 1px 3px rgba(60, 64, 67, 0.3); color: #1f1f1f; }
.jam-ext-btn > svg { flex: none; width: 18px; height: 18px; }
/* A section whose one action removes something: a hairline in the
   negative colour so it is not mistaken for the sections above it,
   and its heading in that colour too. Not a red band — DESIGN.md's
   refusals hold — a quiet fence around the one thing on the page that
   cannot be undone. */
.jam-section--danger { padding: 1rem 1.25rem 1.1rem; border: 1px solid var(--rst-tone-negative-fg); border-radius: 12px; }
.jam-section--danger > h2 { color: var(--rst-tone-negative-fg); }
.jam-section--danger > .jam-lede:last-of-type { margin-bottom: 0.75rem; }
/* Two sections side by side, when neither needs the width: they stack
   on a phone. Each column is an ordinary .jam-section. */
.jam-cols { display: grid; grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr)); gap: 1.5rem 2.5rem; margin-top: 2.25rem; }
.jam-cols > .jam-section { margin-top: 0; }
.jam-check-row { display: flex; align-items: center; gap: 0.5rem; padding: 0.35rem 0; }
.jam-settings-page .jam-crumb { display: block; }
.jam-list-head__title { font-weight: 600; font-size: 1.75rem; margin: 0 0 0.25rem; }

/* ── Development sign-in ───────────────────────────────────────────── */
.jam-dev { max-width: 64rem; margin: 3rem auto 0; padding-top: 2rem; border-top: 1px dashed var(--rst-line-strong); }
.jam-dev__title { font-size: 1.25rem; margin: 0 0 0.25rem; }
.jam-dev__fresh { display: grid; gap: 0.4rem; margin: 1rem 0 1.5rem; padding: 0.9rem 1rem; border: 1px solid var(--rst-line); border-radius: 8px; background: var(--rst-surface); font-size: var(--rst-fs-sm); }
.jam-dev__fresh input { font: inherit; padding: 0.4rem 0.6rem; border: 1px solid var(--rst-line-strong); border-radius: 6px; min-width: 16rem; background: var(--rst-surface); color: inherit; }
.jam-dev__teams { display: grid; grid-template-columns: repeat(auto-fill, minmax(18rem, 1fr)); gap: 1.25rem; margin-top: 1rem; }
.jam-dev__team h3 { font-size: var(--rst-fs-sm); text-transform: uppercase; letter-spacing: 0.08em; color: var(--rst-text-faint); margin: 0 0 0.4rem; }
.jam-dev__file { text-transform: none; letter-spacing: 0; font-weight: 400; color: var(--rst-text-faint); margin-left: 0.4rem; }
.jam-dev__people { list-style: none; padding: 0; margin: 0; }
.jam-dev__person { display: flex; align-items: center; gap: 0.5rem; width: 100%; text-align: left; background: none; border: none; padding: 0.3rem 0.4rem; border-radius: 8px; font: inherit; color: inherit; cursor: pointer; }
.jam-dev__person:hover { background: var(--rst-surface); }
.jam-dev__person .jam-muted { font-size: var(--rst-fs-xs); }
.jam-flash { position: relative; }
/* The toast: what just happened, and the way back out of it, on a
   raised sheet above the panes. It leaves on its own — a CSS
   animation, so it leaves with scripts off too, and shortcuts.js
   takes the node away when the animation ends so the z key stops
   undoing something nobody can see. It is the app's one other moment
   of motion; a reader who asked for less gets the sheet without the
   travel, and still gets it going away. */
.jam-toast { position: fixed; z-index: 60; left: 1rem; right: 1rem; bottom: 1.5rem; margin-inline: auto; width: fit-content; display: flex; align-items: center; gap: 0.9rem; max-width: min(34rem, 100%); padding: 0.6rem 0.7rem 0.6rem 1rem; background: var(--rst-surface); border: 1px solid var(--rst-line); border-radius: 12px; box-shadow: 0 8px 24px hsl(240 5% 12% / 0.16), 0 2px 6px hsl(240 5% 12% / 0.08); animation: jam-toast-in 200ms cubic-bezier(0.2, 0.8, 0.2, 1) both, jam-toast-out 240ms ease-in 9s forwards; }
.jam-toast__text { margin: 0; font-size: var(--rst-fs-sm); color: var(--rst-text); }
.jam-toast__undo { margin: 0; flex: none; }
.jam-toast__undo button { display: inline-flex; align-items: center; padding: 0.3rem 0.7rem; border: 0; border-radius: 8px; background: var(--rst-accent-soft); color: var(--rst-accent-strong); font: inherit; font-size: var(--rst-fs-sm); font-weight: 600; cursor: pointer; }
.jam-toast__undo button:hover { background: var(--rst-accent); color: var(--rst-on-accent); }
.jam-toast__undo .jam-key { border-color: currentColor; opacity: 0.65; }
@keyframes jam-toast-in { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }
@keyframes jam-toast-out { from { opacity: 1; transform: none; } to { opacity: 0; transform: translateY(4px); visibility: hidden; } }
@keyframes jam-toast-gone { to { opacity: 0; visibility: hidden; } }
@media (prefers-reduced-motion: reduce) {
  .jam-toast { animation: jam-toast-gone 1ms linear 9s forwards; }
}
.jam-chip--box { background: var(--rst-surface-2); border: 1px solid var(--rst-line); color: var(--rst-text-muted); }
.jam-pop__label { font-size: var(--rst-fs-sm); min-width: 6rem; }

/* ── Select mode / bulk actions ────────────────────────────────────── */
.jam-list-head { display: flex; align-items: baseline; justify-content: space-between; gap: 0.75rem; min-height: calc(var(--jam-head-row) + var(--jam-head-gap)); }
/* Drawn only where there is no pointer (see "Selecting with no
   pointer"): thin ink beside the heading, lit while the list is open
   for picking, so the switch says which side of it you are on. */
.jam-pickbtn { display: none; align-self: center; flex: none; align-items: center; box-sizing: border-box; min-height: 1.7rem; margin-bottom: var(--jam-head-gap); padding: 0.2rem 0.7rem; border: 1px solid var(--rst-line); border-radius: 999px; background: var(--rst-surface); color: var(--rst-text-muted); font-size: var(--rst-fs-sm); font-weight: 500; cursor: pointer; }
body:has(#jam-pick:checked) .jam-pickbtn { background: var(--rst-accent-soft); border-color: transparent; color: var(--rst-accent-strong); }
/* The views pane's heading stands over its own list of links, so its
   text starts where their text starts — the link's own padding, not
   the pane's edge. */
.jam-side-head { display: flex; align-items: baseline; min-height: calc(var(--jam-head-row) + var(--jam-head-gap)); padding-left: 0.6rem; }
/* Selecting is not a mode: the bar is in the page from the start and
   shows itself the moment something is checked, taking the heading's
   place. CSS does it, so it happens with scripts off too. */
.jam-bulk { display: contents; }
.jam-bulkbar { display: none; }
.jam-main:has(.jam-selbox input:checked) .jam-bulkbar { display: flex; }
.jam-main:has(.jam-selbox input:checked) .jam-list-head { display: none; }
.jam-bulkbar { flex-wrap: nowrap; align-items: center; gap: 0.4rem 0.6rem; box-sizing: border-box; min-height: var(--jam-head-row); padding: 0.3rem 0.75rem; margin: 0 0 var(--jam-head-gap); background: var(--jam-note); border-radius: 12px; }
.jam-bulkbar__close { display: inline-grid; place-items: center; width: 1.8rem; height: 1.8rem; border-radius: 50%; color: var(--jam-note-ink); }
.jam-bulkbar__close:hover { background: var(--rst-surface); }
.jam-bulkbar__count { font-size: var(--rst-fs-base); }
.jam-bulkbar__scope { font-size: var(--rst-fs-sm); color: var(--jam-note-ink); text-decoration: underline; text-underline-offset: 2px; }
.jam-bulkbar > * { flex: none; }
/* After the blanket flex: none, or it would never grow: the gap that
   holds Clear apart from the verbs it undoes. */
.jam-bulkbar__grow { flex: 1; }
/* The bar never clips: Label and ⋯ hang their menus below it, and an
   overflow of anything but visible on either axis clips both. The bar
   has no scroll to lose — it sheds verbs until it fits, which
   TestBrowserSelectingDoesNotMoveTheList holds it to at every width. */
/* A narrower window sheds the second-rank verbs rather than wrapping:
   Read and Unread move into the ⋯ menu, and the bar has to stay
   exactly as tall as the heading it replaces. The two live in both
   places in the markup and each width shows one of them — a menu that
   repeats what is already a button beside it is a menu of noise. */
@media (max-width: 1000px) { .jam-bulkbar__spare, .jam-bulkbar__scope { display: none; } }
/* Over-qualified on purpose: the menu item rule sets display, and at
   one class this would lose to it — a width that has already put Read
   in the bar would get it in the menu as well. Anything that takes a
   menu item away has to outrank the rule that draws one. */
@media (min-width: 1001px) { .jam-pop__body--col .jam-bulkbar__dup[rst-btn] { display: none; } }
.jam-bulkbar [rst-btn] { font-size: var(--rst-fs-sm); padding: 0.3rem 0.8rem; background: var(--rst-surface); }
.jam-pop__body--col .jam-danger { color: var(--rst-danger, #c62828); }
.jam-bulkpop__head { margin: 0.1rem 0 0.25rem; padding: 0.3rem 0.65rem 0; font-size: var(--rst-fs-xs); font-weight: 600; letter-spacing: 0.05em; text-transform: uppercase; color: var(--rst-text-muted); }
.jam-bulkpop .jam-mrow__name { font-weight: 450; }
.jam-key { font: inherit; font-size: var(--rst-fs-xs); line-height: 1.5; color: var(--rst-text-faint); border: 1px solid var(--rst-line); border-bottom-width: 2px; border-radius: 4px; padding: 0 0.5rem; margin-left: 0.35rem; }
.jam-act-btn .jam-key, .jam-bulkbar .jam-key { border-color: var(--rst-line-strong); }
html:not(.js) .jam-key { display: none; }
/* A copy button is a script's promise; without one the text is there
   to select, and the button would be a lie. */
html:not(.js) [data-copy-target] { display: none; }
.jam-token-line { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem; margin: 0.5rem 0 0; }
/* No pointer, most likely no keyboard: the hint would be furniture. */
@media (hover: none) { .jam-key { display: none; } }
/* The mark column. Every triage row reserves it, so nothing shifts
   sideways when a checkbox appears: it holds the unread dot at rest,
   and the checkbox under the pointer, under the keyboard cursor, on
   focus, when the row is selected, or once anything in the list is.
   Absolute, inside the padding the link already reserves — the row
   stays one column, and no other list that uses .jam-row is touched. */
.jam-row { position: relative; }
.jam-rows--pick > .jam-row > .jam-row__link { padding-left: calc(var(--jam-row-bleed) + 1.9rem); }
/* On the subject — the line the eye reads the row by — and measured
   from the bottom, never the top. The first line is as tall as
   whatever sits in it (an assignee's avatar, a mailbox chip, or
   nothing), so an offset from the top lands somewhere different on
   every row; below the subject there is only the snippet, one line of
   one size, so the sum is exact: the padding, the snippet, the gap
   and half a subject line, less half this box. */
/* The mark column is a control, not prose, and it has to say so. To the
   browser a <label> holding a checkbox is still text, and shift-click is
   its own "extend the selection" gesture, acting on mousedown: a
   shift-click meant to pick a range swept every row between the two
   points into a highlight instead — and then, because the gesture
   counted as selecting, the label's forwarding to the input was skipped,
   so the box never ticked and the range never ran. One cause, both
   symptoms. Measured, not guessed (issue #10): the square is 1.5rem of
   label around a 1.05rem input with the 0.45rem dot painted over its
   middle, so dead centre — where the eye aims — is the dot, and only an
   off-centre hit ever reached the input. That is the whole of the
   issue's "sometimes". CSS rather than a mousedown handler because the
   browser does this with scripts off too, and there the box still has to
   tick. */
.jam-selbox { user-select: none; -webkit-user-select: none; position: absolute; left: 0; bottom: calc(var(--jam-row-below) + var(--rst-fs-sm) * var(--jam-row-lh) + var(--jam-row-gap) + var(--rst-fs-base) * var(--jam-row-lh) / 2 - 0.85rem); z-index: 1; display: grid; place-items: center; width: 1.7rem; height: 1.7rem; }
.jam-selbox input { grid-area: 1 / 1; accent-color: var(--rst-accent); width: 1.05rem; height: 1.05rem; margin: 0; cursor: pointer; opacity: 0; }
.jam-selbox__dot { grid-area: 1 / 1; width: 0.45rem; height: 0.45rem; border-radius: 50%; background: var(--rst-accent); opacity: 0; }
.jam-row--unread .jam-selbox__dot { opacity: 1; }
.jam-row:hover .jam-selbox input,
.jam-row--cursor .jam-selbox input,
.jam-selbox input:focus,
.jam-selbox input:checked,
.jam-main:has(.jam-selbox input:checked) .jam-selbox input { opacity: 1; }
.jam-row:hover .jam-selbox__dot,
.jam-row--cursor .jam-selbox__dot,
.jam-selbox:focus-within .jam-selbox__dot,
.jam-main:has(.jam-selbox input:checked) .jam-selbox__dot { opacity: 0; }
/* ── Selecting with no pointer ───────────────────────────────────────
   The mark gives way to a checkbox under the pointer. A finger has no
   under: there is nothing to hover with, so this used to answer by
   simply drawing every checkbox, always — a column of empty boxes down
   the left of the mail, and the unread dot they take the place of
   never shown at all.

   The door is "Select", in the list's own heading: a label over a
   checkbox, the same no-script switch the menu and the search box use.
   Inside it the marks are boxes; outside it they are the dot again,
   exactly as they are on a screen with a pointer at rest. It is still
   not a mode anywhere there is a pointer to reveal a mark with, and
   the moment anything is checked the rule above shows the boxes
   whether the door was used or not — so a selection that survives a
   reload, or one made with no script at all, is still visible.

   The switch is inside the bulk form, so Clear closes the door on the
   way out; with nothing selected the heading is back and pressing
   Select again is the way out. And a list with no rows in it has
   nothing to select, so it is not offered.

   Two arms, because neither question alone is the whole of it. No
   hover is the real reason — a device that cannot reveal a mark needs
   a door to it, at any size. Narrow is the second because that is
   where the app is already in its phone shape, and because a browser
   does not always admit to having a pointer: a headless one answers
   (hover: none) for every window it opens. */
@media (hover: none), (max-width: 720px) {
  .jam-pickbtn { display: inline-flex; }
  .jam-main:not(:has(.jam-rows--pick > .jam-row)) .jam-pickbtn { display: none; }
  .jam-main:has(#jam-pick:checked) .jam-selbox input { opacity: 1; }
  .jam-main:has(#jam-pick:checked) .jam-selbox__dot { opacity: 0; }
  /* With the door shut and nothing ticked, the column holds a dot and
     nothing else, and a dot does not need two rem of the row. The
     subject, the preview and the claim take the space back, starting
     where the heading starts, and the column narrows to the bleed the
     rows already keep at the pane's edge — the dot rides in that
     gutter, and a pointer that finds the box there finds one that fits
     inside it. Open the door, or tick anything, and the column is its
     full width again (issue #113). */
  .jam-main:not(:has(#jam-pick:checked, .jam-selbox input:checked)) .jam-rows--pick > .jam-row > :is(.jam-row__link, .jam-row__claim) { padding-left: var(--jam-row-bleed); }
  .jam-main:not(:has(#jam-pick:checked, .jam-selbox input:checked)) .jam-selbox { left: calc(-1 * var(--jam-row-bleed)); width: var(--jam-row-bleed); }
}
/* The bulk bar's verbs are words, and each carries a drawing only for
   the phone: a count and five words are wider than a phone, and the bar
   ran its last button off the right-hand edge as soon as a team had a
   second mailbox (issue #113). There the drawing is the button and the
   word stays its name, the way the conversation's own bar does it. */
.jam-bulkbar [rst-btn] > .jam-mi { display: none; }
@media (max-width: 720px) {
  .jam-bulkbar { gap: 0.35rem; padding: 0.15rem 0.5rem 0.15rem 0.75rem; }
  /* Named one by one rather than as every button in the bar: Read and
     Unread are buttons here too, hidden below 1000px by a lighter rule
     that a display written for all of them would undo. */
  .jam-bulkbar > [rst-btn]:not(.jam-bulkbar__spare), .jam-bulkbar > .jam-pop > summary[rst-btn] { display: inline-flex; align-items: center; justify-content: center; box-sizing: border-box; min-width: 24px; min-height: 24px; padding: 0.2rem 0.4rem; }
  .jam-bulkbar [rst-btn] > .jam-mi { display: block; flex: none; }
  .jam-bulkbar__word { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
}
/* On a phone the mark is found from the TOP of the row instead. The
   measure above counts up from the bottom, which is exact while the
   last line is one line — and on a phone the last line carries the
   labels as well as the preview, so a heavily labelled conversation
   wraps them and the row grows downwards. Line one is held to the
   height of a face whether the row has one or not, so counting down
   from the top is the measure that cannot move. */
@media (max-width: 720px) {
  .jam-row__top { min-height: 1.65rem; }
  .jam-selbox { top: calc(var(--jam-row-pad-y) + 1.65rem + var(--jam-row-gap) + var(--rst-fs-base) * var(--jam-row-lh) / 2 - 0.85rem); bottom: auto; }
}
.jam-row--selected .jam-row__link { background: var(--jam-note); }
/* The ··· on a row: Jelly's menu. Sits beside the link (an interactive
   element cannot live inside one), shows on hover, focus or when open. */
/* The button sits in the link's bleed (the gutter the row's hover
   background runs into), 0.75rem in from the pane's edge, on the
   row's first line; the link keeps 3rem clear so the time never
   runs under it. The summary itself is a plain block — WebKit puts a
   summary's text in its marker box when the summary is a grid — and
   the circle is a span inside it. */
.jam-row { --jam-row-bleed: 1.5rem; }
.jam-row__menu { position: absolute; top: 0.42rem; right: calc(0.75rem - var(--jam-row-bleed)); z-index: 2; }
.jam-row__menu[open] { z-index: 20; }
.jam-row__menu .jam-pop__body { right: 1rem; }
.jam-row:has(.jam-row__menu[open]) { z-index: 20; }
.jam-row__more { list-style: none; display: block; cursor: pointer; border-radius: 50%; }
.jam-row__more::-webkit-details-marker { display: none; }
.jam-more-btn { display: grid; place-items: center; width: 1.75rem; height: 1.75rem; border-radius: 50%; border: 1px solid var(--rst-line); background: var(--rst-surface); color: var(--rst-text-faint); box-shadow: 0 1px 2px hsl(240 5% 12% / 0.06); transition: color 120ms, background-color 120ms, border-color 120ms; }
.jam-row__more:hover .jam-more-btn { color: var(--rst-text); background: var(--rst-surface-2); border-color: var(--rst-line-strong); }
.jam-row__menu[open] .jam-more-btn { color: var(--rst-accent-strong); background: var(--rst-accent-soft); border-color: transparent; }
.jam-row__more:focus-visible { outline: none; }
.jam-row__more:focus-visible .jam-more-btn { outline: 2px solid var(--rst-accent); outline-offset: 2px; }
.jam-rows > .jam-row > .jam-row__link { padding-right: 3rem; }
/* "I got this" on a row (issue #46). It is the row's call to action,
   so it is drawn as one: filled in the accent, under the snippet, its
   left edge on the same line the subject and the snippet start from.
   An outlined pill in the right-hand gutter — which is where this sat
   first — reads as a fourth quiet control beside the ···, not as the
   thing to press.
   It is a block in the row's own flow rather than something floating
   over the link, so the row grows by exactly one button, and only the
   rows that offer the claim grow at all. It carries the link's bleed
   and the link's hover, because it is part of the same row and has to
   light up with it. */
.jam-row__claim { box-sizing: border-box; height: var(--jam-claim-h); margin: 0 calc(-1 * var(--jam-row-bleed)); padding: 0.3rem var(--jam-row-bleed) var(--jam-row-pad-y); }
.jam-rows--pick > .jam-row > .jam-row__claim { padding-left: calc(var(--jam-row-bleed) + 1.9rem); }
.jam-row:has(> .jam-row__claim) > .jam-row__link { padding-bottom: 0; }
/* One row, one hover. The link's own :hover cannot light the claim
   block — the pointer is over the button, not over the link — and the
   claim block's own :hover cannot light the link, so lighting them
   separately made the pointer paint whichever half it was over and the
   row read as two things. On a row that carries the claim, the hover
   is the ROW's and both halves take it. The cursor and the selection
   already worked this way: each of them is a class on the row that
   both halves answer to. */
.jam-row:has(> .jam-row__claim):hover > .jam-row__link,
.jam-row:has(> .jam-row__claim):hover > .jam-row__claim { background: var(--rst-surface-2); }
.jam-row--cursor > .jam-row__claim { background: var(--rst-surface-2); }
.jam-row--selected > .jam-row__claim { background: var(--jam-note); }
.jam-claim-btn { display: inline-flex; align-items: center; justify-content: center; height: 1.7rem; padding: 0 0.85rem; border: 1px solid var(--rst-accent); border-radius: 999px; background: var(--rst-accent); font: inherit; font-size: var(--rst-fs-sm); font-weight: 600; line-height: 1; white-space: nowrap; color: var(--rst-on-accent); cursor: pointer; }
.jam-claim-btn:hover { background: var(--rst-accent-strong); border-color: var(--rst-accent-strong); }
.jam-claim-btn:focus-visible { outline: 2px solid var(--rst-accent); outline-offset: 2px; }
/* The claim in the conversation's action bar is the bar's one
   coloured verb: everything else there is a state change, this is
   the one that puts your name on it. */
.jam-act-btn--claim { color: var(--rst-accent-strong); font-weight: 600; }
.jam-act-btn--claim .jam-mi { color: var(--rst-accent-strong); }
.jam-act-btn--claim:hover { background: var(--rst-accent-soft); border-color: transparent; }
/* The pickers repeated inside More, for a bar too small to carry them.
   Drawn on no screen by default: where the bar has room, the menu
   beside it saying the same three things again is clutter. */
.jam-actions-bar__spare { display: none; }
.jam-menu-list { flex-direction: column; align-items: stretch; gap: 0; min-width: 16rem; padding: 0.35rem; white-space: nowrap; }
.jam-menu-list form { display: block; }
.jam-menu-list hr { border: 0; border-top: 1px solid var(--rst-line); margin: 0.35rem 0; }
/* A long menu filters and scrolls inside itself rather than growing past
   the viewport: the list gets a filter box (only past ten items, from the
   template), a scrolling middle whose height shortcuts.js clamps to the
   space below the button, and a fixed footer for the manage link. */
.jam-menu-list__filter { display: block; box-sizing: border-box; width: 100%; margin: 0.15rem 0 0.35rem; padding: 0.4rem 0.6rem; font: inherit; font-size: var(--rst-fs-sm); border: 1px solid var(--rst-line); border-radius: 7px; background: var(--rst-surface); color: inherit; }
.jam-menu-list__filter::placeholder { color: var(--rst-text-faint); }
.jam-menu-list__filter:focus { outline: none; border-color: var(--rst-accent); box-shadow: 0 0 0 2px var(--rst-accent-soft); }
.jam-menu-list__scroll { overflow-y: auto; max-height: var(--jam-menu-max, 60vh); overscroll-behavior: contain; scrollbar-width: thin; padding-bottom: 0.35rem; }
.jam-menu-list__scroll form[hidden] { display: none; }
.jam-menu-list__none { display: none; margin: 0; padding: 0.4rem 0.6rem; color: var(--rst-text-muted); font-size: var(--rst-fs-sm); }
.jam-menu-list__scroll:has(> :is(form, .jam-mrow, a):not([hidden])) + .jam-menu-list__none { display: none; }
.jam-menu-list__scroll:not(:has(> :is(form, .jam-mrow, a):not([hidden]))) + .jam-menu-list__none { display: block; }
.jam-menu-list__manage { color: var(--rst-text-muted); font-size: var(--rst-fs-sm); }
.jam-menu-list__manage .jam-mi { color: var(--rst-text-muted); }
.jam-menu-list .jam-mi { flex: none; color: var(--rst-text-muted); }
.jam-menu-list .jam-menu-list__danger { color: var(--rst-danger, #c62828); }
.jam-sub > summary { list-style: none; }
.jam-sub > summary::-webkit-details-marker { display: none; }
.jam-sub__chev { margin-left: auto; color: var(--rst-text-faint); font-size: 1.1em; }
.jam-sub[open] > summary { background: var(--rst-surface-2); }
/* A submenu is a menu, not a fold: it opens beside its row, as the
   same panel the conversation header offers, rather than pushing
   the rest of the menu down the screen. It hangs off the row
   (.jam-sub is the positioned box), aligned with its top and
   overlapping the parent's padding by the gap. A row menu is
   right-aligned, so the natural side is the left one; data-fly-x
   sends it the other way when the left runs out, the way
   data-flip-x does for a top-level menu. */
.jam-sub { position: relative; display: block; }
.jam-sub > .jam-sub__body { top: -0.35rem; left: auto; right: calc(100% + 0.3rem); margin-top: 0; max-height: min(60vh, 22rem); overflow-y: auto; overscroll-behavior: contain; }
.jam-sub[data-fly-x] > .jam-sub__body { right: auto; left: calc(100% + 0.3rem); }
.jam-sub[data-fly-y] > .jam-sub__body { top: auto; bottom: -0.35rem; }
/* A phone has room for one panel, not two side by side: there the
   submenu goes back to unfolding inside the menu, which is what it
   always did and is still the right answer at that width. */
@media (max-width: 720px) {
  .jam-sub > .jam-sub__body { position: static; min-width: 0; max-height: 16rem; margin: 0 0 0.2rem 0.9rem; border: 0; border-radius: 0; box-shadow: none; padding: 0.1rem 0; background: none; }
}
/* The parent stays open behind it, so the row it came from has to go
   on looking like the way in. */
.jam-sub[open] > summary .jam-sub__chev,
.jam-sub.is-open > .jam-sub__open .jam-sub__chev { color: var(--rst-accent-strong); }
.jam-sub.is-open > .jam-sub__open { background: var(--rst-surface-2); }
/* While the panel is on its way. It is one local request, so this
   is usually never seen — which is the point: no spinner, just the
   row holding its highlight so the click is not lost. */
.jam-sub.is-loading > .jam-sub__open { cursor: progress; }
.jam-row--selected .jam-av { background: var(--rst-surface); box-shadow: inset 0 0 0 1px var(--jam-note-mid); }

/* ── Message menu, rename, split, forward ──────────────────────────── */
/* The delivery pill rides in here beside the time, so the row is let
   to wrap: a long label ("Delivered to 2 of 3") would otherwise widen
   the head's auto column and push the time off the edge. Below 2.4k the
   whole side drops to its own grid row, where wrapping costs nothing. */
.jam-msg__side { display: flex; flex-wrap: wrap; justify-content: flex-end; align-items: center; gap: 0.35rem; row-gap: 0.2rem; }
.jam-msg__more { list-style: none; display: block; cursor: pointer; border-radius: 50%; }
.jam-msg__more::-webkit-details-marker { display: none; }
.jam-msg__more:hover .jam-more-btn { color: var(--rst-text); background: var(--rst-surface-2); border-color: var(--rst-line-strong); }
.jam-msg__menu[open] .jam-more-btn { color: var(--rst-accent-strong); background: var(--rst-accent-soft); border-color: transparent; }
.jam-msg__more:focus-visible { outline: none; }
.jam-msg__more:focus-visible .jam-more-btn { outline: 2px solid var(--rst-accent); outline-offset: 2px; }
.jam-msg__menu [rst-btn] { text-decoration: none; text-align: left; }
.jam-thread__split { margin: -0.4rem 0 0.6rem; font-size: var(--rst-fs-sm); color: var(--rst-text-muted); }
.jam-elsewhere { margin: 0 0 0.75rem; padding: 0.45rem 0.8rem; border-radius: 6px; background: var(--rst-tone-warning-bg); color: var(--rst-tone-warning-fg); font-size: var(--rst-fs-sm); }
.jam-elsewhere a { color: inherit; font-weight: 600; }
.jam-rename { display: grid; gap: 0.35rem; padding: 0.25rem 0.5rem 0.6rem; border-bottom: 1px solid var(--rst-line); margin-bottom: 0.35rem; min-width: 18rem; }
.jam-rename input { font: inherit; padding: 0.35rem 0.5rem; border: 1px solid var(--rst-line-strong); border-radius: 6px; background: var(--rst-sheet); color: inherit; }
.jam-forwarded { border-left: 2px solid var(--rst-line-strong); padding-left: 0.9rem; margin-top: 0.75rem; color: var(--rst-text); }

/* ── Send later ────────────────────────────────────────────────────── */
.jam-later summary { list-style: none; }
.jam-later summary::-webkit-details-marker { display: none; }
.jam-later__body { display: grid; gap: 0.5rem; padding: 0.75rem; min-width: 16rem; font-size: var(--rst-fs-sm); }
.jam-later__body input { font: inherit; padding: 0.35rem 0.5rem; border: 1px solid var(--rst-line-strong); border-radius: 6px; background: var(--rst-sheet); color: inherit; }
/* A quiet fact, not an alarm: when this sends, and who set it. */
.jam-scheduled { flex: 1; margin: 0; padding: 0.55rem 0.85rem; border: 1px solid var(--rst-line); background: var(--rst-surface-2); border-radius: 10px; color: var(--rst-text); font-size: var(--rst-fs-sm); display: flex; flex-wrap: wrap; gap: 0.35rem 0.6rem; align-items: center; }
.jam-scheduled time { font-weight: 600; }
.jam-scheduled [rst-btn] { margin-left: auto; font-size: var(--rst-fs-sm); }
.jam-msg__via { font-size: var(--rst-fs-xs); color: var(--rst-text-faint); margin-left: 0.4rem; }

/* ── Team activity ─────────────────────────────────────────────────── */
/* A feed read by day: what to show, whose it is and which label it is
   on as a filter bar (.jam-fbar, above); then each day's entries — a
   face wearing a badge for the verb, the sentence with the
   conversation as a quiet link, an excerpt, the time. */
.jam-activity { width: 100%; padding-bottom: 3rem; }
.jam-feed__day { margin: 0 0 1.25rem; }
.jam-feed__date { font-weight: 600; font-size: 1.05rem; margin: 0 0 0.25rem; padding: 0.4rem 0; position: sticky; top: 0; background: var(--rst-bg); z-index: 1; display: flex; align-items: center; gap: 0.75rem; }
.jam-feed__date::after { content: ""; flex: 1; height: 1px; background: var(--rst-line); }
.jam-feed { list-style: none; padding: 0; margin: 0; }
/* No rule between rows: the avatars, verbs and day headers carry the
   rhythm, and the sheets set replies and notes apart on their own. */
.jam-feed__item { display: grid; grid-template-columns: 2.2rem minmax(0, 1fr) auto; gap: 0.85rem; align-items: start; padding: 0.8rem 0.25rem; }
.jam-feed__who { position: relative; display: block; width: 1.9rem; height: 1.9rem; margin-top: 0.05rem; }
.jam-feed__badge { position: absolute; right: -0.35rem; bottom: -0.35rem; width: 1.05rem; height: 1.05rem; border-radius: 50%; background: var(--rst-surface); border: 1px solid var(--rst-line); display: grid; place-items: center; color: var(--rst-text-muted); }
.jam-feed__badge .jam-mi { width: 11px; height: 11px; }
.jam-feed__item[data-verb="commented"] .jam-feed__badge { color: var(--jam-note-ink); background: var(--jam-note); border-color: var(--jam-note-line); }
.jam-feed__item[data-verb="received"] .jam-feed__badge, .jam-feed__item[data-verb="sent"] .jam-feed__badge { color: var(--rst-accent-strong); background: var(--rst-accent-soft); border-color: transparent; }
.jam-feed__body { display: grid; gap: 0.15rem; min-width: 0; }
.jam-feed__line { line-height: 1.4; }
/* Every conversation link in a feed line dresses the same, wherever
   the sentence was built. */
.jam-feed__subject, .jam-feed__line a { color: var(--rst-text); font-weight: 500; text-decoration: none; border-bottom: 1px solid var(--rst-line-strong); }
.jam-feed__subject:hover, .jam-feed__line a:hover { color: var(--rst-accent-strong); border-bottom-color: var(--rst-accent-strong); }
/* What was said is the point: a reply or a message sits on a small
   sheet, a note on a note-coloured one, two lines at most. */
.jam-feed__excerpt { display: block; margin-top: 0.35rem; padding: 0.55rem 0.85rem; border: 1px solid var(--rst-line); border-radius: 9px; background: var(--rst-surface); color: var(--rst-text); font-size: var(--rst-fs-base); line-height: 1.45; box-shadow: 0 1px 2px hsl(240 5% 12% / 0.05); }
.jam-feed__excerpt-text { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.jam-feed__item[data-verb="commented"] .jam-feed__excerpt { background: var(--jam-note); border-color: var(--jam-note-line); color: var(--jam-note-ink); }
.jam-feed__when { font-size: var(--rst-fs-sm); color: var(--rst-text-faint); font-variant-numeric: tabular-nums; padding-top: 0.15rem; }
/* The feed's two ends are .jam-pages now, like every other paged list.
   They used to be a flex row that pushed Older to the right so the
   pair kept its places when only one of them was there; the strip
   holds the place itself, by drawing the unusable end disabled rather
   than not drawing it. */
@media (max-width: 720px) {
  .jam-feed__item { grid-template-columns: 2.2rem minmax(0, 1fr); }
  .jam-feed__when { grid-column: 2; padding-top: 0; }
}

/* ── Panes ─────────────────────────────────────────────────────────────
   Two panes by default: the views, and the list. Opening a
   conversation adds a third that slides in from the right and covers
   the list, which slides over the views; each leaves 100px showing.
   A covered pane peeks out under the pointer and stays usable: its
   links click and it scrolls. The move is a cross-document view
   transition, so it needs no script; with none, the panes simply are
   where they should be. */
/* dvh, not vh: on a phone 100vh is the viewport with the browser's own
   bars retracted, so a page sized to it puts its last few centimetres
   under a toolbar that is on screen — the reply button and the people
   following, off the bottom with nothing to scroll. dvh is the height
   there actually is, and it follows the bars as they come and go. */
body { min-height: 100dvh; display: flex; flex-direction: column; margin: 0; }
.jam-top { flex: none; position: relative; z-index: 40; }
/* The flex column above is for the panes, but it applies to every
   screen, and it quietly changed what <main> means. .rst-page centres
   itself with `margin: 0 auto`, and auto margins on a flex item's
   cross axis cancel the stretch that would have filled the line: the
   content column stopped being "as wide as the shell, capped at
   64rem" and became "as wide as its own max-content, capped at
   64rem". A column sized by its contents moves whenever they change,
   and on /compose the widest thing in it is the editor toolbar — so
   the two words "Draft saved" arriving in the toolbar's status widened
   the whole screen by the width of those two words. Width comes from
   the shell; max-width still decides where it stops. Left at low
   specificity on purpose, so the screens that ask for a width of their
   own (.jam-page--wide, .jam-page--settings) still get it. */
body > main { width: 100%; }
.jam-brandbtn, .jam-menu-backdrop { display: none; }
/* ── The app menu ──────────────────────────────────────────────────
   Past the width where the header can carry the app's screens as a
   row of links, the brand at the top left becomes the control that
   opens them. Label over a checkbox, so it works with scripts off;
   the backdrop is a second label over the same checkbox, so a tap
   anywhere else closes it.

   It replaces nothing on a wide screen — there the links are simply
   in the header. On a narrow one they were hidden and handed to the
   list's own views drawer, which only the inbox and a conversation
   have. So on Settings, Profile, Activity, Contacts and Compose the
   header offered a button that flipped a checkbox no rule was
   listening to, and the person had no way off the screen: that is
   issue #25, and this is where it is answered.

   Nothing between the panel and the viewport may clip it: the header
   carries no overflow of its own, and the panel hangs out of it. */
@media (max-width: 720px) {
  .jam-brand { display: none; }
  .jam-brandbtn { display: inline-flex; }
  .jam-menu-backdrop { display: block; position: fixed; inset: 0; z-index: 30; background: rgba(20, 20, 24, 0.25); opacity: 0; pointer-events: none; transition: opacity 160ms; }
  body:has(#jam-menu:checked) .jam-menu-backdrop { opacity: 1; pointer-events: auto; }
  /* The header is already the stacking context over the panes (z 40),
     so the panel needs no height of its own in the page's stack — and
     the backdrop sits below the header on purpose, or it would cover
     the very control that closes it. */
  body:has(#jam-menu:checked) .jam-top .jam-nav {
    display: flex; flex-direction: column; align-items: stretch; gap: 0; overflow: visible;
    position: absolute; top: 100%; left: 0.75rem; z-index: 5; margin: 0.35rem 0 0;
    min-width: 12rem; padding: 0.35rem; box-sizing: border-box;
    border: 1px solid var(--rst-line); border-radius: 12px;
    background: var(--rst-surface); box-shadow: var(--rst-shadow-pop);
  }
  body:has(#jam-menu:checked) .jam-top .jam-nav a { padding: 0.5rem 0.7rem; border-radius: 8px; }
}
/* The views button opens the pane that holds the views, so it belongs
   to the screens that have one and to no others. */
body:not(:has(.jam-panes)) .jam-views-btn { display: none; }
/* ── The header on a phone ────────────────────────────────────────────
   The mail is what the screen is for, and the header was taking two
   rows of it before any of it showed: the mark, the menu, an avatar, a
   button saying New message in words, and a search box across the
   whole width whether anyone was searching or not.

   One row now. The mark stays at the top left, where issue #62 put it
   and where it is still the way into the app's screens; the views,
   the search and New message are the drawings they already carried
   beside their words, each one named. The field itself is one tap
   away — and it opens itself whenever it already holds a query, which
   is the only time somebody reading a list wants to see it. */
@media (max-width: 720px) {
  .jam-top { gap: 0.4rem; padding: 0.5rem 0.75rem; }
  .jam-findbtn { display: inline-flex; order: 3; margin-left: auto; }
  .jam-top__compose { order: 4; padding: 0.4rem; }
  .jam-top__compose-mark { display: block; }
  .jam-top__compose-word { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
  .jam-menu { order: 5; margin-left: 0; }
  .jam-top .jam-search { order: 9; display: none; flex-basis: 100%; max-width: none; margin: 0.15rem 0 0; }
  body:has(#jam-find:checked) .jam-top .jam-search,
  .jam-top:has(.jam-search input:not(:placeholder-shown)) .jam-search { display: block; }
  /* On a page of results the box is already open and holds the query,
     so the button that opens it has nothing left to do: it goes, rather
     than standing there unlit over an open box. */
  .jam-top:has(.jam-search input:not(:placeholder-shown)) .jam-findbtn { display: none; }
}
/* A short screen — a phone on its side — has no height to spare for
   a header at all. It folds into the brand, a small pill at the top
   left; tapping it opens the whole header as a menu over the page,
   and the panes have the height to themselves. The same checkbox and
   the same button as above, so there is one control to learn.

   Gated on the button's own presence: signed out there is no menu to
   open, and a header folded into a button that is not there is a
   header nobody can see. */
@media (max-height: 560px) and (max-width: 1100px) {
  body:has(.jam-brandbtn) .jam-top { position: fixed; top: 0.45rem; left: 0.6rem; z-index: 50; width: min(22rem, calc(100vw - 1.2rem)); padding: 0; border: 0; background: none; flex-direction: column; align-items: stretch; gap: 0; max-height: calc(100dvh - 1rem); overflow: auto; pointer-events: none; }
  body:has(.jam-brandbtn) .jam-top > * { display: none; }
  /* The pill: out of the header's flow, so folding the header leaves
     it exactly where it was. */
  body:has(.jam-brandbtn) .jam-top > .jam-brandbtn { display: inline-flex; position: fixed; top: 0.45rem; left: 0.6rem; z-index: 52; pointer-events: auto; margin: 0; padding: 0.2rem 0.6rem 0.2rem 0.45rem; border: 1px solid var(--rst-line); border-radius: 999px; background: var(--rst-surface); box-shadow: 0 1px 2px hsl(240 5% 12% / 0.08); }
  body:has(#jam-menu:checked) .jam-top > .jam-brandbtn { border-color: transparent; }
  /* Open, the header is the panel and the pill is still the brand on
     top of it: the link home and the views button would be a second
     brand and a button with nowhere to put its pane. */
  body:has(#jam-menu:checked) .jam-top > .jam-brand,
  body:has(#jam-menu:checked) .jam-top > .jam-views-btn { display: none; }
  .jam-menu-backdrop { display: block; position: fixed; inset: 0; z-index: 48; background: rgba(20, 20, 24, 0.25); opacity: 0; pointer-events: none; transition: opacity 160ms; }
  body:has(#jam-menu:checked) .jam-menu-backdrop { opacity: 1; pointer-events: auto; }
  body:has(#jam-menu:checked) .jam-top { pointer-events: auto; padding: 3rem 0.75rem 0.75rem; border: 1px solid var(--rst-line); border-radius: 14px; background: var(--rst-surface); box-shadow: var(--rst-shadow-pop); gap: 0.5rem; }
  body:has(#jam-menu:checked) .jam-top > * { display: flex; }
  /* Folded, the nav is a section of the open header rather than a
     panel hanging off it — the header is the panel. */
  body:has(#jam-menu:checked) .jam-top .jam-nav { position: static; flex-direction: column; align-items: stretch; gap: 0; margin: 0; padding: 0; min-width: 0; border: 0; border-radius: 0; background: none; box-shadow: none; }
  body:has(#jam-menu:checked) .jam-top .jam-nav a { padding: 0.5rem 0.6rem; border-radius: 8px; }
  body:has(#jam-menu:checked) .jam-top .jam-search { order: 0; margin: 0; max-width: none; flex-basis: auto; }
  /* The header IS the panel here, so New message is a button in a
     column of them and says what it does again — the drawing alone is
     for the one-row header, not for this. */
  body:has(#jam-menu:checked) .jam-top .jam-top__compose { order: 0; justify-content: center; padding: 0.4rem 0.9rem; }
  body:has(#jam-menu:checked) .jam-top .jam-top__compose-mark { display: none; }
  body:has(#jam-menu:checked) .jam-top .jam-top__compose-word { position: static; width: auto; height: auto; clip-path: none; }
  body:has(#jam-menu:checked) .jam-top .jam-findbtn { display: none; }
  body:has(#jam-menu:checked) .jam-top .jam-menu { order: 0; }
  body:has(#jam-menu:checked) .jam-top .jam-menu__panel { position: static; box-shadow: none; border: 0; padding: 0.25rem 0 0; min-width: 0; }
  /* The brand pill floats over the top left of the panes, so every
     head drops below it — together, or they stop lining up. The views
     pane's heading is one of the three now, so the inset is the
     pane-set's, not the open pane-set's.

     The pill is drawn smaller here than it is in a header, and the
     inset follows it down. On a screen this short that clearance is
     the single tallest thing above the mail — 3.4rem of it was an
     eighth of the whole window, in all three panes at once, holding
     room for a mark that does not need to be that big to be the mark.
     Shrink the pill and every head rises by the difference, still in
     one row. */
  body:has(.jam-brandbtn) .jam-panes { --jam-head-inset: 2.5rem; }
  body:has(.jam-brandbtn) .jam-top > .jam-brandbtn { top: 0.35rem; padding: 0.15rem 0.5rem 0.15rem 0.35rem; }
  body:has(.jam-brandbtn) .jam-top > .jam-brandbtn .jam-brand__mascot { height: 1.35rem; }
  body:has(.jam-brandbtn) .jam-top > .jam-brandbtn .jam-brand__word { width: 2.3rem; height: 0.92rem; }
  body:has(#jam-menu:checked) .jam-top .jam-nav { order: -1; }
  body:has(#jam-menu:checked) .jam-top .jam-menu { align-self: flex-start; }
}
.jam-panes { position: relative; flex: 1; min-height: 0; overflow: hidden; }
/* The panes are the page; nothing may scroll the document sideways. */
body:has(> .jam-panes) { overflow: hidden; }
.jam-pane { position: absolute; top: 0; bottom: 0; overflow-y: auto; overflow-x: hidden; box-sizing: border-box; background: var(--rst-bg); }
/* Scroll anchoring is wrong for a head that shrinks as you scroll. It
   exists to hold your place when something above the viewport changes
   size — and that is exactly what the head does, on purpose, every
   frame. The browser reads the head losing height as content moving
   and pulls the scroll back up to compensate, cancelling most of the
   gesture; with a subject wrapped to three lines the head shed more
   height than the scroll that drove it, the correction outran the
   input, and the pane could not be scrolled at all. Off here; the
   panes have nothing else above the fold that resizes late. The views
   pane is in it too: its head condenses on the list's scroll, and its
   own scroll now has a job (the fade under that head), so a correction
   there would cancel a gesture the same way. */
.jam-pane--nav, .jam-pane--list, .jam-pane--conv { overflow-anchor: none; }
.jam-pane--nav { left: 0; width: 15rem; padding: 0 0.75rem 3rem; border-right: 1px solid var(--rst-line); }
.jam-pane--list { --jam-list-pad-x: 1.5rem; left: 15rem; right: 0; padding: 0 var(--jam-list-pad-x) 3rem; view-transition-name: pane-list; transition: transform var(--jam-slide-ms, 320ms) var(--jam-slide-ease, cubic-bezier(0.2, 0.8, 0.2, 1)); }
/* The list slides to x=100px when a conversation opens; the conversation
   pane starts --jam-list-strip further right, which is exactly how much
   of the list stays visible (the dev menu tunes it). */
.jam-pane--conv { left: calc(100px + var(--jam-list-strip, 100px)); right: 64px; padding: 0; background: var(--rst-bg); view-transition-name: pane-conv; transition: transform var(--jam-slide-ms, 240ms) var(--jam-slide-ease, cubic-bezier(0.2, 0.8, 0.2, 1)); }

/* ── The heads that stay ───────────────────────────────────────────────
   Scroll a pane and its head stays at the top of it: the list keeps
   saying which list this is, the conversation keeps saying which
   conversation. Both shrink as they go — the heading a few points, the
   facts down to one line, the buttons a hair tighter — and both lay a
   soft fade under themselves so the rows dissolve rather than being
   cut off at a line.

   --jam-stuck is the whole mechanism: one registered number, driven
   from 0 to 1 over the first few rem of the pane's scroll, that every
   measure above is written against. It is a scroll-driven animation,
   so there is no script and nothing to keep in sync; a browser that
   has never heard of one leaves it at 0 and gets a head that sticks at
   full size, which is the point of the exercise anyway.

   The fade is the one thing in here not written against it, and the
   distinction is worth keeping straight: condensing is about the row
   of heads, which move together whichever pane is being driven, and
   the fade is about one pane's own first row going up under its own
   head. A pane nobody has scrolled has nothing going anywhere, so
   fading its top says the reader is somewhere they are not.
   --jam-scrolled is the second number — the same shape over the same
   range, always taken from the pane it sits on — and the fade under
   the head is the only thing that reads it. */
@property --jam-stuck { syntax: "<number>"; inherits: true; initial-value: 0; }
@property --jam-scrolled { syntax: "<number>"; inherits: true; initial-value: 0; }
@keyframes jam-stuck { from { --jam-stuck: 0; } to { --jam-stuck: 1; } }
@keyframes jam-scrolled { from { --jam-scrolled: 0; } to { --jam-scrolled: 1; } }
/* The views pane is a scroller too, and now that its head fades on its
   own account it needs a timeline of its own to fade from. */
.jam-pane--nav { scroll-timeline: --jam-nav-scroll block; }
.jam-pane--list { scroll-timeline: --jam-list-scroll block; }
.jam-pane--conv { scroll-timeline: --jam-conv-scroll block; }
/* Two animations, one range: the first is how condensed the row of
   heads is, the second is how far this pane itself has gone. In the
   pane a reader is driving they are the same timeline twice; in the
   other two they come apart, which is the whole point. */
.jam-main, .jam-thread, .jam-views { animation: jam-stuck linear both, jam-scrolled linear both; animation-range: 0px 11rem; }
.jam-main { animation-timeline: --jam-list-scroll, --jam-list-scroll; }
.jam-views { animation-timeline: --jam-list-scroll, --jam-nav-scroll; }
.jam-thread { animation-timeline: --jam-conv-scroll, --jam-conv-scroll; }
/* With a conversation open the two heads have to condense together or
   they stop lining up, and the head a reader is actually driving is
   the conversation's: the list is a sliver at that point, kept to say
   which list this is and to be the way back, and nobody scrolls it.
   So while a conversation is open the list's head takes its progress
   from the conversation's scroll, and the two headings hold one
   baseline the whole way down instead of only at the ends.

   timeline-scope is what makes that possible: a named scroll timeline
   is visible to the declaring element's descendants and nowhere else,
   and the two panes are siblings. Named on the box that holds them
   both, it reaches across.

   A browser without scroll-driven animations was never in this at
   all: both heads stay at --jam-stuck 0, the same size, still level
   with each other. */
.jam-panes { timeline-scope: --jam-list-scroll, --jam-conv-scroll; }
/* Only the first of the two travels: what the reader is driving decides
   how condensed every head is, and never whether a pane they have left
   alone is showing its first row cut off. --jam-nav-scroll stays out of
   the scope above because nothing outside the views pane reads it. */
.jam-panes--open .jam-main { animation-timeline: --jam-conv-scroll, --jam-list-scroll; }
.jam-panes--open .jam-views { animation-timeline: --jam-conv-scroll, --jam-nav-scroll; }

/* One head, cut twice.
   ------------------------------------------------------------------
   The list's heading and the conversation's subject are the same
   heading in two panes standing side by side, and a reader sees them
   as one line across the app or as a mistake. So neither pane owns a
   measure: every number the two heads are built from is declared once,
   here, and read by both. Hand-tuning either one apart from the other
   is how they came to be 20px out of line in the first place.

   The declarations sit on .jam-main and .jam-thread because that is
   where --jam-stuck lives — one per pane, driven by that pane's own
   scroll — and a custom property resolves its var()s against the
   element it is declared on. Put them any higher and both heads would
   read the same, permanently unstuck, 0.

   --jam-head-inset is the exception, and deliberately so: it is the
   distance from the top of a pane to the top of its head, and it is a
   property of the *page*, not of either pane — the app bar folding
   into a floating pill moves both heads or neither. It is declared on
   .jam-panes and inherited by both. */
.jam-panes { --jam-head-inset: 1.25rem; }
.jam-main, .jam-thread, .jam-views {
  /* The type at rest and condensed; a breakpoint moves these two and
     everything below follows. */
  --jam-head-big: 1.75rem;
  --jam-head-small: 1.3rem;
  /* Nothing happens to the type over the first four fifths of the
     travel, and then all of it does. That timing is the conversation
     subject's and it is not decoration: a subject wraps, the summary's
     clamp is taking its extra lines away over that stretch, and type
     that shrank at the same time would re-wrap — a step change in the
     head's height in the middle of a gesture, which is the thing the
     whole mechanism is built to avoid. The list's heading has no lines
     to lose, but it keeps the timing anyway: two headings on one
     baseline have to shrink on one schedule or they are only aligned
     at the ends. */
  --jam-head-drop: max(0, calc((var(--jam-stuck, 0) - 0.8) * 5));
  --jam-head-size: calc(var(--jam-head-big) - (var(--jam-head-big) - var(--jam-head-small)) * var(--jam-head-drop));
  /* What both headings actually fill: one line of that type, set
     tight. Same size and same leading in both panes is most of what
     puts the two first baselines at the same height — a baseline sits
     at half-leading plus the font's ascent, and the ascent scales with
     the size, so headings of different sizes cannot be made to line up
     by padding.

     It is not all of it, because the ascent is the face's and the two
     heads are two faces on purpose: the list's heading is chrome and
     set in the sans, the subject is the conversation's own words and
     set in the serif. Two faces centred in one box are two baselines,
     and no measure here can reach that — half-leading plus ascent is
     the same arithmetic on different numbers. So the serif head is
     given the sans's ascent and descent at the @font-face, and this
     block's numbers are then true of both: --jam-serif-head, above,
     and the note in layout.html. */
  --jam-head-leading: calc(var(--jam-head-size) * 1.15);
  /* The row the heading stands in, which is taller than the line box
     it fills: in the list the bulk bar takes the heading's place, and
     a bar has controls in it. The heading sits at the top of the row
     and the slack falls below, so the bar can be as tall as it needs
     to be without moving the heading beside it. */
  --jam-head-row: calc(var(--jam-head-size) * 1.55);
  --jam-head-gap: calc(0.5rem - 0.2rem * var(--jam-stuck, 0));
  /* The air above the heading, which travels with the head because the
     head is sticky, and comes off as it condenses — never past a
     little over half of it, or a head at the top of a short pane has
     no margin left at all. */
  --jam-head-pad-t: calc(var(--jam-head-inset, 1.25rem) - min(0.6rem, var(--jam-head-inset, 1.25rem) * 0.5) * var(--jam-stuck, 0));
}

/* Both heads: opaque to the pane's edges, and a BFC so the gap under
   the heading is inside the box that sticks, not collapsed out of it
   — otherwise the bar taking the heading's place would step the list. */
.jam-list-top, .jam-thread__head, .jam-side-top { position: sticky; top: 0; display: flow-root; background: var(--rst-bg); }
/* The views pane's own padding is 0.75rem, so its head bleeds by that
   much to reach the pane's edges — the same trick as the other two,
   with the pane's number.

   No z-index, unlike the other two: they lift themselves over the row
   menus that open beside them, and this pane has none. Lifted, it
   would rise out of its pane instead — the panes carry no z-index of
   their own on a wide screen, so a positioned descendant with one
   paints above every pane that comes after it, and the conversation
   pane overlaps this one's right-hand edge. The subject's first inch
   then belonged to a head in another pane. */
.jam-side-top { margin: 0 -0.75rem; padding: var(--jam-head-pad-t) 0.75rem 0; }
.jam-list-top { z-index: 6; margin: 0 calc(-1 * var(--jam-list-pad-x, 1.5rem)); padding: var(--jam-head-pad-t) var(--jam-list-pad-x, 1.5rem) 0; }
/* Under the close, which rides the same corner, and under an open row
   menu, which has to be readable over the head it hangs beside. */
.jam-thread__head { z-index: 4; margin: 0 calc(-1 * var(--jam-conv-pad-x, 1.5rem)); padding: var(--jam-head-pad-t) var(--jam-conv-pad-x, 1.5rem) 0; }
/* The fade is the head's own shadow on the page: nothing at rest, a
   short wash of the page colour once it is holding station — and it is
   this pane's own scroll that decides, not the row of heads'. Scroll
   the conversation and all three heads condense together, because they
   are one line across the app; the list and the views keep their first
   row hard-edged, because a reader who has not scrolled them is
   looking at their top and there is nothing above it to dissolve. */
.jam-list-top::after, .jam-thread__head::after, .jam-side-top::after { content: ""; position: absolute; left: 0; right: 0; top: 100%; height: 1.4rem; background: linear-gradient(var(--rst-bg) 25%, transparent); opacity: var(--jam-scrolled, 0); pointer-events: none; }
/* Asked for less movement, the heads still stay — they just stay the
   size they started. */
@media (prefers-reduced-motion: reduce) { .jam-main, .jam-thread, .jam-views, .jam-thread__subject { animation: none; } }
/* A covered pane peeks out under the pointer: the panes above it shift
   right a little, so more of it can be read before you commit. */
.jam-panes--open:has(.jam-pane--nav:hover) .jam-pane--list { transform: translateX(min(0px, calc(100px - 15rem + var(--jam-peek, 8rem)))); transition-delay: var(--jam-peek-delay, 150ms); }
.jam-panes--open:has(.jam-pane--nav:hover) .jam-pane--conv { transform: translateX(var(--jam-peek, 8rem)); transition-delay: var(--jam-peek-delay, 150ms); }
.jam-panes--open:has(.jam-pane--list:hover) .jam-pane--conv { transform: translateX(calc(var(--jam-peek, 8rem) + 2rem)); transition-delay: var(--jam-peek-delay, 150ms); }
/* The same peek when the keyboard's focus lands on a covered pane. */
.jam-panes--open.jam-panes--peek-nav .jam-pane--list { transform: translateX(min(0px, calc(100px - 15rem + var(--jam-peek, 8rem)))); }
.jam-panes--open.jam-panes--peek-nav .jam-pane--conv { transform: translateX(var(--jam-peek, 8rem)); }
.jam-panes--open.jam-panes--peek-list .jam-pane--conv { transform: translateX(calc(var(--jam-peek, 8rem) + 2rem)); }
.jam-link--cursor { outline: 2px solid var(--jam-note-mid); outline-offset: -2px; border-radius: 6px; }
.jam-panes--still .jam-pane { transition: none; }
/* The contact: a fourth pane at the far right, folded to a 64px tab
   the full height of the window — avatar, the name running down the
   tab, and a "»»" cue — opening under the pointer or on a click
   (focus); clicking back into the conversation folds it away. */
.jam-pane--contact { right: 0; width: calc(18rem + 64px); padding: 0; display: grid; grid-template-columns: 64px minmax(0, 1fr); background: var(--rst-surface); border-left: 1px solid var(--rst-line); box-shadow: -10px 0 28px -14px rgba(40, 30, 20, 0.35); transform: translateX(18rem); transition: transform var(--jam-slide-ms, 240ms) var(--jam-slide-ease, cubic-bezier(0.2, 0.8, 0.2, 1)); view-transition-name: pane-contact; outline: none; overflow: hidden; }
/* Hovering the tab nudges the pane out by --jam-contact-nudge (the dev
   menu tunes it) as an invitation; a click, or keyboard focus, opens it. */
.jam-pane--contact:hover { transform: translateX(calc(18rem - var(--jam-contact-nudge, 40px))); }
.jam-pane--contact:focus-within, .jam-pane--contact:focus, .jam-pane--contact.jam-contact--pinned { transform: none; cursor: default; }
.jam-contact__hint { margin-top: auto; writing-mode: vertical-rl; font-size: var(--rst-fs-xs); color: var(--rst-text-faint); letter-spacing: 0.04em; white-space: nowrap; }
.jam-contact__hint-unpin, .jam-contact--pinned .jam-contact__hint-pin { display: none; }
.jam-contact--pinned .jam-contact__hint-unpin { display: inline; }
.jam-contact__tab { display: flex; flex-direction: column; align-items: center; gap: 0.9rem; padding: 1.1rem 0 1.25rem; border-right: 1px solid var(--rst-line); color: var(--rst-text-muted); cursor: pointer; }
.jam-contact__tab .jam-av--lg { width: 2.75rem; height: 2.75rem; font-size: 0.9rem; }
.jam-contact__vname { writing-mode: vertical-rl; font-weight: 600; font-size: 1.05rem; color: var(--rst-text); white-space: nowrap; max-height: 40vh; overflow: hidden; text-overflow: ellipsis; letter-spacing: 0.01em; }
.jam-contact__more { color: var(--rst-text-faint); margin-top: 0.25rem; }
.jam-contact__body { position: relative; padding: 1.25rem 1.25rem 3rem 1rem; overflow-y: auto; opacity: 0; transition: opacity 180ms ease 80ms; }
/* A quiet way out at the top right of each open pane. */
.jam-pane__close { position: absolute; top: 1rem; right: 1.25rem; border: 0; padding: 0; background: none; font: inherit; font-size: var(--rst-fs-xs); font-weight: 500; letter-spacing: 0.02em; color: var(--rst-text-faint); text-decoration: none; cursor: pointer; z-index: 5; }
/* The conversation's close rides the top of the pane, above the scroll:
   a zero-height sticky strip that lets its one word overflow. After
   the base rule, not before it — the two selectors weigh the same.

   It said height: 0 and did not mean it: with box-sizing: border-box a
   padding-top of 1rem is still 1rem of box, and that rem stood above
   the conversation and pushed all of it — head, subject and all — 16px
   below the list's heading beside it, which is most of why the two
   never lined up. The padding stays, because it is what puts the word
   where it belongs and what a click at it lands on; the negative
   margin under it is what takes those 16px back out of the flow. */
.jam-pane__close--conv { position: sticky; top: 0; right: auto; display: flex; justify-content: flex-end; height: 0; overflow: visible; padding: 1rem 1.25rem 0 0; box-sizing: border-box; margin-bottom: -1rem; }
/* That strip is zero-height on purpose — it right-aligns the link
   without taking a row of its own — so a flex item inside it has
   no height to stretch to, and the keycap arrived as a 3px sliver
   of its own borders. Only the keycap opts out, so the word
   "Close" lands exactly where it always did on the screens that
   never show the hint. */
.jam-pane__close--conv .jam-key { align-self: baseline; }
.jam-pane__close:hover { color: var(--rst-text); }
.jam-contact__close { top: 1.1rem; right: 1.1rem; }
.jam-contact__body > .jam-av--lg { display: none; }
.jam-contact__vname, .jam-contact__more { transition: opacity 140ms ease; }
/* Opening: the name running down the tab fades out as the card, with
   the name set straight, fades in. */
.jam-pane--contact:focus-within .jam-contact__body, .jam-pane--contact:focus .jam-contact__body, .jam-contact--pinned .jam-contact__body { opacity: 1; }
/* The vertical name stays through the hover nudge and only fades once
   the pane is actually open — and only after the slide has landed. */
.jam-pane--contact:focus-within .jam-contact__vname, .jam-pane--contact:focus .jam-contact__vname,
.jam-pane--contact:focus-within .jam-contact__more, .jam-pane--contact:focus .jam-contact__more, .jam-contact--pinned .jam-contact__vname, .jam-contact--pinned .jam-contact__more { opacity: 0; transition-delay: var(--jam-slide-ms, 240ms); }
.jam-contact__name { margin-top: 0.1rem; }
@media (prefers-reduced-motion: reduce) { .jam-contact__body, .jam-contact__vname, .jam-contact__more { transition: none; } }
.jam-pane--conv .jam-conv { max-width: none; grid-template-columns: minmax(0, 1fr); }
.jam-panes--open .jam-pane--list { transform: translateX(calc(100px - 15rem)); }
.jam-panes--open .jam-pane--list, .jam-pane--conv { box-shadow: -10px 0 28px -14px rgba(40, 30, 20, 0.35); border-left: 1px solid var(--rst-line); }
.jam-pane--conv .jam-conv { max-width: none; }
.jam-row--open .jam-row__link { background: var(--jam-note); }
/* On a conversation page the list pane is the way back: a link the
   size of the pane sits under the rows, so a click on anything that
   is not a conversation returns to the list. */
.jam-pane__back { position: absolute; inset: 0; z-index: 0; }
.jam-panes--open .jam-pane--list > .jam-main { position: relative; z-index: 1; pointer-events: none; }
.jam-panes--open .jam-pane--list > .jam-main a, .jam-panes--open .jam-pane--list > .jam-main button, .jam-panes--open .jam-pane--list > .jam-main input, .jam-panes--open .jam-pane--list > .jam-main select, .jam-panes--open .jam-pane--list > .jam-main label { pointer-events: auto; }
.jam-crumb { display: none; }
.jam-side { position: static; }
@view-transition { navigation: auto; }
/* A transition must never cost a click. While one runs, its
   pseudo-elements fill the viewport and take every click, and whatever
   it has captured is painted only as a snapshot, so nothing inside it
   can be hit either. Capturing the root captures the whole page: for
   the quarter-second of the default crossfade every link was dead, and
   a person clicking through the settings pages, or the nav, found the
   second click did nothing. So the root and the bar are not captured
   — only the panes and an open menu move — and clicks pass through the
   overlay to the live page beneath. Inside a sliding pane a click still
   waits for the slide; that one is visible.

   A pane the transition has captured is not there to be clicked: its
   pixels are painted in the pseudo-tree, and the live element under
   them hit-tests as nothing at all (elementFromPoint answers with the
   panes' container) for as long as ANY animation in the pseudo-tree
   runs — the group's included, whose default quarter-second runs
   whether or not the geometry changed. So a pane is captured only
   where it moves: the views never move on a wide screen and carry no
   name there, only as the phone's drawer (below); and shortcuts.js
   skips the whole transition unless a conversation is opening or
   closing, which is the only navigation that moves a pane at all.
   Before that, the views were dead to the pointer for the first three
   hundred milliseconds of every list, and a person clicking through
   them clicked everything twice. */
:root, .jam-top { view-transition-name: none; }
::view-transition { pointer-events: none; }
/* An open menu is captured apart from the bar it hangs off: its ink
   overflows the bar's box, and a navigation would stretch the joint
   snapshot into the bar's frame — the ghost-shape flash. Named on its
   own, it simply fades. (Two menus open at once abort the transition,
   which degrades to no animation, not a wrong one.) */
details[open] > .jam-menu__panel, details[open] > .jam-pop__body, details[open] > .rst-row-menu__panel, details[open] > [rst-row-menu-panel] { view-transition-name: open-menu; }
::view-transition-group(pane-list), ::view-transition-group(pane-conv), ::view-transition-group(pane-contact) { animation-duration: var(--jam-slide-ms, 320ms); animation-timing-function: var(--jam-slide-ease, cubic-bezier(0.2, 0.8, 0.2, 1)); }
/* The views and the list only move; they never crossfade. */
::view-transition-old(pane-nav), ::view-transition-old(pane-list) { display: none; }
::view-transition-new(pane-nav), ::view-transition-new(pane-list) { animation: none; }
::view-transition-new(pane-conv):only-child, ::view-transition-new(pane-contact):only-child { animation: var(--jam-slide-ms, 320ms) var(--jam-slide-ease, cubic-bezier(0.2, 0.8, 0.2, 1)) both var(--jam-enter, jam-pane-in); }
::view-transition-old(pane-conv):only-child, ::view-transition-old(pane-contact):only-child { animation: calc(var(--jam-slide-ms, 320ms) * 0.8) cubic-bezier(0.4, 0, 1, 1) both var(--jam-leave, jam-pane-out); }
@keyframes jam-pane-fade-in { from { opacity: 0; } to { opacity: 1; } }
@keyframes jam-pane-fade-out { from { opacity: 1; } to { opacity: 0; } }
@keyframes jam-pane-slide-fade-in { from { transform: translateX(40%); opacity: 0; } to { transform: none; opacity: 1; } }
@keyframes jam-pane-slide-fade-out { from { transform: none; opacity: 1; } to { transform: translateX(40%); opacity: 0; } }
@keyframes jam-pane-none { from { opacity: 1; } to { opacity: 1; } }
@keyframes jam-pane-in { from { transform: translateX(100%); } }
@keyframes jam-pane-out { to { transform: translateX(100%); } }
@media (prefers-reduced-motion: reduce) { ::view-transition-group(*), ::view-transition-old(*), ::view-transition-new(*) { animation: none !important; } .jam-pane--list, .jam-pane--conv, .jam-pane--contact { transition: none; } }
.jam-views-btn { display: none; }
.jam-side__list--pages { display: none; }
@media (max-width: 900px) {
  .jam-side { display: block; margin: 0; padding: 0; overflow: visible; }
  .jam-side__list { display: block; }
  .jam-side__title { display: block; }
  .jam-pane--nav { width: 12rem; }
  .jam-panes { --jam-head-inset: 1rem; }
  .jam-main, .jam-thread, .jam-views { --jam-head-big: 1.6rem; --jam-head-small: 1.2rem; }
  /* A smaller heading is a smaller row, and the bar stands in that
     row: its controls come down to fit, or selecting a conversation
     steps the list a pixel down the moment the bar appears. */
  .jam-bulkbar { height: var(--jam-head-row); }
  .jam-bulkbar [rst-btn] { padding: 0.15rem 0.6rem; }
  .jam-pane--list { left: 12rem; --jam-list-pad-x: 1rem; }
  .jam-row { --jam-row-bleed: 1rem; }
  .jam-row__link { padding: var(--jam-row-pad-y) 1rem; margin: 0 -1rem; }
  .jam-section-title { margin: 0 -1rem 0.25rem; padding: 0.45rem 1rem; }
  .jam-panes--open .jam-pane--list { transform: translateX(calc(100px - 12rem)); }
}
/* ── The phone: the same stack, one pane wide ─────────────────────────
   The list fills the screen and the views are a drawer off its left
   edge (the header's button flips a checkbox; no script). Opening a
   conversation covers the list, leaving a 28px sliver that is purely
   the way back — there is no pointer to peek with. The contact folds to
   a 44px tab that opens over the conversation. */
@media (max-width: 720px) {
  .jam-views-btn { display: inline-flex; align-items: center; justify-content: center; width: 2.25rem; height: 2.25rem; border-radius: 8px; color: var(--rst-text-muted); cursor: pointer; margin-right: -0.25rem; }
  .jam-views-btn:hover { background: var(--rst-surface-2); color: var(--rst-text); }
  .jam-pane--nav { left: 0; width: min(17rem, 85vw); transform: translateX(-100%); transition: transform var(--jam-slide-ms, 240ms) var(--jam-slide-ease, cubic-bezier(0.2, 0.8, 0.2, 1)); z-index: 8; box-shadow: 10px 0 28px -14px rgba(0, 0, 0, 0.35); border-right: 1px solid var(--rst-line); background: var(--rst-surface); }
  body:has(#jam-views:checked) .jam-pane--nav { transform: none; }
  /* The one place the views move: choosing a view from the open
     drawer, the transition slides it away. Named here alone, so on a
     wide screen — where it never moves — it is live throughout. */
  .jam-pane--nav { view-transition-name: pane-nav; }
  body:has(#jam-views:checked) .jam-panes::after { content: ""; position: absolute; inset: 0; background: rgba(20, 20, 24, 0.25); z-index: 7; }
  .jam-panes { --jam-head-inset: 0.75rem; }
  .jam-pane--list { left: 0; right: 0; --jam-list-pad-x: 1rem; }
  .jam-panes--open .jam-pane--list { transform: none; }
  .jam-panes--open:has(.jam-pane--nav:hover) .jam-pane--list, .jam-panes--open.jam-panes--peek-nav .jam-pane--list { transform: none; }
  /* The conversation alone: the list and the contact pane step aside;
     the crumb leads back and names the contact. */
  .jam-pane--conv { left: 0; right: 0; }
  .jam-panes--open .jam-pane--list, .jam-pane--contact { display: none; }
  .jam-crumb { display: flex; justify-content: space-between; align-items: center; gap: 1rem; margin: 0 0 0.6rem; }
  .jam-crumb a { display: inline-flex; align-items: center; gap: 0.3rem; min-height: 24px; color: var(--rst-text-muted); }
  .jam-crumb__contact { font-weight: 600; color: var(--rst-text); }
  .jam-nav { display: none; }
  .jam-side__list--pages { display: block; border-top: 1px solid var(--rst-line); padding-top: 0.5rem; margin-top: 0.5rem; }
  .jam-panes--open:has(.jam-pane--nav:hover) .jam-pane--conv, .jam-panes--open:has(.jam-pane--list:hover) .jam-pane--conv,
  .jam-panes--open.jam-panes--peek-nav .jam-pane--conv, .jam-panes--open.jam-panes--peek-list .jam-pane--conv { transform: none; }
  /* The sliver is the way back and nothing else: rows do not receive
     the tap through it. */
  .jam-panes--open .jam-pane--list > .jam-main a, .jam-panes--open .jam-pane--list > .jam-main button, .jam-panes--open .jam-pane--list > .jam-main input, .jam-panes--open .jam-pane--list > .jam-main select, .jam-panes--open .jam-pane--list > .jam-main label { pointer-events: none; }
  .jam-pane--contact { width: min(calc(18rem + 44px), calc(100% - 28px)); grid-template-columns: 44px minmax(0, 1fr); transform: translateX(calc(100% - 44px)); }
  .jam-pane--contact:hover, .jam-pane--contact:focus-within, .jam-pane--contact:focus { transform: none; }
  .jam-contact__tab { padding: 0.9rem 0 1rem; gap: 0.7rem; }
  .jam-contact__tab .jam-av--lg { width: 2rem; height: 2rem; font-size: 0.7rem; }
  .jam-contact__vname { font-size: 0.9rem; max-height: 32vh; }
  /* The conversation on a phone: the head keeps its margin, the
     messages run edge to edge, notes and activity keep to the spine. */
  .jam-pane--conv .jam-conv { --jam-conv-pad-x: 0; padding-bottom: 4rem; }
  .jam-thread__head, .jam-thread__folds { padding-left: 0.9rem; padding-right: 0.9rem; }
  .jam-thread__folds::before { left: calc(0.9rem + 0.95rem - 1px); }
  .jam-entries { padding-right: 0.9rem; }
  .jam-entries::before { left: calc(0.9rem + 0.95rem - 1px); }
  .jam-entries { padding-left: calc(0.9rem + 2.2rem); }
  .jam-msg { margin-right: -0.9rem; border-radius: 0; border-left: 0; border-right: 0; padding: 1rem 0.9rem 1.1rem; }
  .jam-entries .jam-msg { margin-left: calc(-0.9rem - 2.2rem); }
  /* .jam-thread__folds carries its own smaller padding at this width
     (below), so the sending row cancels that instead. */
  .jam-thread__folds .jam-msg { margin-left: -0.9rem; }
  .jam-msg__head { grid-template-columns: 2rem minmax(0, 1fr); }
  .jam-msg__head .jam-av { width: 2rem; height: 2rem; font-size: 0.65rem; }
  .jam-msg__side { grid-column: 2; justify-self: start; }
  .jam-node { left: -2.2rem; width: 1.9rem; }
  .jam-fold { margin-left: 2.2rem; }
  .jam-fold--reply { margin-left: 0; }
  .jam-presence { margin-left: 2.2rem; }
  /* The way back is the crumb, which names where back is. The Close
     link in the corner said the same thing again in the same row, and
     the two were printed on top of each other. */
  .jam-pane__close--conv { display: none; }
}

/* The action bar is one row, or it is not a bar: it wrapped onto two,
   with More stranded on the second. Every verb here already carries a
   drawing beside its word, so the drawing becomes the whole button and
   the word becomes its name — held for a screen reader, taken off the
   screen. The pill keeps its word, because what it says ("Unassigned",
   or whose face it is) is the state and not a verb.

   Two arms: a narrow screen has no width for the words, and a short
   one has no height for a second row of them. A phone on its side is
   the second without being the first. */
@media (max-width: 720px), ((max-height: 560px) and (max-width: 1100px)) {
  /* And the bar carries three things, not seven: who has it, the one
     verb that clears it, and the ··· holding the rest — which is what
     a row in the list has offered all along. Snooze, Label and Mailbox
     move into that menu (conversation.html); claiming was never only a
     button, it is the first line of the pill's own menu.

     A picker opened by URL comes back to the bar for as long as it is
     open: ?menu=label is how the ··· reaches these with no script, and
     what it opens has to be on the screen. */
  .jam-actions-bar__spare { display: contents; }
  .jam-actions-bar > form:has(.jam-act-btn--claim),
  .jam-actions-bar > .jam-actions-bar__rule,
  .jam-actions-bar > details.jam-pop:not(.jam-assign):not(.jam-pop--right):not([open]) { display: none; }
  .jam-actions-bar { gap: 0.2rem; }
  .jam-act-btn { padding: calc(0.34rem - 0.1rem * var(--jam-stuck, 0)) calc(0.45rem - 0.1rem * var(--jam-stuck, 0)); }
  .jam-act-btn__word { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
  .jam-act-btn .jam-mi { width: 18px; height: 18px; }
  .jam-assign__names { display: none; }
  .jam-actions-bar__rule { margin: 0 0.15rem; }
}

/* ── Development menu ──────────────────────────────────────────────── */
.jam-devmenu { position: fixed; left: 0.75rem; bottom: 0.75rem; z-index: 60; font-size: var(--rst-fs-xs); }
.jam-devmenu > summary { list-style: none; cursor: pointer; width: 2rem; height: 2rem; display: grid; place-items: center; border-radius: 50%; background: var(--rst-surface); border: 1px solid var(--rst-line); color: var(--rst-text-muted); box-shadow: var(--rst-shadow-pop); opacity: 0.55; }
.jam-devmenu > summary::-webkit-details-marker { display: none; }
.jam-devmenu > summary:hover, .jam-devmenu[open] > summary { opacity: 1; }
.jam-devmenu__body { position: absolute; left: 0; bottom: 2.5rem; width: 17rem; padding: 0.8rem 0.9rem; background: var(--rst-surface); border: 1px solid var(--rst-line); border-radius: 10px; box-shadow: var(--rst-shadow-pop); display: grid; gap: 0.6rem; }
.jam-devmenu__body h3 { margin: 0; font-size: var(--rst-fs-sm); }
.jam-devmenu__body label { display: grid; gap: 0.2rem; }
.jam-devmenu__body label > span { display: flex; justify-content: space-between; color: var(--rst-text-muted); }
.jam-devmenu__body select, .jam-devmenu__body input[type="range"] { width: 100%; font: inherit; }
.jam-devmenu__body button { justify-self: start; font: inherit; font-size: var(--rst-fs-xs); background: none; border: 1px solid var(--rst-line); border-radius: 6px; padding: 0.2rem 0.5rem; cursor: pointer; color: var(--rst-text-muted); }

/* ── Type on a phone ────────────────────────────────────────────────── */
/* A phone, upright or on its side, reads at a slightly smaller root
   size so the rem-based layout tightens with it; the reading sizes
   (messages, the editor) hold at one rem. */
/* The 15px root is for phones — portrait by width, landscape by the
   height arm, which is gated on a coarse pointer so a short desktop
   window keeps the full 16px scale. */
@media (max-width: 720px), ((max-height: 560px) and (pointer: coarse)) {
  html { font-size: 15px; }
  .jam-msg__body, .jam-editor-wrap--reply .jam-editor { font-size: 1rem; line-height: 1.55; }
  .jam-main, .jam-thread, .jam-views { --jam-head-big: 1.4rem; --jam-head-small: 1.05rem; }
  /* A smaller heading is a smaller row, so the bar's controls come
     down to fit it — it still has to be the same height. */
  .jam-bulkbar { height: var(--jam-head-row); }
  .jam-bulkbar [rst-btn] { padding: 0.15rem 0.6rem; }
  .jam-section-title { font-size: 1rem; }
  .jam-note__body { font-size: 0.95rem; }
}

/* ── A pane with no height ────────────────────────────────────────────
   A phone on its side, or a short window. The app bar has already
   folded into its pill (above); this is the panes' own heads doing the
   same thing. On 285 points of pane the conversation's head was 174 of
   them — subject, facts, and an action bar wrapped onto two rows — and
   the mail was read through the 111 that were left, scrolling inside
   them.

   The head still stays, because "which conversation is this" is worth
   a line at any size. It is a much smaller line: the type starts near
   its own condensed size instead of at full size, the verbs are their
   drawings (above), and the condense that takes the facts away happens
   over the first 3rem of the scroll instead of the first 11 — on a
   screen this short, 11rem of travel is most of the conversation, so
   the head would still be at full size by the time you had read it.
   Scrolled, the head is the subject and the verbs and nothing else,
   and the mail has the rest. */
@media (max-height: 560px) and (max-width: 1100px) {
  .jam-main, .jam-thread, .jam-views { --jam-head-big: 1.15rem; --jam-head-small: 1rem; animation-range: 0px 3rem; }
  /* Smaller type, but the heading is still the control that renames
     the conversation, so it keeps the 24px a finger needs (WCAG
     2.5.8) — four pixels of the head, which is the cheapest thing in
     it. The box grows downwards and nothing re-centres inside it: the
     summary lays its children out on a baseline, and that baseline is
     shared with the list's heading beside it. */
  .jam-thread__title-summary { margin-bottom: calc(0.3rem - 0.1rem * var(--jam-stuck, 0)); min-height: 24px; }
  /* Close is the only way back at this width — the crumb that names
     it belongs to the one-pane phone — so it is a target and not just
     a word. The padding it grows by is taken off the margin below it,
     so the head does not move. */
  .jam-pane__close--conv { padding-bottom: 0.7rem; margin-bottom: -1.7rem; }
  /* All the way to nothing, not down to 2rem: a line of chips is a
     third of what is left to read on. */
  .jam-thread__facts { margin-bottom: calc(0.45rem - 0.45rem * var(--jam-stuck, 0)); max-height: calc(30rem - 30rem * var(--jam-stuck, 0)); }
  .jam-actions-bar { padding: calc(0.35rem - 0.1rem * var(--jam-stuck, 0)) 0; }
  .jam-pane--conv .jam-conv { padding-bottom: 2rem; }
  .jam-msg { padding: 0.75rem 1.1rem 0.85rem; }

  /* ── One pane, while a conversation is open ─────────────────────────
     The panes were still three wide here: a sliver of the views, a
     sliver of the list, a tab for the contact, and the mail reading
     through what was left in the middle. On a screen this shape the
     slivers are not a way back worth 200 points of it — the crumb
     says where back is in two words, and says it in the same row as
     everything else. The list keeps its views beside it as before;
     this is only what happens once something is open. */
  .jam-panes--open .jam-pane--nav,
  .jam-panes--open .jam-pane--list,
  .jam-pane--contact { display: none; }
  .jam-pane--conv { left: 0; right: 0; }
  .jam-panes--open .jam-pane--conv,
  .jam-panes--open:has(.jam-pane--nav:hover) .jam-pane--conv,
  .jam-panes--open:has(.jam-pane--list:hover) .jam-pane--conv,
  .jam-panes--open.jam-panes--peek-nav .jam-pane--conv,
  .jam-panes--open.jam-panes--peek-list .jam-pane--conv { transform: none; }
  .jam-panes--open .jam-pane--conv { border-left: 0; box-shadow: none; }

  /* ── The head, in one row ───────────────────────────────────────────
     The way back, the subject, and the verbs, on the line the subject
     was taking on its own. The facts are the one thing that can be two
     lines, and only until the reader moves: they fold to nothing on
     the scroll, a few lines up. */
  .jam-thread__head { display: flex; flex-wrap: wrap; align-items: center; gap: 0 0.6rem; }
  .jam-crumb { display: flex; flex: none; margin: 0; }
  .jam-crumb a { display: inline-flex; align-items: center; gap: 0.3rem; min-height: 24px; color: var(--rst-text-muted); }
  /* The contact is named by every message under it, and this row has
     no width to spare for saying it again. */
  .jam-crumb a.jam-crumb__contact { display: none; }
  .jam-thread__title { flex: 1 1 6rem; min-width: 0; padding-right: 0; }
  /* One line of subject, because the row is one line. The balanced
     wrap, and the animation that lets a subject wrap at rest and takes
     the extra lines away on the scroll, belong to a head with room for
     more than one line — and an animation beats a plain declaration,
     so the animation has to go rather than be argued with. */
  .jam-thread__subject { animation-name: none; white-space: nowrap; }
  /* Renaming needs the width the row cannot give it, so the form takes
     a line of its own. */
  .jam-thread__title[open] { flex: 1 0 100%; }
  .jam-actions-bar { flex: none; border: 0; }
  .jam-thread__facts, .jam-elsewhere { order: 1; flex: 1 0 100%; }
  /* The crumb is the way back now, so the word in the corner is not —
     and it stood exactly where the verbs now stand. */
  .jam-pane__close--conv { display: none; }

  /* The app's own bar has folded into a pill at the top left, and with
     one pane that pill floats over this head. Every head used to drop
     below it, which cost 2.5rem of a screen that has none to give;
     the open conversation steps around it instead. It can, because it
     is the only head on the screen — there is nothing left for it to
     line up with. */
  body:has(.jam-brandbtn) .jam-panes--open .jam-thread__head { padding-left: 4rem; }
  /* The mark alone, without the logotype beside it. The drawing is
     what anybody recognises at this size, and the letters were 2.3rem
     of a row the subject is being truncated out of. */
  body:has(.jam-brandbtn) .jam-top > .jam-brandbtn .jam-brand__word { display: none; }

  /* What is left of the facts is what somebody would look for: which
     labels are on it and which mailboxes it is in. The address it
     arrived at and the note that you are following are true, quiet and
     not worth a line here — following is a line of the ··· menu, and
     it says which way it is set. */
  .jam-thread__facts .jam-arrived-fact,
  .jam-thread__facts .jam-following-fact { display: none; }

  /* ── The row's one middle ───────────────────────────────────────────
     The pill, the way back, the subject and the verbs are one row, and
     a row a person reads across has one middle. It had three: the way
     back and the subject stood on a baseline at the top of the row,
     the row was as tall as the bar, the bar was centred in all of it,
     and the head's top padding came off as it condensed — so "← Inbox"
     rode above the pill's middle and the buttons at the right sat as
     much as twelve pixels below it, by different amounts at rest and
     scrolled (issue #116).

     The pill is fixed to the window, so its middle is the line the row
     is given. Its top and its height are written here once, and the
     pill wears them too, so the two cannot drift: the height is the
     mark, the pill's padding and its hairline. The head then starts at
     the top of the pane with no padding of its own, its first line is
     the pill's height with the pill's margin above and below it, and
     everything in that line is centred — the subject inside its own
     24px as well, and the bar with no padding to push it off. */
  body:has(.jam-brandbtn) { --jam-pill-top: 0.35rem; --jam-pill-h: calc(1.35rem + 0.3rem + 2px); }
  body:has(.jam-brandbtn) .jam-top > .jam-brandbtn { box-sizing: border-box; top: var(--jam-pill-top); height: var(--jam-pill-h); }
  body:has(.jam-brandbtn) .jam-panes--open .jam-thread__head { padding-top: 0; align-items: center; }
  body:has(.jam-brandbtn) .jam-panes--open .jam-crumb { align-items: center; min-height: calc(2 * var(--jam-pill-top) + var(--jam-pill-h)); }
  .jam-thread__head .jam-thread__title-summary { align-items: center; margin-bottom: 0; }
  .jam-thread__head > .jam-actions-bar { padding: 0; }
}

/* Second factors: the QR to scan, and the key beside it. */
.jam-qr { width: 12rem; height: 12rem; margin: 1rem 0; color: var(--rst-text); }
.jam-qr svg { width: 100%; height: 100%; display: block; }
.jam-key { user-select: all; word-break: break-all; letter-spacing: 0.06em; }
.jam-auth__wide { width: 100%; box-sizing: border-box; text-align: center; justify-content: center; }
.jam-auth__forget { margin-top: 0.5rem; text-align: center; }
.jam-auth__step { margin-top: 1rem; }

/* ── Delivery: what became of a message the team sent ──────────────────
   The pill is ui/status-pill and the explanation is the callout's own
   attributes, so there is nothing here for either box. This is the
   explanation's spacing, under the message it is about or above the
   draft it kept, and the two quieter paragraphs inside it. */
.jam-delivery { margin: 0.85rem 0 0; }
.jam-delivery__todo { color: var(--rst-text); }
.jam-delivery__detail { color: var(--rst-text-faint); font-size: var(--rst-fs-sm); overflow-wrap: anywhere; }
.jam-compose .jam-delivery { margin: 0 0 1rem; }
