/* 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 one place two
     faces have to share a baseline: the conversation's subject beside
     the list's heading. 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 */
}
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; }
.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; }
.jam-brand { font-weight: 600; font-size: 1.15rem; text-decoration: none; color: var(--rst-text); letter-spacing: -0.01em; }
.jam-brand::before { content: ""; display: inline-block; width: 0.55em; height: 0.55em; border-radius: 50% 50% 50% 0; background: var(--rst-accent); margin-right: 0.45em; transform: rotate(-45deg) translateY(-1px); }
.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; }
.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; }

/* 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; }
/* 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. */
.jam-list-head h1 { 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; }
/* 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); }
.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); }
.jam-hint form { display: contents; }
.jam-hint button { border: 0; background: none; padding: 0; 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); }
.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; }
.jam-row__body { display: grid; gap: var(--jam-row-gap); min-width: 0; }
.jam-row__top { display: flex; justify-content: space-between; gap: 1rem; align-items: center; }
.jam-row__side { display: inline-flex; align-items: center; gap: 0.4rem; flex: none; }
/* The row's side cluster reads 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__side .jam-chip { box-sizing: border-box; display: inline-flex; 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; }
.jam-row__side .jam-chip .jam-mi { width: 13px; height: 13px; }
.jam-row__side .jam-row__follow { color: var(--rst-text-muted); }
.jam-row__side .jam-row__follow .jam-mi { width: 15px; height: 15px; }
.jam-row__side .jam-av--md { width: 1.65rem; height: 1.65rem; font-size: 0.62rem; }
/* 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__body { gap: var(--jam-row-gap); }
.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; margin-left: 0.4rem; 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; 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%; }

/* 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--box { display: inline-flex; align-items: center; gap: 0.3rem; }
.jam-chip--box .jam-mi { width: 12px; height: 12px; }
.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); }
.jam-chip--btn { cursor: pointer; }
[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; }
.jam-thread__pencil { 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. */
.jam-thread__facts { 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: calc(30rem - 28rem * var(--jam-stuck, 0)); overflow: hidden; }
.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. */
.jam-msg { margin-left: -3.75rem; z-index: 1; }
.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); }
.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); }
.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); }
.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__title { font-weight: 600; font-size: 1.15rem; margin: 0 0 0.4rem; }
.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; }
/* 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-brand { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 40vw; }
.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; }
  .jam-brand { max-width: 60vw; 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-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-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; }
}

/* ── 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; min-height: calc(var(--jam-head-row) + var(--jam-head-gap)); }
/* 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-pad-y) + var(--rst-fs-sm) * var(--jam-row-lh) + var(--jam-row-gap) + var(--rst-fs-base) * var(--jam-row-lh) / 2 - 0.75rem); z-index: 1; display: grid; place-items: center; width: 1.5rem; height: 1.5rem; }
.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; }
/* No pointer to hover with: the box is simply there. */
@media (hover: none) { .jam-selbox input { opacity: 1; } .jam-row .jam-selbox__dot { opacity: 0; } }
.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; }
.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 ──────────────────────────── */
.jam-msg__side { display: flex; align-items: center; gap: 0.35rem; }
.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; }
@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. */
body { min-height: 100vh; 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-brand--toggle, .jam-menu-backdrop { display: none; }
/* A short screen — a phone on its side — has no height to spare for
   a header. It folds into its logo, 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. */
@media (max-height: 560px) and (max-width: 1100px) {
  .jam-brand--toggle { display: inline-flex; position: fixed; top: 0.45rem; left: 0.6rem; z-index: 52; align-items: center; padding: 0.25rem 0.7rem 0.25rem 0.55rem; border: 1px solid var(--rst-line); border-radius: 999px; background: var(--rst-surface); box-shadow: 0 1px 2px hsl(240 5% 12% / 0.08); cursor: pointer; font-size: 1rem; }
  .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; }
  .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(100vh - 1rem); overflow: auto; pointer-events: none; }
  .jam-top > * { display: none; }
  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; }
  body:has(#jam-menu:checked) .jam-top > .jam-brand, body:has(#jam-menu:checked) .jam-top > .jam-views-btn { display: none; }
  body:has(#jam-menu:checked) .jam-top .jam-nav { flex-direction: column; align-items: stretch; gap: 0; }
  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; }
  body:has(#jam-menu:checked) .jam-top .jam-top__compose { order: 0; justify-content: center; }
  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; }
  body:has(#jam-menu:checked) .jam-brand--toggle { background: var(--rst-accent-soft); border-color: transparent; color: var(--rst-accent-strong); }
  .jam-panes .jam-pane.jam-pane--nav { padding-top: 3.4rem; }
  /* The brand pill floats over the top left of the open panes, so
     every head drops below it — together, or they stop lining up. */
  .jam-panes.jam-panes--open { --jam-head-inset: 3.4rem; }
  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. */
.jam-pane--list, .jam-pane--conv { overflow-anchor: none; }
.jam-pane--nav { left: 0; width: 15rem; padding: 1.25rem 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. */
@property --jam-stuck { syntax: "<number>"; inherits: true; initial-value: 0; }
@keyframes jam-stuck { from { --jam-stuck: 0; } to { --jam-stuck: 1; } }
.jam-pane--list { scroll-timeline: --jam-list-scroll block; }
.jam-pane--conv { scroll-timeline: --jam-conv-scroll block; }
.jam-main, .jam-thread { animation: jam-stuck linear both; animation-range: 0px 11rem; }
.jam-main { animation-timeline: --jam-list-scroll; }
.jam-thread { animation-timeline: --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; }
.jam-panes--open .jam-main { animation-timeline: --jam-conv-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 {
  /* 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 { position: sticky; top: 0; display: flow-root; background: var(--rst-bg); }
.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. */
.jam-list-top::after, .jam-thread__head::after { content: ""; position: absolute; left: 0; right: 0; top: 100%; height: 1.4rem; background: linear-gradient(var(--rst-bg) 25%, transparent); opacity: var(--jam-stuck, 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-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-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; 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-left: calc(-0.9rem - 2.2rem); margin-right: -0.9rem; border-radius: 0; border-left: 0; border-right: 0; padding: 1rem 0.9rem 1.1rem; }
  .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; }
  .jam-actions-bar { gap: 0.2rem; }
  .jam-act-btn { padding: calc(0.34rem - 0.1rem * var(--jam-stuck, 0)) calc(0.55rem - 0.12rem * var(--jam-stuck, 0)) calc(0.34rem - 0.1rem * var(--jam-stuck, 0)) calc(0.45rem - 0.1rem * var(--jam-stuck, 0)); }
  .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-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; }
}

/* 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; }
