/* 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;
  --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). */
.rst-input, [rst-input], .rst-textarea, [rst-textarea],
.jam-settings input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not([type="file"]),
.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%; }
.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; }
.jam-setlist { list-style: none; margin: 0; padding: 0; border: 1px solid var(--rst-line); border-radius: 12px; background: var(--rst-surface); overflow: hidden; }
.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); }
.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; }
/* Native selects in a line form dress like the inputs beside them. */
.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-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); }
.jam-switch { position: relative; width: 2.3rem; height: 1.3rem; border-radius: 999px; border: 0; background: var(--rst-line-strong); cursor: pointer; padding: 0; transition: background-color 160ms; }
.jam-switch[aria-checked="true"] { background: var(--rst-accent); }
.jam-switch__knob { position: absolute; top: 0.15rem; left: 0.15rem; 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); }
.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; }
.jam-row__snippet 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); 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); }
/* 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-pop summary` sets inline-block; these summaries are flex rows. */
.jam-pop > summary.jam-act-btn, .jam-pop > summary.jam-assign__pill { display: inline-flex; }
/* 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 .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 .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; }
.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; }
.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, what the team can do, their conversations
   beside what the team knows about them. */
.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: center; margin: 0 0 0.9rem; }
.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__cols { display: grid; grid-template-columns: minmax(0, 1fr) 19rem; gap: 2rem; margin-top: 1.5rem; }
.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; }
.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__cols { 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-contact__name a { text-decoration: none; color: inherit; }
.jam-contact__name a:hover { color: var(--rst-accent-strong); }
.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; }

/* ── 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. */
.jam-google-btn { display: inline-flex; align-items: center; gap: 0.75rem; margin-top: 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-google-btn:hover { background: #f8f9fa; box-shadow: 0 1px 3px rgba(60, 64, 67, 0.3); }
.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; }
/* 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. */
.jam-selbox { 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):not([hidden])) + .jam-menu-list__none { display: none; }
.jam-menu-list__scroll:not(:has(> :is(form, .jam-mrow):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 ─────────────────────────────────────────────────── */
/* ── Team activity ─────────────────────────────────────────────────── */
/* A feed read by day: what to show as a segment, who and which label
   as small pickers; 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-activity__filters { display: flex; flex-wrap: wrap; gap: 0.6rem 1rem; align-items: center; margin: 1rem 0 1.25rem; }
/* The segment never wraps its tabs; on a narrow screen it scrolls sideways
   instead, and the two picks move to the next line as one unit. */
.jam-activity__filters .jam-seg { flex: 0 1 auto; min-width: 0; max-width: 100%; overflow-x: auto; scrollbar-width: none; white-space: nowrap; }
.jam-activity__filters .jam-seg::-webkit-scrollbar { display: none; }
.jam-activity__filters .jam-seg a { flex: none; }
.jam-activity__picks { display: flex; flex-wrap: wrap; gap: 0.6rem 1rem; align-items: center; }
@media (max-width: 720px) {
  .jam-activity__filters .jam-seg { flex-basis: 100%; }
  .jam-activity__picks { flex-basis: 100%; }
  .jam-activity__pick { flex: 1 1 10rem; }
  .jam-activity__pick select { flex: 1; min-width: 0; }
}
.jam-activity__pick { display: inline-flex; align-items: center; gap: 0.4rem; font-size: var(--rst-fs-sm); color: var(--rst-text-muted); }
.jam-activity__pick select { font: inherit; font-size: var(--rst-fs-sm); color: var(--rst-text); border: 1px solid var(--rst-line); border-radius: 999px; padding: 0.3rem 1.6rem 0.3rem 0.7rem; 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; appearance: none; -webkit-appearance: none; cursor: pointer; }
.jam-activity__pick select:hover { border-color: var(--rst-line-strong); }
.jam-activity__apply { border-color: var(--rst-line); }
.jam-activity__apply[hidden] { display: none; }
.jam-activity__filters .jam-seg a { padding: 0.28rem 0.7rem; }
.jam-activity__pick select { padding: 0.28rem 1.5rem 0.28rem 0.6rem; }
.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-activity__filters .jam-seg { max-width: 100%; overflow-x: auto; flex-wrap: nowrap; scrollbar-width: none; }
  .jam-activity__filters .jam-seg::-webkit-scrollbar { display: none; }
  .jam-activity__filters .jam-seg a { white-space: nowrap; }
  .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); view-transition-name: pane-nav; }
.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 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. */
  --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; }
.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. */
: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; }
  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; }
}
