/*
 * app.css — the console's one stylesheet. Neutral, quiet and flat (owner, 2026-10-01: "very modern,
 * clean, and somewhat minimal", after a reference with a grey sidebar, white cards, black primary
 * buttons and one blue for data). The system typeface: nothing to load, and crisp at table sizes.
 * Tokens first; every colour below comes from them.
 *
 * --accent is the ink of the interface (pills, primary buttons, the chosen thing). --link is the
 * colour of a link, kept apart because a black link in a table reads as plain text. --data is the
 * one colour for charts. Themes set all three.
 */
:root {
  /* The page itself is pure white (owner, 2026-10-01: "i want it to be ffffff white"). The sidebar
     is NOT: it keeps the shade it had, so the two still read as separate surfaces. See --sheet. */
  --bg:#ffffff; --sheet:#f7f7f8; --card:#ffffff; --fg:#111113; --muted:#6b6b72; --line:#e6e6e9; --line-soft:#f0f0f2;
  --accent:#111113; --accent-ink:#ffffff; --accent-soft:#f1f1f3; --accent-soft-2:#e4e4e8;
  --link:#2563eb; --data:#2f6fed; --tint:#fafafa;
  --ok:#15803d; --ok-bg:#eaf7ef; --warn:#a15c07; --warn-bg:#fdf3e1; --stop:#c0362c; --stop-bg:#fdeceb; --info:#1d4ed8; --info-bg:#edf3ff; --look:#6d28d9; --look-bg:#f3eeff;
  /* Where a pinned band hangs from. Nothing sits across the top of the page since the sidebar
     replaced the top bar, so 0; on a phone the sidebar is a strip across the top again. */
  --header-h:0px;
  --radius:18px; --radius-sm:11px; --shadow:0 1px 2px rgba(16,16,20,.03); --pop:0 2px 6px rgba(16,16,20,.06), 0 16px 40px rgba(16,16,20,.12); --focus:0 0 0 3px rgba(17,17,19,.12);
  --font:-apple-system, BlinkMacSystemFont, "SF Pro Text", "Segoe UI Variable Text", "Segoe UI", Inter, Roboto, Helvetica, Arial, sans-serif; --mono:ui-monospace, "SF Mono", "Cascadia Mono", Menlo, Consolas, monospace;
}
* { box-sizing:border-box; }
/* The background is painted on BODY alone, on purpose. A person's theme is an attribute on body,
   so html sits outside it and would paint the default cream under every themed page — visible
   below short pages and on every overscroll. With html painting nothing, CSS propagates body's
   background to the whole canvas, and it is the themed one. */
html, body { margin:0; color:var(--fg); font:15px/1.5 var(--font); -webkit-font-smoothing:antialiased; }
body { background:var(--bg); }
a { color:var(--link); text-decoration:none; } a:hover { text-decoration:underline; text-underline-offset:2px; }
main { max-width:1240px; margin:0 auto; padding:34px 40px 72px; }
body.wide main { max-width:1760px; padding:24px 24px 64px; }
/* Below 1300 the side gutters give their width back to the tables, which are sized in percent. */
@media (max-width:1299px) { main { padding-left:24px; padding-right:24px; } }
h1 { font-size:30px; font-weight:700; letter-spacing:-.025em; line-height:1.15; margin:4px 0 8px; display:flex; align-items:center; gap:12px; flex-wrap:wrap; }
h1 small, h2 small { color:var(--muted); font-weight:600; font-size:12px; background:var(--accent-soft); border-radius:999px; padding:3px 10px; letter-spacing:0; }
h2 { font-size:19px; font-weight:700; margin:28px 0 12px; letter-spacing:-.015em; }
h1 small:empty, h2 small:empty { display:none; }
/* Presence: who else has this case open. Owner 2026-09-06: bolder and darker so it is obvious.
   In the "look" colour rather than the accent, because that is what the same fact wears on a queue
   row and a rail row, and a VA reads all three within seconds of each other. */
#presence { background:var(--look-bg); color:var(--look); font-weight:700; font-size:12.5px; padding:4px 10px;
  border-radius:999px; display:inline-flex; align-items:center; gap:5px; }
/* 🛑 An ID beats `h2 small:empty` above, so without this the pill paints as a bare coloured blob
   next to "Decide" on every case page nobody else is on — which is nearly all of them. */
#presence:empty { display:none; }
/* Who else has this ticket open: the same coloured initials on a rail row, a queue row and the
   case page (http/looking.cjs). One circle per person in the colour they wear on their avatar and
   their notes, so "is somebody already on this?" is answered by a glance instead of a click.
   🛑 No colour is set here beyond the fallback: the .tone[data-color] rules further down are what
   honour a person's own pick, and they only win while this stays less specific than they are. */
/* 🛑 SCOPED TO THE SPAN, because `who` is two different things (2026-10-01). This is the presence
   chip (http/looking.cjs, a <span>); the sidebar's profile menu is <details class="menu who">, and
   a bare `.who` matched BOTH. The profile box inherited inline-flex and margin-right:5px from here
   and so shrink-wrapped its own text and sat 5px narrower than every nav item above it, which is
   what the owner saw as "too small... should have same padding on left and right side". `.who b`
   was styling his name at 10px muted too, and only lost to a more specific rule by luck. */
span.who { display:inline-flex; align-items:center; gap:2px; vertical-align:-3px; margin-right:5px; }
span.who i { display:inline-flex; align-items:center; justify-content:center; width:17px; height:17px; border-radius:50%;
  font-style:normal; font-size:9px; font-weight:700; letter-spacing:.02em; background:var(--line-soft); color:var(--muted); }
span.who b { font-size:10px; font-weight:700; color:var(--muted); }
[hidden] { display:none !important; }
a.back { display:inline-flex; align-items:center; gap:6px; margin:0 0 12px; padding:6px 12px 6px 10px; border-radius:999px; background:var(--card); border:1px solid var(--line); color:var(--muted); font-size:13px; font-weight:600; text-decoration:none; } a.back:hover { color:var(--fg); border-color:var(--accent-soft-2); text-decoration:none; }
h3 { font-size:13px; font-weight:600; color:var(--muted); margin:18px 0 8px; letter-spacing:0; }
p { margin:8px 0; }
p.blurb { color:var(--muted); font-size:16px; margin:0 0 24px; max-width:760px; }
.muted { color:var(--muted); } .small { font-size:12.5px; } .ok { color:var(--ok); } .warn { color:var(--warn); } .stop { color:var(--stop); }
kbd { font:11px/1 var(--font); font-weight:700; background:var(--accent-soft); border:1px solid var(--accent-soft-2); border-radius:6px; padding:2px 6px; color:var(--accent); vertical-align:middle; }
code { font:12.5px/1.4 var(--mono); background:var(--accent-soft); border-radius:5px; padding:1px 5px; }
pre.note.preview { font:14px/1.55 var(--font); background:var(--card); }
pre.note { font:12.5px/1.5 var(--mono); background:var(--tint); border:1px solid var(--line); padding:12px 14px; white-space:pre-wrap; border-radius:var(--radius-sm); margin:8px 0; }

/* ---------- the sidebar (owner, 2026-10-01: a left sidebar, collapsible, replacing the top bar)
   Still a <header> element because app.js refreshes the queue counts through "header .nav b".
   Its ground is the page's own background pulled a few percent toward the ink, so it is a shade
   darker than the page in every theme, light or dark, without each theme naming a colour for it. */
body.has-nav .shell { display:flex; align-items:stretch; min-height:100vh; }
.page { flex:1 1 auto; min-width:0; }
header.sidebar { position:sticky; top:0; height:100vh; width:252px; flex:none; z-index:5; display:flex; flex-direction:column; gap:2px;
  padding:18px 14px 14px; background:color-mix(in srgb, var(--sheet, var(--bg)) 95%, var(--fg)); border-right:1px solid var(--line); overflow-y:auto; scrollbar-width:thin;
  /* It slides rather than snapping (zeezy, 2026-10-01: "make the slide-out animation smoother
     instead of kind of jagged"). Width and padding are the only things that actually move; the
     labels are display:none in mini and cannot be tweened, and fading them in a 68px column would
     only spill them out of it, so they switch cleanly while the panel travels. */
  transition:width .24s cubic-bezier(.4, 0, .2, 1), padding .24s cubic-bezier(.4, 0, .2, 1); }
/* 🛑 A THEME'S SIDEBAR STILL COMES FROM ITS OWN PAGE. --sheet is what the sidebar's ground is mixed
   from. The default look pins it to the grey the page used to be, so making the page white leaves
   the sidebar exactly where it was; every theme points it back at its own --bg, so none of them
   has to name a second colour and none inherits the default's grey.
   Matched on the bare attribute rather than per theme on purpose: picking Default stores NULL
   (api/admin.cjs), so the absence of the attribute IS the default look, and naming that key in a
   selector here would read to themes.test.cjs as a theme repainting one variable and none of the
   other six. Do not write that key into this file, in a rule or a comment: the test greps the
   stylesheet as text, and a mention in prose fails it exactly as a real rule would. */
body[data-theme] { --sheet:var(--bg); }
@media (prefers-reduced-motion: reduce) { header.sidebar { transition:none; } }
.sidebar .sb-top { display:flex; align-items:center; justify-content:space-between; gap:8px; padding:2px 4px 14px; }
.sidebar .brand { display:flex; align-items:center; padding:6px 8px; border-radius:var(--radius-sm); }
.sidebar .brand img { height:21px; width:auto; display:block; }
.sidebar .sb-toggle { display:inline-flex; align-items:center; justify-content:center; width:34px; height:34px; border:0; border-radius:var(--radius-sm); background:none; color:var(--muted); cursor:pointer; flex:none; }
.sidebar .sb-toggle:hover { background:color-mix(in srgb, var(--fg) 7%, transparent); color:var(--fg); }
.sidebar .ico { flex:none; display:block; }
/* Search: a field with the glass inside it. */
.sidebar .search { position:relative; margin:0 0 12px; }
.sidebar .search > .ico { position:absolute; left:12px; top:50%; transform:translateY(-50%); color:var(--muted); pointer-events:none; }
.sidebar .search input { width:100%; padding:9px 12px 9px 38px; font-size:14px; border:1px solid var(--line); border-radius:var(--radius-sm); background:var(--card); }
.sidebar nav.tabs { display:flex; flex-direction:column; gap:2px; }
.sidebar .nav { display:flex; align-items:center; gap:12px; padding:9px 12px; border-radius:var(--radius-sm); color:color-mix(in srgb, var(--fg) 78%, transparent); font-weight:500; font-size:15px; white-space:nowrap; position:relative; transition:background .12s, color .12s; }
.sidebar .nav .lbl { flex:1; min-width:0; overflow:hidden; text-overflow:ellipsis; }
.sidebar .nav:hover { background:color-mix(in srgb, var(--fg) 6%, transparent); color:var(--fg); text-decoration:none; }
.sidebar .nav.on { background:color-mix(in srgb, var(--fg) 9%, transparent); color:var(--fg); font-weight:600; }
.sidebar .nav b { font-size:12px; font-weight:600; background:var(--card); color:var(--fg); border:1px solid var(--line); border-radius:999px; padding:0 8px; min-width:24px; text-align:center; line-height:20px; }
.sidebar .nav small { color:inherit; opacity:.7; font-weight:500; }
.sidebar .sep { height:1px; background:var(--line); margin:10px 12px; }
.sidebar .grow { flex:1; min-height:12px; }
/* The owner menu: in the full sidebar it opens in place, as a group under its own heading. */
.sidebar details.menu { position:relative; }
.sidebar details.menu > summary { list-style:none; cursor:pointer; } .sidebar details.menu > summary::-webkit-details-marker { display:none; }
.sidebar details.nav-menu > summary { margin-top:2px; }
.sidebar details.nav-menu .caret { margin-left:auto; color:var(--muted); transition:transform .12s; }
.sidebar details.nav-menu[open] .caret { transform:rotate(180deg); }
.sidebar details.nav-menu .menu-body { display:flex; flex-direction:column; gap:2px; padding:2px 0 4px 12px; }
.sidebar details.nav-menu .menu-body a { display:flex; align-items:center; gap:12px; padding:8px 12px; border-radius:var(--radius-sm); color:color-mix(in srgb, var(--fg) 78%, transparent); font-size:14px; font-weight:500; }
.sidebar details.nav-menu .menu-body a:hover { background:color-mix(in srgb, var(--fg) 6%, transparent); color:var(--fg); text-decoration:none; }
.sidebar details.nav-menu .menu-body a.on { background:color-mix(in srgb, var(--fg) 9%, transparent); color:var(--fg); font-weight:600; }
/* Who you are, at the foot, the way the reference does it; the menu opens upward from it. */
/* 🛑 THE PROFILE BOX FILLS THE COLUMN (owner, 2026-10-01: "make this box a bit wider to be centered
   on the left panel... it should have same padding on left and right side").
   It did not, and the cause is a class collision rather than anything written here: this element is
   `<details class="menu who">`, and `.who` up at the top of this file is the PRESENCE chip, which
   is display:inline-flex. So the profile shrink-wrapped its own text and sat narrower than every
   nav item above it, with the sidebar's 14px gutter on the left and whatever was left over on the
   right. Blocking it here makes the summary span the column, so both gutters are the sidebar's. */
.sidebar details.who { display:block; }
.sidebar details.who > summary { display:flex; align-items:center; gap:10px; padding:10px; border-radius:var(--radius); border:1px solid var(--line); background:var(--card); }
.sidebar details.who > summary:hover, .sidebar details.who[open] > summary { border-color:var(--accent-soft-2); }
.sidebar .who-txt { display:flex; flex-direction:column; min-width:0; line-height:1.3; }
.sidebar .who-txt b { font-size:14px; font-weight:600; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.sidebar .who-txt small { font-size:12px; color:var(--muted); }
.sidebar .avatar { width:36px; height:36px; flex:none; border-radius:50%; background:var(--accent); color:var(--accent-ink); display:inline-flex; align-items:center; justify-content:center; font-size:13px; font-weight:700; font-style:normal; }
.sidebar details.who .menu-body { position:absolute; left:0; right:0; bottom:calc(100% + 6px); background:var(--card); border:1px solid var(--line); border-radius:var(--radius-sm); box-shadow:var(--pop); padding:10px 0 6px; display:flex; flex-direction:column; z-index:6; }
.sidebar details.who .menu-body b, .sidebar details.who .menu-body small { padding:0 14px; } .sidebar details.who .menu-body small { color:var(--muted); margin-bottom:8px; font-size:12.5px; }
.sidebar details.who .menu-body a { padding:8px 14px; color:var(--fg); font-weight:500; font-size:14px; } .sidebar details.who .menu-body a:hover { background:var(--accent-soft); text-decoration:none; }

/* Collapsed: icons only, the counts as a badge on the icon, every menu opening out to the right.
   overflow is let go so those menus are not clipped by the sidebar's own box. */
body.caseshell .sidebar .sb-toggle { display:none; }
body.nav-mini header.sidebar { width:68px; padding:16px 10px 12px; overflow:visible; }
body.nav-mini .sidebar .sb-top { justify-content:center; padding:0 0 12px; }
body.nav-mini .sidebar .brand, body.nav-mini .sidebar .lbl, body.nav-mini .sidebar .who-txt, body.nav-mini .sidebar .caret { display:none; }
body.nav-mini .sidebar .nav { justify-content:center; padding:10px 0; }
/* Collapsed, the count becomes a plain dot (zeezy, 2026-10-01: "make it so that the numbers don't
   show, so it's just a circle... once you expand it the numbers will show"). font-size:0 hides the
   digits without removing them, so the number is still read out and still in the page for anything
   that is not a pair of eyes; expanding restores it because this rule simply stops applying. */
body.nav-mini .sidebar .nav b { position:absolute; top:5px; right:12px; width:9px; height:9px; min-width:0; padding:0;
  border-radius:50%; background:var(--accent); color:transparent; font-size:0; line-height:0; border:0; overflow:hidden; }
/* A dot means there is something here. An empty queue has nothing to point at, and a dot on it
   would say the opposite of the truth, so zero shows nothing at all. */
body.nav-mini .sidebar .nav b.zero { display:none; }
body.nav-mini .sidebar .sep { margin:8px 6px; }
body.nav-mini .sidebar .search input { width:48px; padding:9px 0 9px 46px; color:transparent; cursor:pointer; }
body.nav-mini .sidebar .search input::placeholder { color:transparent; }
body.nav-mini .sidebar .search > .ico { left:15px; }
body.nav-mini .sidebar .search input:focus, body.nav-mini .sidebar .search input:not(:placeholder-shown) { position:absolute; left:0; top:0; z-index:7; width:300px; padding-left:38px; color:var(--fg); cursor:text; box-shadow:var(--pop); }
body.nav-mini .sidebar .search input:focus::placeholder { color:var(--muted); }
body.nav-mini .sidebar .search:focus-within > .ico { z-index:8; left:12px; }
body.nav-mini .sidebar details.nav-menu .menu-body, body.nav-mini .sidebar details.who .menu-body { position:absolute; left:calc(100% + 10px); right:auto; top:0; bottom:auto; min-width:200px; padding:6px; background:var(--card); border:1px solid var(--line); border-radius:var(--radius-sm); box-shadow:var(--pop); z-index:7; }
body.nav-mini .sidebar details.who .menu-body { top:auto; bottom:0; padding:10px 0 6px; }
body.nav-mini .sidebar details.who > summary { padding:4px; justify-content:center; border:0; background:none; }

/* A laptop narrower than 1200px gets the collapsed sidebar whatever was chosen: 252px of labels
   out of a 1000px window left the returns table so little room that a column folded into a
   442px-tall row (browser.test.cjs measures it). Same rules as body.nav-mini, keyed on the width. */
@media (min-width:761px) and (max-width:1199px) {
  .sidebar .sb-toggle { display:none; }
  body.has-nav header.sidebar { width:68px; padding:16px 10px 12px; overflow:visible; }
  body.has-nav .sidebar .sb-top { justify-content:center; padding:0 0 12px; }
  body.has-nav .sidebar .brand, body.has-nav .sidebar .lbl, body.has-nav .sidebar .who-txt, body.has-nav .sidebar .caret { display:none; }
  body.has-nav .sidebar .nav { justify-content:center; padding:10px 0; }
  body.has-nav .sidebar .nav b { position:absolute; top:1px; right:2px; min-width:18px; padding:0 5px; font-size:10.5px; line-height:16px; background:var(--accent); color:var(--accent-ink); border:0; }
  body.has-nav .sidebar .sep { margin:8px 6px; }
  body.has-nav .sidebar .search input { width:48px; padding:9px 0 9px 46px; color:transparent; cursor:pointer; }
  body.has-nav .sidebar .search input::placeholder { color:transparent; }
  body.has-nav .sidebar .search > .ico { left:15px; }
  body.has-nav .sidebar .search input:focus, body.has-nav .sidebar .search input:not(:placeholder-shown) { position:absolute; left:0; top:0; z-index:7; width:300px; padding-left:38px; color:var(--fg); cursor:text; box-shadow:var(--pop); }
  body.has-nav .sidebar .search input:focus::placeholder { color:var(--muted); }
  body.has-nav .sidebar .search:focus-within > .ico { z-index:8; left:12px; }
  body.has-nav .sidebar details.nav-menu .menu-body, body.has-nav .sidebar details.who .menu-body { position:absolute; left:calc(100% + 10px); right:auto; top:0; bottom:auto; min-width:200px; padding:6px; background:var(--card); border:1px solid var(--line); border-radius:var(--radius-sm); box-shadow:var(--pop); z-index:7; }
  body.has-nav .sidebar details.who .menu-body { top:auto; bottom:0; padding:10px 0 6px; }
  body.has-nav .sidebar details.who > summary { padding:4px; justify-content:center; border:0; background:none; }
}
/* A phone: the sidebar folds into a strip across the top, icons only, and scrolls sideways. */
@media (max-width:760px) {
  :root { --header-h:58px; }
  body.has-nav .shell { display:block; }
  header.sidebar, body.nav-mini header.sidebar { position:sticky; top:0; height:auto; width:auto; flex-direction:row; align-items:center; gap:4px; padding:8px 12px; overflow-x:auto; overflow-y:visible; border-right:0; border-bottom:1px solid var(--line); }
  .sidebar .sb-top, .sidebar .lbl, .sidebar .who-txt, .sidebar .caret, .sidebar .grow { display:none; }
  /* Search stays, as a glass that opens across the top of the screen when tapped. Fixed, because
     the strip scrolls sideways and would clip anything hanging out of it. */
  .sidebar .search, body.nav-mini .sidebar .search { order:-1; flex:none; margin:0 4px 0 0; }
  .sidebar .search input { width:42px; padding:9px 0 9px 40px; color:transparent; cursor:pointer; }
  .sidebar .search input::placeholder { color:transparent; }
  .sidebar .search > .ico { left:12px; }
  .sidebar .search input:focus, .sidebar .search input:not(:placeholder-shown) { position:fixed; left:12px; right:12px; top:8px; width:auto; z-index:8; padding-left:38px; color:var(--fg); cursor:text; box-shadow:var(--pop); }
  .sidebar .search input:focus::placeholder { color:var(--muted); }
  .sidebar .search:focus-within > .ico { position:fixed; left:24px; top:28px; z-index:9; }
  .sidebar nav.tabs { flex-direction:row; }
  .sidebar .nav { padding:9px 10px; }
  .sidebar .nav b { position:absolute; top:0; right:0; min-width:16px; padding:0 4px; font-size:10px; line-height:15px; background:var(--accent); color:var(--accent-ink); border:0; }
  .sidebar .sep { width:1px; height:22px; margin:0 4px; }
  .sidebar details.who > summary { padding:4px; border:0; background:none; }
  .sidebar details.who .menu-body, .sidebar details.nav-menu .menu-body { position:fixed; left:auto; right:12px; top:58px; bottom:auto; min-width:200px; background:var(--card); border:1px solid var(--line); border-radius:var(--radius-sm); box-shadow:var(--pop); padding:6px; z-index:7; }
}

/* ---------- home */
.home-head { margin:4px 0 6px; } .home-head h1 { margin-bottom:0; }
.tiles { display:grid; grid-template-columns:repeat(auto-fit, minmax(180px, 1fr)); gap:12px; margin:10px 0 22px; }
/* The exact window the ticket numbers cover, in words, above the tiles (owner, 2026-09-16: "make it
   super obvious what it is", New York time, 12:00 AM to 11:59 PM). Sized to be read, not skimmed past. */
.tickets-when { margin:2px 0 4px; font-size:14px; color:var(--fg); }
.tickets-when .live { color:var(--info); font-weight:600; }
/* One fact per row (zeezy, 2026-10-01: "the text is all like a big blob ... there's no distinction").
   Two columns: the number, then the words it belongs to. The grid is what does the work a <br> could
   not -- a label too long for the tile wraps under ITSELF rather than under the number, so a wrapped
   line never reads as a new fact, and the numbers stay in a column the eye can run straight down.
   Right-aligned and tabular so 1,556 and 424 line up on their last digit. A note has no number of its
   own, spans both columns and stays prose. */
.tiles.tickets .tile .s { display:grid; grid-template-columns:auto 1fr; gap:4px 8px; align-items:baseline; line-height:1.35; }
.tiles.tickets .tile .s > b { font-weight:600; color:var(--fg); text-align:right; white-space:nowrap; font-variant-numeric:tabular-nums; }
.tiles.tickets .tile .s > .note { grid-column:1 / -1; }
/* The per-person list is a different kind of fact from the counts above it, so it is divided off. */
.tiles.tickets .tile .s > .note.people { border-top:1px solid var(--line); margin-top:2px; padding-top:7px; }
.tiles.tickets .tile .s > .note.people b { font-weight:600; color:var(--fg); font-variant-numeric:tabular-nums; }
/* The mini leaderboard under "Handled by people" (zeezy, 2026-10-19: "line by line ... next to the
   name, also have their Discord profile picture"). A line is four cells -- position, face, name,
   number -- so a long name is cut with an ellipsis instead of wrapping under the face, and every
   number stays in a column the eye can run down. Everyone who handled a ticket gets a line, so the
   gap is kept tight: a row of tiles is as tall as its tallest, and this is the one that grows. */
.tiles.tickets .tile .s > .board { display:grid; gap:4px; }
.board .row { display:grid; grid-template-columns:11px 20px minmax(0,1fr) auto; gap:7px; align-items:center; }
.board .row .r { color:var(--muted); font-size:11px; text-align:right; font-variant-numeric:tabular-nums; }
.board .face { width:20px; height:20px; border-radius:50%; object-fit:cover; display:inline-flex; align-items:center; justify-content:center;
  font-size:9px; font-weight:700; font-style:normal; letter-spacing:.02em; background:var(--line-soft); }
.board .who { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.board .row.top .who { font-weight:600; color:var(--fg); }
.tile { background:var(--card); border:1px solid var(--line); border-radius:var(--radius); padding:18px 20px; box-shadow:var(--shadow); display:flex; flex-direction:column; gap:4px; min-width:0; }
.tile.link { padding:0; } .tile.link > a { display:flex; flex-direction:column; gap:2px; padding:14px 16px; color:inherit; border-radius:var(--radius); } .tile.link > a:hover { background:var(--accent-soft); text-decoration:none; }
.tile .n { font-size:30px; font-weight:700; letter-spacing:-.03em; line-height:1.1; font-variant-numeric:tabular-nums; } .tile .n small { font-size:14px; font-weight:600; color:var(--muted); margin-left:2px; }
.tile .l { font-size:13.5px; font-weight:500; color:var(--muted); } .tile .s { font-size:13px; color:var(--muted); } .tile .d { font-size:12px; font-weight:600; margin-top:auto; padding-top:6px; }
/* The queue's own icon above its number. Muted, because the number is the thing being read and a
   full-strength icon on five tiles in a row competes with all five of them. */
.tile .t-ico { display:block; color:var(--muted); margin-bottom:2px; line-height:0; }
.tile.link > a:hover .t-ico { color:var(--fg); }
.card.review h3 { margin-top:16px; } .narrative { background:var(--accent-soft); border-radius:var(--radius-sm); padding:12px 16px; margin:8px 0 12px; } .narrative p { margin:0; } .narrative ol, .narrative ul { margin:6px 0 0 18px; padding:0; } .narrative li { margin:6px 0; }
ul.findings { list-style:none; margin:0; padding:0; } ul.findings li { padding:9px 0; border-bottom:1px solid var(--line-soft); } ul.findings li:last-child { border:0; } ul.findings .chip { margin-right:6px; }
/* People: the permissions fold to one line ("7 of 10 turned on") and open to the boxes. */
details.perm-drop > summary { cursor:pointer; list-style:none; display:inline-flex; align-items:center; gap:6px; font-size:12.5px; font-weight:600; padding:4px 10px; border:1px solid var(--line); border-radius:var(--radius-sm); background:var(--card); white-space:nowrap; }
details.perm-drop > summary::-webkit-details-marker { display:none; } details.perm-drop > summary::after { content:"▾"; color:var(--muted); }
details.perm-drop[open] > summary::after { content:"▴"; } details.perm-drop[open] > summary { margin-bottom:8px; } details.perm-drop .perm-list { min-width:260px; }
.perm-list { display:flex; flex-direction:column; gap:4px; } .perm-list label { display:flex; gap:6px; align-items:flex-start; font-size:12.5px; color:var(--fg); font-weight:500; white-space:normal; }
.banner { padding:11px 40px; font-size:13.5px; border-bottom:1px solid var(--line); } .banner.warn { background:var(--warn-bg); color:#6b4600; } .banner.stop { background:var(--stop-bg); color:#7a1a14; } .banner code { background:rgba(0,0,0,.06); }

/* ---------- tables */
table.tbl { width:100%; border-collapse:separate; border-spacing:0; background:var(--card); border:1px solid var(--line); border-radius:var(--radius); overflow:hidden; box-shadow:var(--shadow); }
.tbl th { text-align:left; font-size:12.5px; color:var(--muted); font-weight:500; padding:12px 16px; border-bottom:1px solid var(--line); background:var(--card); }
/* 🛑 overflow-wrap:anywhere, not break-word (owner, 2026-09-24: the returns table ran off the page).
   Only `anywhere` lets a cell REPORT a smaller minimum width, and a table is sized from the sum of
   its columns' minimums: with break-word a 26-digit tracking number still demands its full 170px
   and pushes everything after it off the right edge. This lets the long numbers fold instead, and
   it only ever kicks in on a word that genuinely does not fit. */
.tbl td { padding:13px 16px; border-bottom:1px solid var(--line-soft); vertical-align:top; overflow-wrap:anywhere; } .tbl tr:last-child td { border-bottom:0; }
/* The card's own border is the frame, so the scroll box carries the radius and the table inside
   drops its own: a rounded table inside a scrolling box shows a white corner as it scrolls. */
.tblwrap { overflow-x:auto; border-radius:var(--radius); }
/* 🛑 Auto layout hands the width to whichever column holds the longest unbreakable thing, which is
   never the column that matters: a tracking number and a button label beat the product titles
   every time. `fixed` makes the widths the page asked for the widths it gets, so a table with
   seven columns still reads like a table. Only for tables whose columns are given widths. */
.tbl.fixed { table-layout:fixed; }
/* Buttons in a cell, evenly spaced and wrapping as a group instead of flowing like words. A label
   as long as "She is not using the label" wraps inside its own pill rather than setting the width
   of the whole column, which is what .mini's nowrap does everywhere else and should not do here. */
.rowacts { display:flex; flex-wrap:wrap; gap:6px; align-items:flex-start; }
.rowacts .mini, .rowacts .btn-like { white-space:normal; text-align:left; }
.rowacts > small { flex:1 0 100%; margin:0; }
/* A row's wide note: its own <tr>, spanning the table, reading as part of the row above it. The
   row above therefore gives up its bottom rule, so the two are one block and not two. */
.tbl tr.has-note > td { border-bottom:0; padding-bottom:6px; }
.tbl tr.rownote > td { padding-top:0; }
.tbl.rows tr[data-href] { cursor:pointer; transition:background .1s; } .tbl.rows tr[data-href]:hover, .tbl.rows tr.sel { background:var(--tint); outline:none; } .tbl.rows tr.sel { box-shadow:inset 3px 0 0 var(--accent); }
.tbl td small { color:var(--muted); font-size:12.5px; }
/* A Total row: one rule above it, the counts in bold, no rule below (it is the table's last line). */
.tbl tfoot td { border-top:1px solid var(--line); border-bottom:0; }
small.brief { color:var(--warn); font-weight:600; display:-webkit-box; -webkit-line-clamp:4; -webkit-box-orient:vertical; overflow:hidden; }
.nowrap { white-space:nowrap; }
.card table.tbl, .side table.tbl { box-shadow:none; }

/* ---------- chips, buttons, inputs */
/* 🛑 A chip wraps rather than overflowing (owner, 2026-09-14: "i dont like how that is running off
   the pill"). This was white-space:nowrap, which is right for "Resolved" and wrong for the chips
   that carry a sentence — "chargeback forming: credit card company", and "CHANGED, NOT TOLD: ..."
   whose tail is free text of no fixed length. nowrap meant those could not wrap, so they grew past
   the right edge of a 300px side tile and were cut off mid-word, losing the end of the sentence.
   Normal white-space only wraps what does not fit, so every short chip still sits on one line.
   max-width stops a chip setting its own width, and overflow-wrap covers the order numbers and
   tracking codes that have no spaces to break at. Guarded by chip-width.test.cjs. */
.chip { display:inline-block; max-width:100%; font-size:11.5px; font-weight:600; padding:2px 9px; border-radius:999px; background:var(--accent-soft); color:var(--accent); overflow-wrap:break-word; margin:1px 0; line-height:1.5; }
.chip.ok { background:var(--ok-bg); color:var(--ok); } .chip.warn { background:var(--warn-bg); color:var(--warn); } .chip.stop { background:var(--stop-bg); color:var(--stop); } .chip.info { background:var(--info-bg); color:var(--info); } .chip.look { background:var(--look-bg); color:var(--look); }
/* "Off" is not a warning and not a success: a switch nobody has turned on should read as quiet. */
.chip.muted { background:var(--line-soft); color:var(--muted); }
/* A label the team put on itself, with the × that takes it off. Distinct from a disposition chip
   on purpose: the ones the queues run on have no ×, because taking one off here would re-file her
   case (2026-10-01). */
.chip.team-label { background:var(--line-soft); color:var(--fg); padding-right:4px; }
.chip.team-label .lbl-x { border:0; background:none; cursor:pointer; font:inherit; font-size:13px; line-height:1; color:var(--muted); padding:0 4px; border-radius:999px; }
.chip.team-label .lbl-x:hover { color:var(--stop); background:var(--stop-bg); }
.lbl-box { display:inline-flex; align-items:center; gap:5px; flex-wrap:wrap; }
.lbl-box .lbl-new { font:inherit; font-size:12.5px; padding:3px 8px; border:1px solid var(--line); border-radius:999px; background:var(--card); color:var(--fg); width:160px; }
.lbl-box .lbl-pick { font:inherit; font-size:12.5px; padding:4px 8px; border:1px solid var(--line); border-radius:999px; background:var(--card); color:var(--fg); max-width:230px; }
/* The About line: what the ticket is, in words, where the run's filing labels used to be. */
.labelsbar .labels.about { margin-right:14px; }
.labelsbar .labels.about .chip { background:var(--info-bg); color:var(--info); }
.btn { padding:10px 18px; border:1px solid var(--line); background:var(--card); border-radius:12px; cursor:pointer; font:inherit; font-size:14px; font-weight:600; color:var(--fg); box-shadow:0 1px 2px rgba(16,16,20,.04); transition:background .12s, transform .06s; }
.btn:hover { background:var(--accent-soft); text-decoration:none; } .btn:active { transform:translateY(1px); }
.btn.primary { background:var(--accent); color:var(--accent-ink); border-color:var(--accent); } .btn.primary:hover { background:color-mix(in srgb, var(--accent) 82%, var(--card)); }
.btn:disabled { opacity:.45; cursor:not-allowed; transform:none; }
a.btn { display:inline-block; }
/* 🛑 A PILL IS A SHAPE, AND A SHAPE CANNOT SURVIVE BEING BROKEN ACROSS TWO LINES. An <a class="mini">
   is inline by default, so in a narrow table cell the browser wrapped "open her ticket" mid-label
   and drew the rounded border twice, open at both ends, with the padding landing in the middle of
   the words (owner, 2026-09-24: "the pill is messed up"). inline-block keeps it one box and nowrap
   keeps the label on one line; a label too long for its column now widens the column instead of
   tearing the control in half. Buttons were already inline-block by their own default, so this
   only ever changes the links. */
.mini, .btn-like { display:inline-block; white-space:nowrap; font:inherit; font-size:12.5px; font-weight:600; padding:5px 11px; border:1px solid var(--line); background:var(--card); border-radius:999px; cursor:pointer; text-decoration:none; color:var(--fg); transition:background .12s; }
.mini:hover, .btn-like:hover { background:var(--accent-soft); text-decoration:none; }
/* The chosen option in a filter row (queue sort): a pill that reads as pressed. */
.btn-like.on, .mini.on { background:var(--accent); color:var(--accent-ink); border-color:var(--accent); }
.lnk { border:0; background:none; color:var(--accent); cursor:pointer; padding:0; font:inherit; font-size:12.5px; font-weight:600; text-decoration:underline; }
/* The "Hide" on a box a person can take off her own case page (Elica, 2026-10-03). Grey and
   unmarked until it is pointed at: it sits on three headings at once, and three blue links down
   the page would be more of exactly the clutter the hiding is for. */
.box-x { font-size:11px; color:var(--muted); text-decoration:none; }
.box-x:hover { color:var(--accent); text-decoration:underline; }
/* On a fact tile the heading is a block, so the Hide shares its line rather than taking one of its
   own: the strip is a grid and a row is as tall as its tallest cell. On a sidebar box the heading
   is already a flex row, so the Hide only needs pushing to its far end. */
.strip .k-row { display:flex; align-items:baseline; justify-content:space-between; gap:8px; }
.side h3 .box-x { margin-left:auto; }
.row { display:flex; gap:10px; align-items:center; flex-wrap:wrap; margin:8px 0; } .stack { display:flex; flex-direction:column; gap:10px; }
.stack label { display:flex; flex-direction:column; gap:4px; font-size:12.5px; font-weight:600; color:var(--muted); }
label.inline, .stack label:has(> input[type=checkbox]) { flex-direction:row; align-items:center; gap:8px; font-weight:500; color:var(--fg); font-size:14px; }
.stack input:not([type=checkbox]), .stack select, .stack textarea { max-width:760px; }
input, select, textarea { font:inherit; font-size:14px; padding:9px 12px; border:1px solid var(--line); border-radius:var(--radius-sm); background:var(--card); color:var(--fg); transition:border-color .12s, box-shadow .12s; }
input:focus, select:focus, textarea:focus { outline:none; border-color:var(--accent); box-shadow:var(--focus); }
textarea { width:100%; resize:vertical; line-height:1.55; } input[type=checkbox] { width:auto; accent-color:var(--accent); }
select { max-width:100%; } .panel select#tpl { flex:1 1 200px; min-width:0; }
/* The template search (Jasper C., 2026-10-03: "a search bar in the template menu"). It stands where
   the menu stood; the list opens under it, inside its own wrapper so a popup cannot hide it, and
   as wide as the titles need up to the panel, where the old menu would have run off the side. */
.tpl-search { position:relative; display:inline-block; flex:1 1 200px; min-width:0; }
.tpl-search > input { width:100%; }
.tpl-list { position:absolute; left:0; top:calc(100% + 4px); z-index:60; min-width:100%; width:max-content; max-width:min(640px, 86vw);
  max-height:min(380px, 55vh); overflow-y:auto; background:var(--card); border:1px solid var(--line); border-radius:var(--radius-sm); box-shadow:var(--pop); padding:4px; }
.tpl-group { font-size:11.5px; font-weight:700; color:var(--muted); padding:8px 9px 3px; }
.tpl-opt { font-size:13px; line-height:1.4; padding:6px 9px 6px 16px; border-radius:6px; cursor:pointer; color:var(--fg); }
.tpl-opt small { display:block; margin-top:2px; font-size:11.5px; }
.tpl-opt:hover, .tpl-opt.on { background:var(--accent-soft); color:var(--accent); }
/* The groups fold (Geng, 2026-10-26). With nothing typed a group name is a row you click, so it reads
   as one, not as the small grey label it stays under a search. */
.tpl-fold { display:flex; align-items:center; gap:7px; font-size:13px; font-weight:600; color:var(--fg); padding:7px 9px; border-radius:6px; cursor:pointer; }
.tpl-fold::before { content:"▸"; color:var(--muted); font-size:11px; transition:transform .12s; flex:none; }
.tpl-fold.open::before { transform:rotate(90deg); }
.tpl-fold:hover, .tpl-fold.on { background:var(--accent-soft); color:var(--accent); }
.tpl-count { margin-left:auto; padding-left:14px; font-size:11.5px; font-weight:500; color:var(--muted); }
.tpl-list.folding .tpl-opt { padding-left:28px; }   /* under the name, past its arrow */
.tpl-none { padding:8px 9px; }
.card { background:var(--card); border:1px solid var(--line); border-radius:var(--radius); padding:22px 26px; margin:16px 0; box-shadow:var(--shadow); } .card > h2:first-child { margin-top:0; }
/* A card that folds shut (settings, 2026-09-23). The summary is the whole clickable header: the
   title on the left, what the setting is currently set to on the right, so the page answers most
   questions with nothing open. Padding moves onto the summary and the body so a shut card is one
   tidy bar rather than a bar with dead space under it. */
.card.fold { padding:0; }
.card.fold > summary.fold-head { list-style:none; cursor:pointer; padding:15px 22px; display:flex; align-items:center; gap:10px 14px; flex-wrap:wrap; border-radius:var(--radius); }
.card.fold > summary.fold-head::-webkit-details-marker { display:none; }
.card.fold > summary.fold-head::before { content:"▸"; color:var(--muted); font-size:11px; transition:transform .12s; flex:none; }
.card.fold[open] > summary.fold-head::before { transform:rotate(90deg); }
.card.fold > summary.fold-head:hover { background:var(--accent-soft); }
.card.fold > summary.fold-head:focus-visible { box-shadow:var(--focus); }
.fold-title { font-weight:700; font-size:16px; color:var(--fg); }
.fold-now { margin-left:auto; text-align:right; font-size:13px; display:flex; align-items:center; gap:6px; flex-wrap:wrap; justify-content:flex-end; }
.card.fold[open] > summary.fold-head { border-bottom:1px solid var(--line-soft); border-radius:var(--radius) var(--radius) 0 0; }
.fold-body { padding:16px 22px 18px; }
.fold-body > p:first-child { margin-top:0; }
@media (max-width:640px) { .fold-now { margin-left:0; width:100%; justify-content:flex-start; text-align:left; } }
.grid2 { display:grid; grid-template-columns:1fr 1fr; gap:18px; }
.grid2 > section { background:var(--card); border:1px solid var(--line); border-radius:var(--radius); padding:22px 26px; box-shadow:var(--shadow); } .grid2 > section > h3:first-child { margin-top:0; }
.login { max-width:400px; margin:90px auto; text-align:center; background:var(--card); border:1px solid var(--line); border-radius:var(--radius); padding:38px 34px; box-shadow:var(--shadow); }
.login .logo { height:26px; width:auto; margin-bottom:22px; } .login h1 { justify-content:center; font-size:20px; color:var(--fg); font-weight:700; margin-bottom:4px; }
/* A refusal is read, not glanced at, so it goes left-aligned with a severity stripe rather than
   centred like the rest of the card: the sentence that says what to do next has to be findable. */
.login .loginmsg { text-align:left; background:var(--stop-bg); border-left:3px solid var(--stop); border-radius:var(--radius-sm); padding:13px 15px; margin:20px 0; }
.login .loginmsg p { margin:0 0 7px; } .login .loginmsg p:last-child { margin-bottom:0; }
.login .loginmsg .mono { font-family:var(--mono); font-size:12px; word-break:break-word; }
.login .btn.primary { display:inline-block; padding:11px 22px; font-size:15px; }
details.notes { margin:8px 0 10px; padding:10px 12px; background:var(--accent-soft); border-radius:var(--radius-sm); } details.notes summary { cursor:pointer; font-weight:600; font-size:13.5px; color:var(--accent); list-style:none; } details.notes summary::before { content:"✎ "; } details.notes[open] summary { margin-bottom:6px; } details.notes textarea { margin-bottom:8px; }
details.history { margin-top:6px; } details.history summary { cursor:pointer; color:var(--muted); font-weight:600; font-size:13px; padding:6px 0; list-style:none; } details.history summary::before { content:"▸ "; } details.history[open] summary::before { content:"▾ "; }
.hint { display:flex; gap:12px; align-items:flex-start; background:var(--accent-soft); border-radius:var(--radius-sm); padding:10px 14px; color:var(--accent); font-size:13.5px; margin:8px 0 14px; }

/* ---------- case */
.casehead h1 { margin-bottom:4px; }
.casehead .brief { margin:8px 0 0; padding:10px 14px; background:var(--info-bg); border-radius:var(--radius-sm); font-size:13.5px; color:var(--info); }
/* Two customers on one ticket (pages/case.cjs twoPeopleLine): it names both addresses, which must wrap on a phone. */
.casehead .brief.two-people { overflow-wrap:anywhere; }
/* 🛑 PINNED, because the facts are what the thread is read AGAINST (a VA, 2026-09-30: "can we lock
   these info boxes ^^ when scroll those don't move like headers"). She was scrolling back up to
   re-read a delivery date and scrolling down again, all day.

   The background is not decoration: the tiles are separated by 10px gaps, and without it the
   thread slides visibly through them. The old top and bottom margins become padding for the same
   reason, so the painted band covers the whole strip rather than stopping at the cards.

   max-height caps what it can take. On a 1440-wide window the tiles sit on one row and it never
   comes near the cap; on a narrow one they wrap to two or three rows, and without this the header
   plus the strip would hold more of the screen than the conversation. Past the cap it scrolls
   inside itself, so every fact is still reachable without unpinning anything.

   z-index sits under the sidebar (5) and over the thread, so on a phone it tucks under the strip.

   🛑 It hangs from the header's MEASURED height, not from a hardcoded 110px. The gap that number
   leaves would be a letterbox here, with the conversation scrolling through it. --header-h is set
   by app.js; 110px is the old value, kept as the fallback for a page that has not measured yet.

   🛑 NO `overscroll-behavior:contain` (owner, 2026-09-30: "when my mouse cursor is on the pill and
   I scroll, it doesn't do anything. I wanted to still scroll the screen"). An overflow:auto box is
   a scroll container even when its content fits, and `contain` stops a scroll container handing
   the wheel to the page at its boundary — so on every ordinary window, where the tiles fit on one
   row and there is nothing here to scroll, the strip swallowed the wheel and the page sat still.
   Chaining is the default; it only ever had to be turned off if two scrollers were fighting, and
   they never were.

   minmax stays at 190px, which is why the ship-to address cost a tile rather than being added as
   one: a 1440 window with the queue rail open gives the case body 1109px, so six tiles would be
   177px each and the status chip broke as "UNFULFILLE / D". Five fit at 214. */
.strip { display:grid; grid-template-columns:repeat(auto-fit, minmax(190px, 1fr)); gap:10px; font-size:13.5px;
  position:sticky; top:var(--header-h, 110px); z-index:4; background:var(--bg); padding:12px 0 12px; margin:0 0 4px;
  max-height:38vh; overflow-y:auto; }
.strip > div { background:var(--card); border:1px solid var(--line); border-radius:var(--radius-sm); padding:12px 15px; box-shadow:var(--shadow); min-width:0; overflow-wrap:anywhere; }
.strip .k { display:block; font-size:12.5px; font-weight:500; color:var(--muted); margin-bottom:4px; }
/* Shopify's verdict on the address and the button that changes it, on one line: two rows there
   would make this tile the tallest in the strip, and a grid row is as tall as its tallest cell. */
/* The small-button row under a fact tile: Ships to's verdict and editor, Tracking's Edit number. */
.shipto-act, .track-act { display:flex; align-items:center; flex-wrap:wrap; gap:6px; margin-top:4px; }
.shipto-act .mini, .track-act .mini { padding:2px 8px; font-size:11.5px; }
/* label / value rows: every fact on its own line, labels aligned in a narrow column */
dl.facts { margin:0; display:grid; grid-template-columns:max-content 1fr; column-gap:10px; row-gap:3px; font-size:13px; }
dl.facts > div { display:contents; } dl.facts dt { color:var(--muted); font-weight:600; white-space:nowrap; } dl.facts dt::before { content:"· "; color:var(--line); }
dl.facts dd { margin:0; min-width:0; overflow-wrap:anywhere; } dl.facts > div.one > dd { grid-column:1 / -1; }
/* A button in a value wraps inside it: the value beside "order email" is ~135px in the 260px side
   column, and "Send order confirmation" ran past the card on one line (owner, 2026-10-24). */
dl.facts dd .mini { white-space:normal; max-width:100%; }
dl.facts.item { padding:6px 0; border-bottom:1px solid var(--line-soft); margin-bottom:4px; }
/* What she bought: product in bold, variant and quantity under it, money on the right. */
.items .item { display:flex; gap:10px; align-items:baseline; padding:7px 0; border-bottom:1px solid var(--line-soft); }
.items .item:last-child { border-bottom:0; }
.items .item .grow { flex:1; min-width:0; overflow-wrap:anywhere; }
.items .item .amt { font-variant-numeric:tabular-nums; white-space:nowrap; font-weight:600; }
.chip.vip { background:#fbefd2; color:#7a5200; border:1px solid color-mix(in srgb, var(--warn) 25%, var(--card)); }
/* Copy button beside an order number. Sized to sit on the same line as the link it follows without
   pushing the fact row to two lines, which is the whole reason the number is worth copying. */
/* An icon, not the word (owner, 2026-09-18): two overlapping squares, a tick while it confirms. */
.copyb { display:inline-flex; align-items:center; justify-content:center; padding:3px 5px; line-height:0; vertical-align:middle;
  border:1px solid var(--line); background:var(--card); color:var(--muted); border-radius:6px; cursor:pointer; transition:background .12s, color .12s, border-color .12s; }
.copyb svg { fill:none; stroke:currentColor; stroke-width:1.5; stroke-linecap:round; stroke-linejoin:round; }
/* Her address on the case page: reading size, not the small print it was, with its copy button beside it. */
.cust-email { display:flex; align-items:center; gap:6px; margin:4px 0 2px; font-size:14.5px; color:var(--fg); }
.cust-email span { overflow-wrap:anywhere; min-width:0; }
.cust-email .copyb { flex:none; }
.copyb:hover { background:var(--accent-soft); color:var(--accent); border-color:var(--accent-soft-2); }
.copyb.ok { background:var(--ok-bg); color:var(--ok); border-color:var(--ok); }
/* The queue rail down the left of a case (Jasper, 2026-09-16). A flex row rather than another grid
   column, so the case body keeps its own three-column grid untouched and only gets narrower. */
.caseview { display:flex; gap:16px; align-items:flex-start; }
.casebody { flex:1 1 auto; min-width:0; }

/* ---------------------------------------------------------------- the case page as an app shell

   🛑 THE WINDOW HOLDS STILL AND THE COLUMNS SCROLL (owner, 2026-10-01: "is there any way to have
   the decide panel always on the screen and no scroll needed? because right now when i scroll all
   the way down the decide panel would be all the way on top").

   Measured on a case at 1440x900 the day it was asked: the header and the pinned facts take 250px,
   leaving 636px of window, and the Decide panel is 1427px against a sidebar of 1307px. A 1427px
   form does not go into 636px, so "everything on one screen with no scrolling" was never available
   and no amount of tightening buys it. What IS available is choosing what scrolls. A support
   console is an app, not a document: the page itself holds still, and the conversation, the
   decision and the sidebar each scroll inside their own column. Read to the end of a six-week
   thread and Decide has not moved, because the conversation is what moved.

   Only here, and only where the three columns actually stand side by side. 1429px is not a round
   number picked by eye: viewport less the collapsed sidebar's 69px, main's 48px of padding and the
   rail's 268 leaves the 1042 that three columns of minimums need (the case page never shows the
   full sidebar, for exactly this reason). Narrower than that
   the sidebar wraps under the other two, and a wrapped column inside a fixed-height shell would
   have to scroll a second row into view, which is worse than a scrolling page. So the shell turns
   itself off and the page scrolls the ordinary way. A VA who collapses the rail on a 1300-1428
   window gets three columns and no shell; that is the one seam, and it is on the safe side.

   min-height:0 on every flex and grid item in the chain is the whole trick, and it is not
   decoration: a flex item's default min-height is auto, which means "at least as tall as my
   content", so one missing line lets a long thread push the shell taller than the window and the
   page grows its scrollbar back. browser.test.cjs asserts the window has nothing to scroll. */
@media (min-width:1429px) {
  body.caseshell { height:100vh; overflow:hidden; display:flex; flex-direction:column; }
  /* The sidebar is a column of its own and already the full height of the window, so the shell
     is the page beside it: banners first, then main, which is what scrolls inside itself. */
  body.caseshell > .shell { flex:1 1 auto; min-height:0; }
  body.caseshell .page { display:flex; flex-direction:column; min-height:0; }
  body.caseshell .page > .banner { flex:0 0 auto; }
  /* width:100%, because a flex item with margin:0 auto shrinks to its content, and the case body
     reports no content width at all (it is a size container), so main collapsed to the rail. */
  body.caseshell .page > main { flex:1 1 auto; min-height:0; width:100%; display:flex; flex-direction:column; padding-bottom:12px; }
  body.caseshell .page > main > .back { flex:0 0 auto; }
  body.caseshell .caseview { flex:1 1 auto; min-height:0; align-items:stretch; }
  body.caseshell .casebody { display:flex; flex-direction:column; min-height:0; }
  /* The title, the labels bar and the facts are the bands that stay put. */
  body.caseshell .casehead, body.caseshell .labelsbar { flex:0 0 auto; }
  body.caseshell .strip { position:static; max-height:none; overflow:visible; flex:0 0 auto; z-index:auto; }
  body.caseshell .cols { flex:1 1 auto; min-height:0; align-items:stretch; }
  /* Each column its own scroller. padding-bottom so the last control in a column is not flush
     against the window edge, which reads as cut off. */
  body.caseshell .cols > * { overflow-y:auto; min-height:0; padding-bottom:10px; }
  /* 🛑 .thread is a flex column, and a flex item shrinks by default. Given a fixed height to
     scroll inside, it squeezed the messages down to their minimum instead of scrolling past them:
     her words compressed, the column still full. Every message keeps its own height; the column
     scrolls. The same would bite any flex column handed a scrollbar. */
  body.caseshell .thread > * { flex:0 0 auto; }
  body.caseshell .qrail { position:static; max-height:none; height:100%; }

  /* 🛑 EVERY PIXEL ABOVE THE COLUMNS IS A PIXEL THE COLUMNS DO NOT GET, and that turned out to be
     where the height had gone. Measured at 1440x900 before this block: header 65, main's padding
     34, the Back pill 34, the title band 92, the labels bar 51, the facts 185 and 25px of margins
     between them. 486px of chrome, leaving the three columns 414. The facts are the one band that
     earns its height, because they are what the decision is checked against; the rest is a title
     nobody reads twice and a row of buttons. Tightened here only, so every other page keeps the
     roomier spacing. */
  body.caseshell .page > main { padding-top:12px; }
  /* align-self, because a flex column stretches its items: without it the Back pill ran the whole
     width of the page as one very wide button. */
  body.caseshell .page > main > .back { align-self:flex-start; margin:0 0 6px; padding:4px 10px 4px 8px; font-size:12.5px; }
  /* Subject and its row of chips and buttons share one line instead of stacking: two bands of
     chrome become one, and the subject is already in the browser tab and the rail. It wraps on a
     long subject rather than truncating, because the subject is often the only clue to what she
     wants before you read her. */
  body.caseshell .casehead { display:flex; align-items:baseline; flex-wrap:wrap; gap:4px 12px; }
  body.caseshell .casehead h1 { font-size:19px; margin:0; }
  body.caseshell .casehead .row { margin:0; }
  body.caseshell .casehead .brief { flex:1 0 100%; margin-top:4px; padding:6px 11px; }
  body.caseshell .labelsbar { padding:5px 11px; margin-top:8px; }
  body.caseshell .strip { padding:9px 0; gap:8px; margin-bottom:0; }
  body.caseshell .strip > div { padding:7px 11px; }
  body.caseshell .strip .k { margin-bottom:3px; }
  /* 14 rows was 324px, and on a real case most of it was the blank between "Hi Sierra," and the
     sign-off. The box keeps its native resize handle for a long reply, and the column scrolls. */
  body.caseshell #reply { height:200px; }
  body.caseshell .panel { padding:13px 15px; }
  body.caseshell .side section { padding:9px 13px 11px; margin-bottom:9px; }
}
/* 🛑 THE WHOLE STRIP FOLDS, OR NONE OF IT DOES (owner, 2026-10-02: "can we make these pills
   collaspable" … "they all show together or collaspe together"). One click, not a caret per card:
   a VA either wants the facts in front of her on this case or wants the height for the thread, and
   she is never halfway.

   🛑 THE SAME GAP UNDER THE LABELS BAR, FOLDED OR NOT (owner, 2026-10-14: folded, "the strip almost
   touches the panel below"). The fold used to zero the strip's padding, so the panels came up flush
   against the bar. Now it keeps the padding the strip has above the cards — 12px, 9 in the app
   shell, whichever rule above set it — so the panels sit exactly as far under the bar as the cards
   did. The band below goes, unless there is a red row to keep off the panels: an empty strip with
   both would be two bands where the cards stood. The red row has no padding of its own for the same
   reason. browser.test.cjs measures it in and out of the shell.

   What is left is the red chips, and only on a case that has one: see the comment over the fold
   row in pages/case.cjs for why. The row drops the card's background, border and shadow because it
   is not a card — it is the sentence the strip would be shouting if it could only say one thing. */
.strip-fold { display:none; }
body.facts-off .strip { margin:0; max-height:none; overflow:visible; }
body.facts-off .strip:not(:has(> .strip-fold)) { padding-bottom:0; }
body.facts-off .strip > div { display:none; }
body.facts-off .strip > .strip-fold { display:flex; grid-column:1 / -1; flex-wrap:wrap; align-items:center;
  gap:5px; background:none; border:0; box-shadow:none; padding:0; }
/* The toggle sits at the right-hand end of the labels bar, which is the band directly above the
   strip and is pinned with it. margin-left:0 because the bar's own gap already spaces it. */
.labelsbar .fold { display:flex; align-items:center; gap:6px; }
/* Pinned at the header's measured bottom, the same edge the strip beside it hangs from. At the old
   flat 110px it sat 45px lower than the strip, which read as a step between two things that are
   side by side (owner, 2026-09-30: "the side ... it should look like the left and right parts"). */
.qrail { flex:0 0 252px; width:252px; background:var(--card); border:1px solid var(--line); border-radius:var(--radius);
  box-shadow:var(--shadow); position:sticky; top:var(--header-h, 110px); max-height:calc(100vh - 14px - var(--header-h, 110px)); display:flex; flex-direction:column; overflow:hidden; }
.qrail-head { display:flex; align-items:flex-start; gap:6px; padding:8px 8px 6px; border-bottom:1px solid var(--line-soft); }
.qtabs { display:flex; flex-direction:column; gap:2px; flex:1; min-width:0; }
.qtabs .qtab { font-size:12px; font-weight:600; color:var(--muted); padding:4px 9px; border-radius:999px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.qtabs .qtab:hover { background:var(--accent-soft); color:var(--fg); text-decoration:none; }
.qtabs .qtab.on { background:var(--accent); color:var(--accent-ink); }
.qtabs .qtab b { font-weight:700; opacity:.75; }
.qrail-toggle { font:inherit; font-size:11px; font-weight:700; padding:3px 8px; border:1px solid var(--line); background:var(--card); color:var(--muted); border-radius:999px; cursor:pointer; flex:0 0 auto; }
.qrail-toggle:hover { background:var(--accent-soft); color:var(--accent); }
.qrail-list { list-style:none; margin:0; padding:4px; overflow-y:auto; flex:1 1 auto; }
.qrail-list li { margin:0; }
.qrail-list li.empty { padding:10px 9px; color:var(--muted); font-size:12.5px; }
.qrail-list a { display:grid; grid-template-columns:1fr max-content; gap:0 8px; padding:7px 9px; border-radius:var(--radius-sm); color:var(--fg); font-size:12.5px; line-height:1.4; }
.qrail-list a:hover { background:var(--accent-soft); text-decoration:none; }
.qrail-list a b { font-weight:700; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.qrail-list a .w { font-size:11px; color:var(--muted); font-variant-numeric:tabular-nums; white-space:nowrap; }
/* Over the SLA, or inside its last quarter (pages/queues.cjs slaClass). Scoped like the rule above:
   a bare .stop loses to it, which is how the rail's wait classes rendered grey until 2026-09-16. */
.qrail-list a .w.stop { color:var(--stop); font-weight:700; } .qrail-list a .w.warn { color:var(--warn); font-weight:700; }
.qrail-list a small .held { color:var(--warn); font-weight:700; }
.qrail-list a small { grid-column:1 / -1; color:var(--muted); font-size:11.5px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
/* The case being read. A left bar rather than only a fill, so it is still obvious at a glance
   after scrolling a long list where several rows are hovered on the way past. */
.qrail-list li.here > a { background:var(--accent-soft); box-shadow:inset 3px 0 0 var(--accent); font-weight:600; }
.qrail-note { margin:0; padding:7px 10px; font-size:11.5px; color:var(--muted); border-top:1px solid var(--line-soft); }
/* Collapsed: the rail keeps only its toggle, so there is always something to click to bring it
   back. Remembered per browser by app.js, never on the server: it is a view preference, not state. */
body.qrail-off .qrail { flex:0 0 auto; width:auto; }
body.qrail-off .qrail .qtabs, body.qrail-off .qrail-list, body.qrail-off .qrail-note { display:none; }
body.qrail-off .qrail-head { border-bottom:0; padding:8px; }

/* 🛑 THE WIDTH CAME BACK TO THE CONVERSATION once the buttons stopped needing it (owner, same day,
   an hour later: "Can you make the side panel a little narrower ... Make the left ticket area a
   little wider like before").

   The first answer to the buttons being too tall was to widen the middle column so the long labels
   could pair up, and it cost the thread 110px. Dropping the bracketed qualifiers to the hover was
   the better answer: the longest label went from 309px to about 230, so two fit at a width the
   middle already had, and the 110px goes back to her message where it reads. The sidebar gives up
   40 as well; it holds short facts and was the widest thing on the page that did not need to be.

   The grid's minimums add up to 1042. Since the sidebar (2026-10-01) the three columns switch at
   exactly that, measured on the case body itself (the container queries below), because the
   collapsed sidebar has taken the 69px of slack the old 1428 breakpoint kept in reserve. */
.cols { display:grid; grid-template-columns:minmax(360px,1.2fr) minmax(390px,1fr) 260px; gap:16px; align-items:start; }
.thread { display:flex; flex-direction:column; gap:12px; }
.msg { background:var(--card); border:1px solid var(--line); border-radius:var(--radius); padding:14px 16px; box-shadow:var(--shadow); }
.msg.us { background:var(--accent-soft); border-color:var(--accent-soft-2); box-shadow:none; } .msg.draft { border-style:dashed; background:var(--tint); }
/* The merged customer log: an entry from another thread is marked, a campaign reply is greyed. */
.msg.other { border-left:3px solid var(--line); } .msg header small.from { flex-basis:100%; color:var(--muted); font-size:12px; } .msg header small.from a { color:inherit; }
.msg.noise { opacity:.6; } .msg.noise .body { font-size:12.5px; }
.merged { padding:8px 12px; border:1px dashed var(--line); border-radius:var(--radius); font-size:13px; } .merged a.here { font-weight:700; }
/* Merge under Other threads (ticket-merges.cjs): the paste box shrinks with the column, never past it. */
.merge-form { display:flex; gap:6px; margin-top:6px; } .merge-form input { flex:1; min-width:0; font-size:13px; }
.replyon { margin:6px 0 10px; font-size:13px; display:flex; gap:8px; align-items:center; flex-wrap:wrap; } .replyon select { max-width:100%; }
.msg header { display:flex; gap:8px; align-items:baseline; margin-bottom:8px; flex-wrap:wrap; } .msg header b { font-weight:700; } .msg header small { color:var(--muted); font-size:12.5px; }
.msg .body { white-space:normal; word-break:break-word; line-height:1.55; }
.msg details.quoted { margin-top:8px; } .msg details summary { color:var(--muted); font-size:12.5px; cursor:pointer; font-weight:600; } .msg details .body { color:var(--muted); font-size:12.5px; margin-top:6px; }
.atts { display:flex; flex-wrap:wrap; gap:8px; margin-top:10px; } .att.img img { max-width:220px; max-height:220px; border:1px solid var(--line); border-radius:var(--radius-sm); } .att { font-size:12.5px; }
.att.img { display:inline-block; cursor:zoom-in; } .att.img:hover img { border-color:var(--accent); }

/* Image viewer. Over the case, never a new tab: the photo and the facts it is judged against
   belong on one screen. Dark ground because a photo is what is being read here, not the page. */
.zoom[hidden] { display:none; }
.zoom { position:fixed; inset:0; z-index:80; display:grid; grid-template-columns:auto 1fr auto; grid-template-rows:auto 1fr;
  background:rgba(12,12,14,.92); backdrop-filter:blur(4px); }
/* The bar carries its own ground: at 92% the queue counts behind it still read through and
   compete with the filename, and this row is the one place a person looks to get back. */
.zoom-bar { grid-column:1 / -1; display:flex; align-items:center; gap:14px; padding:12px 16px; color:#f4f4f5; background:rgba(0,0,0,.55); }
.zoom-bar .zoom-cap { font-weight:600; font-size:14px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.zoom-bar .zoom-count { color:#a1a1aa; font-size:12.5px; white-space:nowrap; }
.zoom-bar .zoom-tab { margin-left:auto; color:#d4d4d8; font-size:12.5px; text-decoration:underline; white-space:nowrap; }
.zoom-bar .zoom-tab:hover { color:#fff; }
.zoom-back { background:#fff; color:var(--fg); border:0; border-radius:999px; padding:8px 16px; font-weight:600; font-size:13.5px; cursor:pointer; white-space:nowrap; }
.zoom-back:hover { background:var(--accent-soft); }
/* overflow:hidden so a magnified photo is cropped by the frame instead of spilling over the bar
   and the arrows; the transform is what moves it, so nothing here may animate. */
.zoom-stage { grid-column:1 / -1; grid-row:2; position:relative; display:flex; align-items:center; justify-content:center; padding:0 16px 26px; min-height:0; overflow:hidden; }
.zoom-stage img { max-width:100%; max-height:100%; object-fit:contain; border-radius:var(--radius-sm); background:#fff; cursor:zoom-in; will-change:transform; }
.zoom.zoomed .zoom-stage img { border-radius:0; cursor:grab; }
.zoom-zoomers { display:flex; align-items:center; gap:4px; margin-left:8px; }
.zoom-pct { min-width:46px; text-align:center; font-size:12.5px; color:#d4d4d8; font-variant-numeric:tabular-nums; }
.zbtn { background:rgba(255,255,255,.14); color:#fff; border:0; border-radius:var(--radius-sm); min-width:30px; height:28px; padding:0 9px;
  font-size:15px; line-height:1; cursor:pointer; }
.zbtn.zoom-fit { font-size:12.5px; font-weight:600; }
.zbtn:hover:not(:disabled) { background:rgba(255,255,255,.3); }
.zbtn:disabled { opacity:.35; cursor:default; }
.zoom.loading .zoom-stage img { opacity:.25; }
.zoom.loading .zoom-stage::after { content:"Loading the photo…"; position:absolute; color:#e4e4e7; font-size:13px; }
.zoom-note { position:absolute; color:#ffd9d4; font-size:13.5px; background:rgba(0,0,0,.5); padding:10px 14px; border-radius:var(--radius-sm); }
.zoom-note[hidden] { display:none; }
/* The stage spans the full width beneath these, so a magnified photo would paint straight over
   the previous arrow (the next one survives only by being later in the DOM). Both sit above it. */
.zoom-nav { grid-row:2; align-self:center; position:relative; z-index:2; background:rgba(255,255,255,.28); color:#fff; border:0; border-radius:50%;
  width:44px; height:44px; font-size:26px; line-height:1; cursor:pointer; margin:0 10px; }
.zoom-nav:hover { background:rgba(255,255,255,.28); } .zoom-nav[hidden] { display:none; }
.zoom-nav.prev { grid-column:1; } .zoom-nav.next { grid-column:3; }
body.noscroll { overflow:hidden; }
@media (max-width:640px) { .zoom-bar { flex-wrap:wrap; gap:8px; } .zoom-bar .zoom-tab { margin-left:0; } .zoom-nav { width:36px; height:36px; font-size:20px; margin:0 4px; } }
/* 🛑 AN ORDINARY BLOCK, deliberately (owner, 2026-09-30: "why is the middle not scrolling with the
   left and right panels"). This was sticky with a max-height and overflow:auto from the console's
   first commit, back when nothing else on the page was pinned and the buttons had to stay in reach
   while a VA read down a long thread. Pinning the facts strip that morning took the rationale away
   and left the bill: a panel sized at window less header less strip is 636px on a 1440x900 screen,
   and it holds 1457px of verbs, gates, the reply she is halfway through and Confirm. 823px of the
   work surface sat behind a scrollbar of its own, the wheel over the middle moved the panel while
   the page stood still, and with the strip pinned above it the screen had two frozen bands and one
   moving column.

   The trade was made with eyes open: scroll far enough into a long thread and the verbs leave the
   top of the screen. What the decision is checked AGAINST is what stays pinned, which is the strip.

   Put nothing here that makes it a scroll container again - no max-height, no overflow - or the
   823px comes back. browser.test.cjs measures both. */
.panel { background:var(--card); border:1px solid var(--line); border-radius:var(--radius); padding:18px 20px;
  box-shadow:var(--shadow); }
.panel > h2:first-child { margin-top:0; }
/* Which order the decision is about, when she has more than one. Sits above the action buttons and
   reads as part of the question, not as a filter: the label is on the same line as the box. */
.decide-order { display:flex; align-items:center; gap:8px; margin:10px 0 2px; font-size:12px;
  font-weight:700; text-transform:uppercase; letter-spacing:.06em; color:var(--muted); }
.decide-order select { flex:1; min-width:0; font:inherit; font-size:12.5px; font-weight:400;
  text-transform:none; letter-spacing:0; color:var(--ink); padding:6px 8px;
  border:1px solid var(--line); border-radius:var(--radius-sm); background:var(--card); }
/* 🛑 A GRID, BECAUSE EVERY BUTTON HAS TO BE THE SAME SIZE (owner, 2026-10-01: "can you make them
   all the same sizes"). Asked for after seeing flex-grow pack them flush, which levels each ROW
   but leaves the buttons inside it all different widths.

   The cost was argued both ways before it was asked for outright, and it is real: a grid makes
   every cell as wide as the widest label, so where flex fitted "Issue Refund", "Hand Off" and
   "Remove an Item" on one row, the grid fits two. Sameness is worth more than the rows. Do not
   quietly put flex back to win the height; it was a deliberate call.

   206px is the widest label ("Pause or Release Shipping") plus room for the typeface it lands in.
   It was 196 in Urbanist; the system font (2026-10-01) set it at 198 in Segoe UI and SF Pro runs a
   little wider, so the margin is there for whichever font the VA's machine has. Go below it and the long
   labels wrap and the sameness quietly stops being true, which is why browser.test.cjs asserts
   one width, one height AND no pill narrower than its own text. The consequence is a real
   breakpoint rather than a smooth one: the panel is 494px inside at 1821 and takes two columns,
   and 360px at 1440 where two do not fit and it becomes one taller column. The only way to buy
   two columns at 1440 was 11px type, measured, and that is too small for the main control on the
   page. */
.verbs { display:grid; grid-template-columns:repeat(auto-fit, minmax(206px, 1fr)); gap:6px; margin:8px 0 12px; }
.verb { border:1px solid var(--line); background:var(--card); padding:6px 12px; border-radius:999px; cursor:pointer; font:inherit; font-size:12.5px; font-weight:600; color:var(--fg); transition:background .12s, border-color .12s; }
/* Lined up (owner, 2026-10-03: "can we have all of this lined up"): the number in one column and
   every label starting at the same edge, rather than each centred on its own width. */
.verbs .verb { display:flex; align-items:center; gap:8px; text-align:left; min-width:0; }
.verbs .verb kbd { flex:none; min-width:20px; text-align:center; }
.verb:hover { background:var(--accent-soft); } .verb.on { border-color:var(--accent); background:var(--accent); color:#fff; } .verb.on kbd { background:rgba(255,255,255,.2); border-color:transparent; color:#fff; }
.verb.money { border-color:color-mix(in srgb, var(--warn) 35%, var(--card)); } .verb.money.on { border-color:var(--accent); }
.plan { border:1px solid var(--line); border-radius:var(--radius-sm); padding:12px 14px; margin:10px 0; font-size:13px; background:var(--tint); } .plan pre { margin:6px 0; white-space:pre-wrap; font:12px/1.45 var(--mono); }
.gates { width:100%; border-collapse:collapse; margin:6px 0; } .gates td { padding:5px 6px; border-bottom:1px solid var(--line-soft); vertical-align:top; } .gates td.g-hard { font-weight:700; }
.lint { font-size:13px; margin:8px 0; } .lint .v { border-left:3px solid var(--stop); padding:6px 10px; margin:6px 0; background:var(--stop-bg); border-radius:0 8px 8px 0; } .lint .v.ov { border-color:var(--warn); background:var(--warn-bg); } .lint .v small { display:block; color:var(--muted); }
.result { border:1px solid var(--line); border-radius:var(--radius-sm); padding:14px; margin-top:12px; font-size:13.5px; } .result.ok { border-color:var(--ok); background:var(--ok-bg); } .result.bad { border-color:var(--stop); background:var(--stop-bg); } .result.part { border-color:var(--warn); background:var(--warn-bg); }
.result ol { margin:8px 0 0 18px; padding:0; } .result li small { color:var(--muted); display:block; white-space:pre-wrap; }
.confirm { margin-top:12px; padding-top:12px; border-top:1px solid var(--line); }
.side section { background:var(--card); border:1px solid var(--line); border-radius:var(--radius); padding:12px 16px 14px; margin-bottom:12px; font-size:13.5px; box-shadow:var(--shadow); } .side h3 { margin:2px 0 8px; display:flex; align-items:center; gap:8px; }
/* The order card's title row: its number, copy, Find order and Unlink are wider than a 360 px phone (2026-11-02). */
.side h3.wraps { flex-wrap:wrap; row-gap:2px; }
ul.plain { list-style:none; margin:0; padding:0; } ul.plain li { padding:6px 0; border-bottom:1px solid var(--line-soft); } ul.plain li:last-child { border:0; } ul.plain li.primary { background:var(--accent-soft); margin:0 -8px; padding:6px 8px; border-radius:8px; }
.labels { display:flex; flex-wrap:wrap; gap:5px; margin-bottom:8px; } .quick { display:flex; flex-wrap:wrap; gap:5px; margin:8px 0; }
.proposals .proposal { background:var(--warn-bg); border:1px solid color-mix(in srgb, var(--warn) 25%, var(--card)); border-radius:var(--radius-sm); padding:10px 12px; margin-bottom:8px; font-size:13px; }
/* Why the order check refused, as a sentence. A .chip is nowrap by design, so a refusal put inside
   one ran off the side of the panel (owner, 2026-09-14). The verdict stays a chip; the reason wraps. */
.proposals .proposal .why { margin:5px 0 2px; font-size:12.5px; line-height:1.45; overflow-wrap:anywhere; }
.proposals .proposal .why.stop { color:var(--stop); } .proposals .proposal .why.warn { color:var(--warn); }
/* Classes, not style="" attributes: the pages' CSP (style-src 'self') blocks inline styles, so the
   quantity boxes and the Find order field rendered unsized and logged a violation (2026-09-16). */
.qty-in { width:60px; } #lookup .lookup-q { flex:1; }
/* The "keep it, 80% back" figure under the template picker: its own line in the flex row, and a
   long product name wraps rather than pushing the column sideways on a phone. */
#refund-pairs { flex-basis:100%; min-width:0; overflow-wrap:anywhere; }
#refund-pairs .refund-pairs-list { display:flex; flex-direction:column; gap:4px; margin:6px 0; }
/* A change nobody confirmed that the order shipped without (2026-09-16): red, not the amber of a live proposal. */
.proposals .proposal.missed { background:var(--stop-bg); border-color:color-mix(in srgb, var(--stop) 22%, var(--card)); }
/* The step a card names, button first and the one line beside it (proposal #37). It wraps rather
   than squeezing the sentence, because the panel is a narrow column. */
.proposals .proposal .step { display:flex; align-items:center; flex-wrap:wrap; gap:8px; margin-top:8px; }
.proposals .proposal .step small { font-size:12.5px; line-height:1.4; }
#find-order-box { margin-top:8px; } #find-order-box table { font-size:12.5px; }
/* 🛑 The columns answer to the width the case body ACTUALLY has, not to the window. Media queries
   measured the viewport, so every piece of chrome beside the case (the queue rail, and since
   2026-10-01 the sidebar) had to be subtracted by hand in a breakpoint per combination, and a
   missed one meant three columns of minimums squeezed into less and a case that scrolled
   sideways. A container query reads .casebody's own width, so the rail and the sidebar can open
   and shut without anyone re-doing the arithmetic. 1042 is the grid's own minimum (360 + 390 +
   260 + two 16px gaps); 796 is where two columns stopped fitting before. Nothing position:fixed
   lives inside .casebody (the photo viewer is appended to body, dialogs open in the top layer),
   so its layout containment is safe. */
.casebody { container-type:inline-size; container-name:casebody; }
@container casebody (max-width:1041px) { .cols { grid-template-columns:1fr 1fr; }
  .side { grid-column:1 / -1; display:grid; grid-template-columns:repeat(auto-fill,minmax(280px,1fr)); gap:12px; } }
@container casebody (max-width:795px) { .cols { grid-template-columns:1fr; } }
/* Narrower than this there is no width to split: the rail goes, and the Back link is the way out. */
@media (max-width:1000px) { .caseview { display:block; } .qrail { display:none; } }
@media (max-width:900px) { main { padding:20px 16px 48px; } .grid2 { grid-template-columns:1fr; } .cols { grid-template-columns:1fr; } }
/* Internal notes: a chat-style log VAs leave for each other on a customer (owner 2026-09-06). */
.notelog { display:flex; flex-direction:column; gap:8px; margin-bottom:10px; max-height:360px; overflow:auto; }
.notelog .note { padding:8px 10px; border-radius:10px; background:var(--accent-soft); font-size:13px; }
.notelog .note header { display:flex; align-items:center; gap:6px; margin-bottom:3px; }
.notelog .note .who { display:inline-block; padding:1px 8px; border-radius:999px; font-weight:700; font-size:12px; }
.notelog .note small { color:var(--muted); }
.notelog .note p { margin:0; white-space:pre-wrap; overflow-wrap:anywhere; }
/* The newest note a person left on her, in the Identity tile (Elica, 2026-10-04: "I want to be able
   to see the internal note all the time"). Name, age and words clamped to two lines, because this
   tile sets the height of the whole strip; the hover has the note in full, and so does Notes. */
.idnote { font-size:12.5px; line-height:1.4; }
.idnote-line { display:-webkit-box; -webkit-box-orient:vertical; -webkit-line-clamp:2; line-clamp:2; overflow:hidden; }
.idnote .tone { padding:0 6px; font-size:11.5px; font-weight:700; }
.idnote small { font-size:11.5px; }
/* Labels + no-reply actions: one horizontal bar above the fact tiles (owner 2026-09-06). */
.labelsbar { display:flex; justify-content:space-between; align-items:center; flex-wrap:wrap; gap:10px 24px; background:var(--card); border:1px solid var(--line); border-radius:var(--radius-sm); padding:9px 13px; margin:12px 0 0; box-shadow:var(--shadow); font-size:13.5px; }
.labelsbar .labels, .labelsbar .quick { margin:0; align-items:center; }
.labelsbar .k { font-size:12.5px; font-weight:500; color:var(--muted); margin-right:6px; }

/* the date-range picker (Home, Money, Metrics) and the money tiles */
.range-head { display:flex; align-items:center; gap:14px; flex-wrap:wrap; margin:18px 0 4px; } .range-head h2 { margin:0; }
.range-head form.range { margin:0; }
/* The picker button and the panel behind it (owner 2026-09-17, Shopify's calendar).
   A <details>, so it opens without JS; the month grid inside it is drawn by app.js. */
details.range-pick { position:relative; }
details.range-pick > summary { list-style:none; cursor:pointer; }
details.range-pick > summary::-webkit-details-marker { display:none; }
summary.range-btn { display:inline-flex; align-items:center; gap:8px; padding:7px 13px; border:1px solid var(--line); border-radius:999px; background:var(--card); font-size:13.5px; font-weight:600; box-shadow:0 1px 2px rgba(16,16,20,.05); }
summary.range-btn:hover { background:var(--accent-soft); }
details.range-pick[open] > summary.range-btn { border-color:var(--accent); box-shadow:var(--focus); }
summary.range-btn .ico { color:var(--muted); } summary.range-btn .dates { font-weight:600; font-size:12.5px; }
summary.range-btn .caret { color:var(--muted); transition:transform .12s; } details.range-pick[open] > summary.range-btn .caret { transform:rotate(180deg); }
/* width:max-content because an absolutely positioned box is sized against its containing block —
   here the button — so without it the calendar wraps under the presets and is cut off. */
.range-pop { position:absolute; top:calc(100% + 7px); left:0; z-index:6; display:flex; gap:12px; padding:12px; width:max-content; max-width:calc(100vw - 48px); background:var(--card); border:1px solid var(--line); border-radius:var(--radius); box-shadow:0 2px 6px rgba(16,16,20,.08), 0 14px 40px rgba(16,16,20,.14); }
.range-pop .presets { display:flex; flex-direction:column; gap:1px; padding-right:12px; border-right:1px solid var(--line); min-width:132px; }
.range-pop .presets a { padding:7px 11px; border-radius:var(--radius-sm); color:var(--fg); font-size:13px; font-weight:600; white-space:nowrap; }
.range-pop .presets a:hover { background:var(--accent-soft); text-decoration:none; }
.range-pop .presets a.on { background:var(--accent); color:var(--accent-ink); }
.range-fields { display:flex; align-items:center; gap:8px; margin-bottom:10px; }
.range-fields input { font-size:13px; padding:7px 10px; } .range-fields .arrow { color:var(--muted); }
.cal { display:flex; gap:20px; }
.cal .mon { width:216px; }
.cal .mhead { display:flex; align-items:center; justify-content:space-between; gap:4px; font-size:13px; font-weight:700; margin-bottom:4px; min-height:26px; }
.cal .dow, .cal .days { display:grid; grid-template-columns:repeat(7, 1fr); }
.cal .dow span { font-size:10px; font-weight:700; letter-spacing:.04em; text-transform:uppercase; color:var(--muted); text-align:center; padding:4px 0; }
.cal button.d { border:0; background:none; padding:0; height:28px; border-radius:var(--radius-sm); font:inherit; font-size:12.5px; font-weight:600; color:var(--fg); cursor:pointer; }
.cal button.d:hover:not(:disabled) { background:var(--accent-soft); }
.cal button.d:disabled { color:var(--line); cursor:default; }
.cal button.d.blank { visibility:hidden; }
.cal button.d.in { background:var(--accent-soft); border-radius:0; }
.cal button.d.edge { background:var(--accent); color:var(--accent-ink); }
.cal button.d.edge.from:not(.to) { border-radius:var(--radius-sm) 0 0 var(--radius-sm); }
.cal button.d.edge.to:not(.from) { border-radius:0 var(--radius-sm) var(--radius-sm) 0; }
.cal button.nav { border:0; background:none; cursor:pointer; color:var(--muted); font:inherit; padding:3px 7px; border-radius:999px; line-height:1; }
.cal button.nav:hover:not(:disabled) { background:var(--accent-soft); color:var(--fg); } .cal button.nav:disabled { opacity:.3; cursor:default; }
.range-go { display:flex; align-items:center; gap:8px; margin-top:12px; }
.range-go [data-cal-say] { margin-right:auto; }
@media (max-width:760px) { .range-pop { flex-direction:column; width:auto; } .range-pop .presets { flex-direction:row; flex-wrap:wrap; border-right:0; border-bottom:1px solid var(--line); padding:0 0 8px; } .cal { flex-direction:column; } }
/* A card that opens with its own picker: the heading is inside .range-head, so it no longer
   matches `.card > h2:first-child` and would sit 18px low. Same rule, one level out. */
.card > .range-head:first-child { margin-top:0; }
.tiles.money .tile .n { color:var(--accent, inherit); }
/* The Money page's Out of pocket figure: the sum its neighbours add up to, ringed like the date picker. */
.tiles .tile.total { border:2px solid var(--accent); padding:17px 19px; }

/* ---------- the chart under the Home tiles (owner 2026-09-17)
   The CSP forbids inline styles, so the SVG carries geometry only and every colour is here. */
.tile.pick { font:inherit; text-align:left; cursor:pointer; align-items:stretch; }
.tile.pick:hover { background:var(--accent-soft); }
.tile.pick.on { border-color:var(--accent); box-shadow:none; }
.tile.pick .n, .tile.pick .l, .tile.pick .s, .tile.pick .d { text-align:left; }
figure.chart { margin:0 0 22px; background:var(--card); border:1px solid var(--line); border-radius:var(--radius); box-shadow:var(--shadow); padding:18px 22px 10px; }
figure.chart figcaption { display:flex; align-items:baseline; gap:8px; flex-wrap:wrap; font-size:13px; margin-bottom:2px; }
figure.chart figcaption .name { font-size:14px; }
figure.chart figcaption .by { font-size:12.5px; }
figure.chart figcaption .legend { margin-left:auto; font-size:12px; display:inline-flex; align-items:center; gap:6px; }
figure.chart .legend .k { width:14px; height:0; border-top:2.5px solid var(--data); display:inline-block; margin-left:8px; }
figure.chart .legend .k.prev { border-top-style:dashed; border-color:var(--muted); }
figure.chart .plot { position:relative; }
figure.chart svg { width:100%; height:auto; display:block; overflow:visible; }
figure.chart .grid { stroke:var(--line); stroke-width:1; }
figure.chart .ylab, figure.chart .xlab { fill:var(--muted); font:11px var(--font); font-weight:600; }
figure.chart .ylab { text-anchor:end; dominant-baseline:middle; }
figure.chart .area { fill:var(--data); opacity:.08; }
figure.chart .line { fill:none; stroke-linejoin:round; stroke-linecap:round; }
figure.chart .line.cur { stroke:var(--data); stroke-width:2.5; }
figure.chart .line.prev { stroke:var(--muted); stroke-width:1.75; stroke-dasharray:5 5; opacity:.75; }
figure.chart .hit { fill:transparent; }
figure.chart .cross { stroke:var(--data); stroke-width:1; stroke-dasharray:3 3; opacity:.5; }
figure.chart .dot.cur { fill:var(--data); stroke:var(--card); stroke-width:2; }
figure.chart .dot.prev { fill:var(--muted); stroke:var(--card); stroke-width:2; }
.chart-tip { position:absolute; pointer-events:none; transform:translate(-50%, -100%); background:var(--fg); color:#fff; border-radius:var(--radius-sm); padding:7px 10px; font-size:12px; line-height:1.45; white-space:nowrap; box-shadow:var(--shadow); z-index:3; }
.chart-tip b { display:block; font-size:12.5px; }
.chart-tip .p { color:#d4d4d8; }

/* folded template groups */
details.group { margin:6px 0; background:var(--card); border:1px solid var(--line); border-radius:var(--radius); box-shadow:var(--shadow); }
details.group summary { cursor:pointer; padding:12px 16px; list-style:none; display:flex; align-items:center; gap:10px; }
details.group summary::-webkit-details-marker { display:none; } details.group summary::before { content:"▸"; color:var(--muted); width:12px; } details.group[open] summary::before { content:"▾"; }
details.group .tbl { margin:0; border-top:1px solid var(--line); }

/* /templates?view=all — every piece of wording in full, for reading rather than editing (the CS
   manager, 2026-10-01: "I'd have to click through them one by one"). Measured for reading: the
   body is capped near 70 characters a line, which is where prose stops being comfortable, and the
   wording is the only thing at full contrast so a page of sixty is scannable by heading. */
/* The whole column is held to a reading width rather than the text inside a full-width card: a
   66-character line floating in a 1130px box reads as a rendering fault, not as typography. */
.spiel-group { margin:0 0 26px; max-width:780px; }
.spiel-group > h3 { margin:0 0 10px; padding-bottom:6px; border-bottom:2px solid var(--line); }
.spiel { background:var(--card); border:1px solid var(--line); border-radius:var(--radius); box-shadow:var(--shadow); padding:12px 16px; margin:0 0 10px; }
.spiel h4 { margin:0 0 4px; font-size:14.5px; }
.spiel .when { margin:0 0 8px; font-size:13px; color:var(--muted); }
/* pre-wrap, not pre: a template body is prose with paragraph breaks in it, and a line that runs off
   the side of the card is the thing this page exists to stop. */
.spiel pre { margin:0; white-space:pre-wrap; overflow-wrap:anywhere; font:14px/1.6 var(--font); }
@media print { .spiel { break-inside:avoid; box-shadow:none; } header.sidebar, .blurb a, nav { display:none; } }

/* the delta line sits at the bottom of every tile, so a row of tiles lines up whatever the subline wraps to */
.tile.link > a { flex:1; }

/* a person's weekly feedback note (Home) and the form that writes it (People) */
.feedback { background:var(--accent-soft); border-radius:var(--radius-sm); padding:10px 12px; margin:8px 0; } .feedback p { margin:4px 0 0; }
.feedback-form textarea { min-width:260px; }

/* the "viewing as a VA" strip */
.banner.info { background:var(--accent-soft); color:var(--accent); } .banner.info a { font-weight:700; margin-left:6px; }

/* A discarded draft never reached anyone, so it is collapsed out of the conversation and reads as
   an aside rather than a message (owner, 2026-09-07: "confusing and misleading"). Opening it
   shows the card, dimmed, so the record is still there for whoever needs it. */
details.msg-deleted > summary { cursor:pointer; padding:6px 2px; list-style:none; border-left:3px solid var(--line); padding-left:10px; }
details.msg-deleted > summary::-webkit-details-marker { display:none; }
details.msg-deleted > summary::before { content:"▸ "; color:var(--muted); }
details.msg-deleted[open] > summary::before { content:"▾ "; }
details.msg-deleted > summary:hover { color:var(--ink); }
details.msg-deleted .msg { opacity:.55; border-style:dashed; margin-top:6px; }

/* A house-style note is not a rule and must not look like one (owner, 2026-09-07). Red for both
   taught people to scroll past both. */
.lint .v.note { border-color:var(--line); background:transparent; color:var(--muted); }
.lint .v.note b { font-weight:600; }

/* "Here is what will happen": the send preview. Plain and calm on purpose (owner, 2026-09-07:
   "very friendly and beginner like and easy to use"), and visibly a preview rather than a result,
   so nobody reads it as something that already happened. */
.preview { border:1px solid var(--line); border-radius:var(--radius-sm); padding:14px 16px; margin:10px 0; background:#fff; font-size:13.5px; }
.preview h4 { margin:0 0 10px; font-size:15px; }
.preview .pv-mail { border:1px solid var(--line); border-radius:var(--radius-sm); overflow:hidden; margin-bottom:12px; }
.preview .pv-hdr { padding:6px 12px; background:var(--tint); border-bottom:1px solid var(--line); font-size:12.5px; color:var(--muted); }
.preview .pv-hdr b { color:var(--fg); margin-right:6px; font-weight:600; }
.preview .pv-body { padding:12px; line-height:1.55; }
/* The files, under the body of the mail card, which is where the customer will see them. */
.preview .pv-att { padding:8px 12px; border-top:1px dashed var(--line); background:var(--tint); font-size:12.5px; display:flex; flex-wrap:wrap; gap:6px 14px; align-items:center; }
.preview .pv-att b { color:var(--muted); font-weight:600; }

/* Attaching a file to a reply (Jasper, 2026-09-16). */
.attach { margin:8px 0 2px; }
.attach-list { list-style:none; margin:8px 0 0; padding:0; display:flex; flex-direction:column; gap:6px; }
.attach-list li { display:flex; align-items:center; gap:9px; padding:5px 9px; border:1px solid var(--line); border-radius:var(--radius-sm); background:var(--card); font-size:12.5px; }
/* A thumbnail, not just a filename: the mistake this feature makes possible is attaching the right
   KIND of file about the wrong customer, and only the picture catches that. */
.attach-list img { width:34px; height:34px; object-fit:cover; border-radius:4px; background:var(--accent-soft); flex:0 0 auto; }
.attach-list .doc { width:34px; height:34px; display:inline-flex; align-items:center; justify-content:center; border-radius:4px; background:var(--accent-soft); color:var(--accent); font-size:9.5px; font-weight:700; flex:0 0 auto; }
.attach-list .n { flex:1; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; font-weight:600; }
.attach-list .sz { color:var(--muted); font-variant-numeric:tabular-nums; white-space:nowrap; }
.preview .pv-list { list-style:none; margin:0; padding:0; }
.preview .pv-list li { padding:5px 0; border-bottom:1px solid var(--line); }
.preview .pv-list li:last-child { border-bottom:0; }
.preview .pv-warn { margin-top:10px; background:var(--warn-bg); color:#6b4600; border-radius:var(--radius-sm); padding:8px 12px; }
.preview .pv-warn ul { margin:4px 0 0; padding-left:18px; }
.preview .pv-stop { margin-bottom:10px; background:var(--stop-bg); color:#7a1a14; border-radius:var(--radius-sm); padding:8px 12px; }

/* What happened, after a return bought its own label (owner, 2026-09-21: a pop-up of everything
   that was done, the sheet row to check, and a message to copy into the warehouse group chat).
   The row is a two-column table because it is read against the sheet, cell by cell. */
.preview .done-list { margin:0 0 14px; padding-left:18px; }
.preview .done-list li { padding:2px 0; }
.preview h3 { margin:0 0 10px; font-size:16px; }
.preview h4 { display:flex; gap:8px; align-items:baseline; }
table.sheet-row { width:100%; border-collapse:collapse; margin:0 0 14px; font-size:12.5px; }
table.sheet-row th { text-align:left; width:38%; padding:4px 8px 4px 0; color:var(--muted); font-weight:600;
  border-bottom:1px solid var(--line); vertical-align:top; }
table.sheet-row td { padding:4px 0; border-bottom:1px solid var(--line); vertical-align:top; word-break:break-word; }
.chat-copy { display:flex; gap:8px; align-items:flex-start; background:var(--tint); border:1px solid var(--line);
  border-radius:var(--radius-sm); padding:10px 12px; }
.chat-copy pre { margin:0; flex:1; white-space:pre-wrap; word-break:break-word; font-size:12.5px; line-height:1.5; font-family:inherit; }

/* The preview opens as a dialog (owner, 2026-09-07: "makes the scroll too long"). The last look
   at an email and the button that sends it sit together, with the page behind dimmed so there is
   nothing else to read past. The email itself scrolls inside; the buttons never leave the view. */
dialog.modal { width:min(680px, calc(100vw - 32px)); max-height:calc(100vh - 64px); padding:0; border:1px solid var(--line);
  border-radius:var(--radius); background:var(--card); color:var(--fg); box-shadow:0 18px 48px rgba(0,0,0,.22);
  display:flex; flex-direction:column; overflow:hidden; }
dialog.modal:not([open]) { display:none; }
dialog.modal::backdrop { background:rgba(16,16,20,.36); }
dialog.modal .preview { margin:0; border:0; border-radius:0; overflow-y:auto; padding:18px 20px; }
dialog.modal .row.confirm { margin:0; padding:12px 20px; border-top:1px solid var(--line); background:var(--tint); align-items:center; }
dialog.modal .row.confirm .grow { flex:1; }
/* Counting down to the send: not greyed out like a refusal, because it is going. */
#confirm.counting:disabled { opacity:1; cursor:progress; }

/* Her message, above the draft: the thing the reply has to answer. Visibly hers, not ours — same
   warm ground as an inbound message in the thread column, and foldable when it is long. */
.preview .pv-chose { margin-bottom:10px; background:var(--warn-bg); color:#6b4600; border-radius:var(--radius-sm); padding:8px 12px; line-height:1.5; }
.preview .pv-hers { margin-bottom:12px; border:1px solid var(--line); border-radius:var(--radius-sm); background:var(--accent-soft); }
.preview .pv-hers > summary { padding:7px 12px; cursor:pointer; font-size:12.5px; color:var(--muted); }
.preview .pv-hers > summary b { color:var(--fg); font-weight:600; }
.preview .pv-hers .pv-hers-body { padding:0 12px 10px; white-space:pre-wrap; line-height:1.5; max-height:230px; overflow-y:auto; }
/* The one row of the "what will happen" list you choose rather than read. */
.preview .pv-list li.pv-opt { padding:9px 0; }
/* The team's labels on the last look. A wrapping row of checkboxes rather than a grid: the names
   are whatever a VA typed, so column widths would be set by the longest one somebody ever made. */
.preview .pv-lbl-h { display:block; font-size:13px; margin-bottom:5px; }
.preview .pv-labels { display:flex; flex-wrap:wrap; gap:4px 14px; margin-bottom:4px; }
.preview .pv-lbl { display:inline-flex; align-items:center; gap:5px; font-size:13.5px; }
.preview .pv-list li.pv-opt label { display:block; cursor:pointer; }
.preview .pv-list li.pv-opt small { display:block; margin-left:22px; margin-top:2px; }

/* ---- Settings: the mode picker and the day switches (2026-09-07) ------------------------- */
/* Each mode is a whole readable row, not a bare word in a dropdown: the choice decides whether
   the system emails real customers unattended, so what it does is next to what it is called. */
.modes { display:flex; flex-direction:column; gap:8px; margin:4px 0 6px; }
.mode-opt { display:flex; gap:10px; align-items:flex-start; padding:11px 14px; border:1px solid var(--line); border-radius:var(--radius-sm); cursor:pointer; background:var(--card); transition:background .12s, border-color .12s; font-weight:500; color:var(--fg); font-size:14px; }
.mode-opt:hover { background:var(--accent-soft); }
.mode-opt.on { border-color:var(--accent); background:var(--accent-soft); }
.mode-opt input { margin-top:3px; flex:0 0 auto; }
.mode-opt > span { display:flex; flex-direction:column; gap:3px; min-width:0; }
.mode-title { display:flex; gap:8px; align-items:center; flex-wrap:wrap; }
.mode-opt small { font-size:12.5px; line-height:1.5; white-space:normal; font-weight:400; }
.days { display:flex; gap:6px; flex-wrap:wrap; margin-top:4px; }
.days .day { display:inline-flex; align-items:center; gap:5px; padding:6px 11px; border:1px solid var(--line); border-radius:999px; cursor:pointer; font-size:12.5px; font-weight:600; color:var(--fg); background:var(--card); }
.days .day:has(input:checked) { border-color:var(--accent); background:var(--accent-soft); color:var(--accent); }
/* Saved / refused, said where the button is rather than in an alert box. */
.saved-note:empty { display:none; }
.saved-note { font-weight:600; }
/* Hours that 24/7 makes irrelevant: dimmed, not disabled — clicking one is how you turn them on. */
.hours-row.off { opacity:.42; }
/* 🛑 No inline style= anywhere on this page: the console sends `style-src 'self'`, so an inline
   style is silently dropped by the browser and the layout quietly differs from what the code says. */
.tile .n.now { font-size:17px; }
.hours-row .hour { flex:0 0 auto; }
#pause-form .why { flex:1 1 260px; }
#schedule-form .mins { max-width:110px; }

/* ---- One colour per person (owner, 2026-09-06) ------------------------------------------- */
/* 🛑 Here, and never in a style="" attribute. The console sends `style-src 'self'`, so an inline
   style is blocked and dropped by the browser: this feature existed in the markup for a day and
   never once rendered. http/colors.cjs emits data-color=; these rules are what makes it visible.
   Add a colour there and you must add its rule here — console.test.cjs fails if you forget. */
.tone[data-color] { border-radius:999px; }
.tone[data-color="purple"] { background:#7c3aed; color:#fff; }
.tone[data-color="blue"]   { background:#2563eb; color:#fff; }
.tone[data-color="green"]  { background:#16a34a; color:#fff; }
.tone[data-color="yellow"] { background:#facc15; color:#3b2f00; }
.tone[data-color="orange"] { background:#f97316; color:#fff; }
.tone[data-color="pink"]   { background:#db2777; color:#fff; }
.tone[data-color="teal"]   { background:#0d9488; color:#fff; }
.tone[data-color="red"]    { background:#dc2626; color:#fff; }
/* Holographic chrome: soft diagonal bands, like light on a foil sheet (the owner's, by request). */
#presence.tone[data-color="chrome"], .tone[data-color="chrome"] { background:linear-gradient(105deg,#dff7ff 0%,#e6e6ff 12%,#ffe3f3 24%,#e1fff6 36%,#fff6d8 46%,#dff7ff 56%,#e9e3ff 68%,#ffe1ef 80%,#dffcff 92%,#f3e9ff 100%); color:#2b2740; }
/* Owner 2026-09-09: let the team pick their own, not just live with a hash. Gold is flat like the
   rest of the palette; rainbow scrolls so it reads as chosen flair, not a mis-set gradient. */
.tone[data-color="gold"] { background:#b4860b; color:#fff; }
.tone[data-color="rainbow"] { background:linear-gradient(90deg,#ff5b5b,#ffb347,#ffe066,#6fd08c,#5bb8ff,#8f7bff,#ff7bd0,#ff5b5b); background-size:200% 100%; color:#1f1a19; animation:rainbow-scroll 6s linear infinite; }
@keyframes rainbow-scroll { from { background-position:0% 0; } to { background-position:200% 0; } }
@media (prefers-reduced-motion:reduce) { .tone[data-color="rainbow"] { animation:none; background-position:0 0; } }

/* ---- Name-colour and theme pickers, on the Me page (owner, 2026-09-09) ------------------- */
.swatches { display:flex; flex-wrap:wrap; gap:8px; margin:6px 0 4px; }
.swatch { width:34px; height:34px; border-radius:50%; border:2px solid transparent; cursor:pointer; padding:0; display:inline-flex; align-items:center; justify-content:center; font-size:13px; font-weight:700; color:#fff; }
.swatch.on { border-color:var(--fg); box-shadow:0 0 0 2px var(--bg); }
/* Already somebody else's. Dimmed rather than hidden, so the row of colours stays in the same
   order for everyone and "the third one along" means the same thing to two people talking. */
.swatch.taken { opacity:.25; filter:grayscale(.6); cursor:not-allowed; }
.swatch[data-swatch="auto"] { background:repeating-conic-gradient(var(--line) 0 25%, var(--card) 0 50%) 0 0/14px 14px; color:var(--muted); }
.theme-swatch { width:56px; height:36px; border-radius:var(--radius-sm); border:2px solid var(--line); cursor:pointer; padding:0; }
.theme-swatch.on { border-color:var(--accent); box-shadow:var(--focus); }
/* Preview fills for the theme swatches on the Me page. Kept separate from the body[data-theme]
   rules below: a swatch previews a theme that may not be the viewer's own active one, so it
   cannot just read the live --bg/--accent variables. Values match themes.cjs's `swatch` field. */
.theme-swatch[data-theme="default"]  { background:#f7f7f8; }
.theme-swatch[data-theme="sage"]     { background:#3f6b4a; }
.theme-swatch[data-theme="sky"]      { background:#2f5c85; }
.theme-swatch[data-theme="blush"]    { background:#9a3f56; }
.theme-swatch[data-theme="peony"]    { background:#f4aed0; }
.theme-swatch[data-theme="midnight"] { background:#1c1b1f; }

/* ---- Workspace themes (owner, 2026-09-09): a person's own background and accent. Presets only —
   the CSP (style-src 'self') blocks any inline override, so the choice is a data-theme attribute
   on body and the actual values live here, same mechanism as data-color above. */
body[data-theme="sage"]     { --bg:#f2f5ee; --card:#ffffff; --accent:#3f6b4a; --link:#3f6b4a; --data:#3f6b4a; --accent-soft:#e5efe1; --accent-soft-2:#d5e6cd; --line:#e2e9dc; --line-soft:#eef2ea; }
body[data-theme="sky"]      { --bg:#eff5fa; --card:#ffffff; --accent:#2f5c85; --link:#2f5c85; --data:#2f5c85; --accent-soft:#dfeaf4; --accent-soft-2:#cbdff0; --line:#dce8f2; --line-soft:#eaf2f8; }
body[data-theme="blush"]    { --bg:#faf1f2; --card:#ffffff; --accent:#9a3f56; --link:#9a3f56; --data:#9a3f56; --accent-soft:#f4e2e6; --accent-soft-2:#eccdd3; --line:#f0dde0; --line-soft:#f7ebed; }
/* Peony: pink on the page, not a hint of it. The accent stays a deep rose because --accent is link
   text and the fill under white button text — a pastel there is unreadable, so the pastel is the
   ground and the rose sits on top of it. Cards stay white so they lift off the pink. */
body[data-theme="peony"]    { --bg:#fde3f0; --card:#ffffff; --accent:#b03a72; --link:#b03a72; --data:#b03a72; --accent-soft:#fbdcec; --accent-soft-2:#f5c4de; --line:#f3c6dd; --line-soft:#f9d9ea; }
body[data-theme="midnight"] { --bg:#1c1b1f; --card:#26242a; --fg:#eee8e2; --muted:#a79f9c; --accent:#c9a15a; --accent-ink:#1c1b1f; --link:#d8b56f; --data:#d8b56f; --tint:#2c2a2e; color-scheme:dark; --accent-soft:#33302f; --accent-soft-2:#3d3936; --line:#38353a; --line-soft:#2c2a2e; }

/* The replacement's line list: what she gets, one row per item, quantity beside it.
   Owner, 2026-09-07: "I want to be able to replace the entire order. Or ... if someone orders 3,
   I want to be able to just replace 2." */
.repl-lines { border:1px solid var(--line); border-radius:var(--radius-sm); padding:10px 12px; background:var(--bg); }
.repl-lines .rl-head { display:flex; align-items:center; gap:8px; margin-bottom:8px; }
.repl-lines .rl-head b { font-size:13px; }
/* One block per product; the sizes and colours she gets are the rows underneath it. */
.repl-product { border-top:1px solid var(--line); padding:10px 0 8px; }
.repl-product:first-of-type { border-top:0; }
.repl-product > header { display:flex; align-items:baseline; gap:8px; margin-bottom:6px; }
.repl-product > header b { font-size:14px; }
.repl-units { display:flex; flex-direction:column; gap:4px; }
.repl-unit { display:flex; align-items:center; gap:8px; padding-left:4px; }
label.repl-line { display:flex; align-items:center; gap:10px; flex:1; min-width:0; font-weight:500; cursor:pointer; }
label.repl-line .rl-name { flex:1; min-width:0; }
label.repl-line select.rl-name { font-weight:500; padding:7px 9px; max-width:100%; }
label.repl-line .rl-qty { width:68px; flex:none; text-align:center; }
label.repl-line small { flex:none; }
.rl-drop { flex:none; opacity:0; transition:opacity .12s; }
.repl-unit:hover .rl-drop, .rl-drop:focus { opacity:1; }
.rl-more { margin:6px 0 0 4px; display:inline-block; }
/* The swap's pair rows: one line per pair she has coming, each choosing where that pair goes.
   Owner, 2026-09-28: "this customer ordered 3, its buy 2 get 1 free. it should be easier to
   select what to change." */
label.swap-pair { display:flex; align-items:center; gap:8px; padding:3px 4px; font-weight:500; cursor:pointer; }
label.swap-pair .sp-no { flex:none; width:20px; height:20px; border-radius:50%; background:var(--accent-soft);
  color:var(--fg); font-size:11px; font-weight:600; display:flex; align-items:center; justify-content:center; }
label.swap-pair .sp-from { flex:none; color:var(--muted); }
label.swap-pair .sp-arrow { flex:none; color:var(--muted); }
label.swap-pair select.sp-to { flex:1; min-width:0; font-weight:500; padding:7px 9px; }
label.swap-pair small { flex:none; }
.sp-all { display:flex; align-items:center; gap:6px; margin-left:auto; font-size:13px; color:var(--muted); font-weight:400; }
.sp-all select { padding:5px 8px; font-size:13px; }
.repl-other { margin:10px 0; }
.repl-other summary { cursor:pointer; color:var(--muted); font-size:13px; }
.repl-other .row { margin-top:8px; gap:8px; }
.repl-other select { flex:1; min-width:0; }

/* Actions checked and waiting to go out together on one decision (2026-09-08). */
.queued { margin:8px 0; padding:8px 10px; border:1px dashed var(--line); border-radius:8px; background:var(--accent-soft); }
.queued .chip button.mini { margin-left:4px; padding:0 4px; line-height:1; }

/* The scheduled loop's last run, on Home. A status bar, not a paragraph: it is read at a glance
   and then ignored, so it stays quiet until something is wrong. Reuses the existing warn role
   rather than introducing a colour for one line. */
.runstrip { display:flex; flex-wrap:wrap; align-items:center; gap:6px; margin:0 0 18px; padding:9px 14px;
  background:var(--card); border:1px solid var(--line); border-radius:999px; box-shadow:var(--shadow); }
.runstrip.warn { background:var(--stop-bg); border-color:color-mix(in srgb, var(--stop) 22%, var(--card)); color:#7a1a14; }
.runstrip .mini { margin-left:auto; }
.runs-history { margin-top:22px; padding-top:18px; border-top:1px solid var(--line-soft); }
.runs-history h3 { margin:0 0 10px; font-size:14px; }
/* The header is sticky, so an anchor jump lands with the target's first rows underneath it.
   Settings links here as /settings#runs, and without this the column headings are the part hidden. */
#runs { scroll-margin-top: 24px; }

/* ---------- the question bubble (owner, 2026-09-10)
   A circle in the bottom-right of every page; clicking opens a panel down the right side that is
   tall but never the full height, so the page behind it stays legible. Tokens only, so it follows
   whichever theme the person is on. */
#ask-launcher { position:fixed; right:20px; bottom:20px; z-index:60; width:56px; height:56px; border-radius:50%;
  border:0; background:var(--accent); color:var(--accent-ink); box-shadow:0 6px 20px rgba(16,16,20,.22); cursor:pointer;
  display:flex; align-items:center; justify-content:center; transition:transform .12s ease, box-shadow .12s ease; }
#ask-launcher:hover { transform:translateY(-2px); box-shadow:0 10px 26px rgba(16,16,20,.28); }
#ask-launcher:focus-visible { outline:none; box-shadow:var(--focus), 0 6px 20px rgba(16,16,20,.22); }
#ask-launcher[aria-expanded="true"] { transform:scale(.92); opacity:.85; }

#ask-panel { position:fixed; right:20px; bottom:88px; z-index:61; width:min(430px, calc(100vw - 32px));
  height:min(76vh, 760px); background:var(--card); border:1px solid var(--line); border-radius:var(--radius);
  box-shadow:0 18px 50px rgba(16,16,20,.18); display:flex; flex-direction:column; overflow:hidden; }
#ask-panel header { display:flex; align-items:baseline; gap:8px; padding:12px 14px; border-bottom:1px solid var(--line-soft); flex:0 0 auto; }
#ask-panel header b { font-size:15px; }
#ask-panel header .small { flex:1; }
#ask-close { border:0; background:none; color:var(--muted); font-size:22px; line-height:1; cursor:pointer; padding:0 2px; }
#ask-close:hover { color:var(--fg); }

#ask-log { flex:1 1 auto; overflow-y:auto; padding:14px; display:flex; flex-direction:column; gap:14px; }
.ask-hello p { margin:0 0 10px; color:var(--muted); font-size:13px; }
.ask-hello ul { list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:6px; }
.ask-eg { text-align:left; width:100%; border:1px solid var(--line); background:var(--bg); border-radius:var(--radius-sm);
  padding:8px 10px; font:13px/1.35 var(--font); color:var(--fg); cursor:pointer; }
.ask-eg:hover { border-color:var(--accent); color:var(--accent); }

.ask-turn { display:flex; flex-direction:column; gap:8px; }
.ask-q { align-self:flex-end; max-width:88%; background:var(--accent); color:var(--accent-ink);
  padding:8px 12px; border-radius:var(--radius) var(--radius) 4px var(--radius); font-size:13.5px; }
.ask-a { align-self:stretch; background:var(--bg); border:1px solid var(--line-soft);
  border-radius:4px var(--radius) var(--radius) var(--radius); padding:10px 12px; font-size:13px; }
.ask-a.bad { border-left:3px solid var(--warn); background:var(--warn-bg); }
.ask-a.thinking { color:var(--muted); font-style:italic; }
.ask-a p { margin:6px 0 0; }
.ask-a p:first-child { margin-top:0; }

.ask-scroll { overflow-x:auto; margin:-2px 0 6px; }
.ask-scroll table { border-collapse:collapse; width:100%; font-size:12.5px; }
.ask-scroll th { text-align:left; font-weight:700; color:var(--muted); font-size:11px; text-transform:uppercase;
  letter-spacing:.05em; padding:4px 10px 4px 0; border-bottom:1px solid var(--line); white-space:nowrap; }
.ask-scroll td { padding:5px 10px 5px 0; border-bottom:1px solid var(--line-soft); vertical-align:top; }
.ask-scroll tr:last-child td { border-bottom:0; }

/* Always visible, never behind a toggle: a wrong query a person can see is the point.
   It flows rather than scrolling in its own box — a scrollbar inside a scrollbar inside a scrolling
   panel was three of them stacked in one 430px column, and none of them read as scrollable. */
pre.ask-sql { font:11.5px/1.45 var(--mono); background:var(--card); border:1px solid var(--line);
  border-radius:var(--radius-sm); padding:8px 10px; margin:6px 0 0; white-space:pre-wrap; word-break:break-word;
  color:var(--muted); }

#ask-form { flex:0 0 auto; display:flex; gap:8px; align-items:flex-end; padding:10px 12px; border-top:1px solid var(--line-soft); }
#ask-input { flex:1; resize:none; border:1px solid var(--line); border-radius:var(--radius-sm); padding:9px 10px;
  font:13.5px/1.4 var(--font); color:var(--fg); background:var(--bg); max-height:130px; }
#ask-input:focus { outline:none; border-color:var(--accent); box-shadow:var(--focus); }
#ask-send { flex:0 0 auto; width:36px; height:36px; border-radius:50%; border:0; background:var(--accent);
  color:var(--accent-ink); font-size:16px; cursor:pointer; }
#ask-send:disabled { opacity:.45; cursor:default; }

@media (max-width:560px) {
  #ask-panel { right:8px; left:8px; width:auto; bottom:78px; height:min(72vh, 620px); }
  #ask-launcher { right:14px; bottom:14px; }
}
@media (prefers-reduced-motion:reduce) { #ask-launcher { transition:none; } }
#ask-panel header a { white-space:nowrap; text-decoration:underline; text-underline-offset:2px; }
#ask-panel header a:hover { color:var(--accent); }

/* .btn.primary does not reach a .mini, and on a row of buttons one is the one you probably want. */
.mini.primary { background:var(--accent); color:var(--accent-ink); border-color:var(--accent); }
.mini.primary:hover { background:color-mix(in srgb, var(--accent) 82%, var(--card)); }
/* The one button in a row that cannot be taken back. Quiet until you reach for it: the colour
   carries the warning, not the weight, so it never competes with the primary action next to it. */
.mini.danger { color:var(--stop); }
.mini.danger:hover { background:var(--stop-bg); border-color:var(--stop); }

/* ---------- creators: the gift form
   Was an inline <style> of flex rows, which put Apt beside a full-width street and left State,
   ZIP, Country and Phone jostling on one line at whatever width they happened to want. A 12
   column grid instead, so a field's width says what belongs in it: a two column State is a
   state code, a nine column Street is a street. */
.gift-form { display:flex; flex-direction:column; gap:16px; margin-top:4px; }
.gift-form .gift-group { border:1px solid var(--line); border-radius:var(--radius-sm); padding:14px 16px 16px; margin:0; background:var(--tint); }
.gift-form .gift-group legend { font-size:11.5px; font-weight:700; text-transform:uppercase; letter-spacing:.08em; color:var(--muted); padding:0 6px; }
.gift-form .gift-hint { margin:0 0 10px; }
.gift-form .grid { display:grid; grid-template-columns:repeat(12, 1fr); gap:10px 12px; }
.gift-form .grid label { display:flex; flex-direction:column; gap:4px; font-size:12px; font-weight:600; color:var(--muted); min-width:0; }
.gift-form .f-2 { grid-column:span 2; } .gift-form .f-3 { grid-column:span 3; } .gift-form .f-5 { grid-column:span 5; }
.gift-form .f-6 { grid-column:span 6; } .gift-form .f-9 { grid-column:span 9; } .gift-form .f-12 { grid-column:1 / -1; }
.gift-form .new-platform .small { font-weight:400; }
.gift-form input, .gift-form select, .gift-form textarea { font:inherit; font-size:14px; color:var(--fg); padding:9px 11px; border:1px solid var(--line); border-radius:var(--radius-sm); background:var(--card); width:100%; min-width:0; }
.gift-form input:focus, .gift-form select:focus, .gift-form textarea:focus { outline:none; border-color:var(--accent); box-shadow:var(--focus); }
.gift-form textarea.full { width:100%; resize:vertical; }
.gift-form .items { display:flex; flex-direction:column; gap:8px; margin-bottom:10px; }
.gift-item { display:grid; grid-template-columns:1fr 84px 34px; gap:8px; align-items:start; }
/* The item combobox: a text box that filters, over a menu that still lists everything. */
.item-pick { position:relative; min-width:0; }
.item-menu { position:absolute; z-index:20; left:0; right:0; top:calc(100% + 4px); max-height:290px; overflow-y:auto; background:var(--card); border:1px solid var(--line); border-radius:var(--radius-sm); box-shadow:var(--shadow); padding:4px; }
.item-opt { display:block; width:100%; text-align:left; font:inherit; font-size:13.5px; color:var(--fg); background:none; border:0; border-radius:6px; padding:8px 10px; cursor:pointer; }
.item-opt:hover, .item-opt:focus { background:var(--accent-soft); outline:none; }
.item-opt small { margin-left:6px; font-size:11.5px; font-weight:600; }
.item-none { padding:8px 10px; margin:0; }
/* The one asymmetry worth keeping: a remove button that is the same size as the fields would read
   as a third field. It is a small round x that only appears once there is more than one row. */
.gift-item .drop-item { width:34px; height:34px; padding:0; border-radius:50%; line-height:1; color:var(--muted); }
.gift-item .drop-item:hover { color:var(--stop); background:var(--stop-bg); border-color:var(--stop); }
/* Two ways of shipping, as two cards. Which one is picked decides whether postage is ever bought. */
.gift-form .ship-pick { display:grid; grid-template-columns:1fr 1fr; gap:10px; }
.gift-form .pick { display:flex; gap:10px; align-items:flex-start; padding:12px 14px; border:1px solid var(--line); border-radius:var(--radius-sm); background:var(--card); cursor:pointer; transition:border-color .12s, background .12s; }
.gift-form .pick:hover { border-color:var(--accent-soft-2); background:var(--accent-soft); }
.gift-form .pick:has(input:checked) { border-color:var(--accent); background:var(--accent-soft); box-shadow:inset 0 0 0 1px var(--accent); }
.gift-form .pick input { width:auto; margin:2px 0 0; flex:none; }
.gift-form .pick span { display:flex; flex-direction:column; gap:3px; }
.gift-form .pick b { font-size:13.5px; } .gift-form .pick small { color:var(--muted); font-size:12px; line-height:1.45; }
.gift-form .gift-send { display:flex; align-items:center; gap:14px; flex-wrap:wrap; }
.gift-form .gift-send p { margin:0; flex:1; min-width:260px; }
/* Paste the whole address here and the boxes below fill themselves. */
.addr-paste { margin-bottom:12px; }
.addr-paste label { display:flex; flex-direction:column; gap:4px; font-size:12px; font-weight:600; color:var(--muted); }
.addr-paste textarea { width:100%; resize:vertical; font:inherit; font-size:14px; padding:9px 11px; border:1px dashed var(--line); border-radius:var(--radius-sm); background:var(--card); }
.addr-paste textarea:focus { outline:none; border-style:solid; border-color:var(--accent); box-shadow:var(--focus); }
.addr-paste-said { margin:6px 0 0; color:var(--ok); }
/* The suggestion. Warm rather than alarming: it is an offer, not a refusal. */
.gift-suggest { border:1px solid var(--warn); background:var(--warn-bg); color:#6b4600; border-radius:var(--radius-sm); padding:10px 12px; margin-bottom:12px; font-size:13px; }
.gift-suggest .muted { color:#8a6a2a; margin-top:4px; }
.gift-suggest button { margin-left:6px; vertical-align:baseline; }
@media (max-width: 720px) {
  .gift-form .grid > label, .gift-form .ship-pick { grid-column:1 / -1; }
  .gift-form .ship-pick { grid-template-columns:1fr; }
}
/* The address dropdown. A child of <body> and positioned fixed by app.js, because the box it
   drops out of can be in a table cell and `table.tbl` clips its own overflow. */
.addr-menu { position:fixed; z-index:60; max-width:min(440px, 86vw); max-height:250px; overflow-y:auto;
  background:var(--card); border:1px solid var(--line); border-radius:var(--radius-sm); box-shadow:var(--shadow); padding:4px; }
.addr-opt { display:block; width:100%; text-align:left; font:inherit; font-size:12.5px; line-height:1.45;
  padding:7px 9px; border:0; border-radius:6px; background:none; color:var(--ink); cursor:pointer; }
.addr-opt:hover, .addr-opt.on { background:var(--accent-soft); color:var(--accent); }
/* The Sent table's in-place fixers */
.inline-form { display:inline; }
.fix-form { display:flex; gap:5px; flex-wrap:wrap; margin-top:7px; }
.fix-form input { font:inherit; font-size:12px; padding:5px 7px; border:1px solid var(--line); border-radius:6px; }
details.fix summary { cursor:pointer; font-size:12px; margin-top:5px; color:var(--accent); }
.card.ok { border-left:3px solid var(--ok); } .card.bad { border-left:3px solid var(--warn); } small.bad { color:var(--warn); }
/* What just happened. The order number is the only handle anyone has on the parcel afterwards,
   so it is the first row and the one set in bold. */
.gift-done h2 { margin:0 0 12px; }
.gift-done dl { display:grid; grid-template-columns:120px 1fr; gap:9px 16px; margin:0; align-items:baseline; }
.gift-done dt { font-size:11.5px; font-weight:700; text-transform:uppercase; letter-spacing:.06em; color:var(--muted); }
.gift-done dd { margin:0; font-size:13.5px; line-height:1.5; }
.gift-done dd a { font-size:15px; }
.gift-done .inline-form { margin-left:8px; }
@media (max-width: 560px) { .gift-done dl { grid-template-columns:1fr; gap:2px 0; } .gift-done dd { margin-bottom:8px; } }

/* New ticket popup (2026-09-12). Reuses dialog.modal; only the form inside it is new. */
dialog.modal.new-ticket { width:min(640px, calc(100vw - 32px)); }
.new-ticket .preview { display:grid; gap:12px; }
.new-ticket h2 { margin:0; }
.new-ticket .fld { display:grid; gap:4px; }
.new-ticket .fld > span { font-weight:600; font-size:12.5px; color:var(--muted); }
/* 🛑 A <select> sizes itself to its widest option, and a grid item's default min-width:auto lets
   that width set the column, so the template picker (2026-09-13) dragged every field past the edge
   of the dialog: 640px of popup around 682px of form, with the padding gone and a horizontal
   scrollbar under it. Both halves are needed — the select is a field like the others and is held
   to the same width, and the grid is told a child may be narrower than its own content. */
.new-ticket .preview > *, .new-ticket .fld > * { min-width:0; }
.new-ticket .fld input, .new-ticket .fld textarea, .new-ticket .fld select { width:100%; }
.new-ticket .find-row { display:flex; gap:6px; }
.new-ticket .find-row input { flex:1; }
.nt-results { display:grid; gap:4px; }
button.nt-hit { text-align:left; padding:8px 10px; border:1px solid var(--line); background:var(--card); border-radius:var(--radius-sm); cursor:pointer; font:inherit; font-size:13px; color:var(--fg); }
button.nt-hit:hover { background:var(--accent-soft); }
.nt-notes { display:grid; gap:2px; } .nt-notes p { margin:0; }
.nt-notes ul.nt-open { margin:2px 0 0 18px; padding:0; font-size:12.5px; }

/* ---------- the contact dialogs: address and email, on the case page (owner, 2026-09-23)
   "i feel like its kinda difficult to see everything. can you make it a popup?"

   These were <details> folding open inside the right panel, which is ~230px wide, so eight
   stacked fields ran off the bottom of the card. In a dialog the form gets the window's width and
   the twelve-column grid below can put first/last and city/state/ZIP on one line each.

   The grid rules are the gift form's, reused rather than copied: the two forms ask for the same
   address and there is no reason for them to lay it out differently. */
.contact-modal { width:min(560px, calc(100vw - 32px)); }
.contact-modal h2 { margin:0 0 4px; font-size:17px; }
.contact-modal .why { margin:0 0 12px; }
.contact-modal .grid { display:grid; grid-template-columns:repeat(12, 1fr); gap:10px 12px; }
.contact-modal .grid label { display:flex; flex-direction:column; gap:4px; font-size:12px; font-weight:600; color:var(--muted); min-width:0; }
.contact-modal .f-3 { grid-column:span 3; } .contact-modal .f-6 { grid-column:span 6; } .contact-modal .f-9 { grid-column:span 9; }
.contact-modal input { width:100%; min-width:0; }
.contact-modal .addr-live { margin:10px 0 0; }
/* The buttons sit in the form, so the shared dialog rule that pins the confirm row still applies
   but the negative margin is needed to reach the padded edges of .preview. */
.contact-modal .row.confirm { margin:16px -20px -18px; }
/* The panel button that opens it: a full-width pill, so it reads as its own control rather than
   as a word tacked onto the address above it. */
.cust-addr { margin-top:10px; }
.cust-addr .mini { width:100%; }

/* The label is bought and she wants to stop: where to void it, since no app may (owner 2026-09-23). */
.voidbox { margin:8px 0 2px; padding:9px 11px; background:var(--warn-bg); border:1px solid var(--warn); border-radius:8px; font-size:13px; line-height:1.5; color:#6b4600; max-width:46em; }
.voidbox .btn-like { margin-left:6px; background:var(--card); }
.voidbox small { display:inline-block; margin-top:5px; color:#7d5a12; }
/* Voided already: the job has cancelled the $0 order, so this is finished news, not a task. */
.voidbox.done { background:var(--ok-bg); border-color:var(--ok); color:#1f5c41; }
.voidbox.done small { color:#2c7a57; }

/* ---------- Exchanges & Returns
   🛑 ALL OF THIS LIVED IN A <style> BLOCK ON THE PAGE AND THE BROWSER THREW EVERY LINE OF IT AWAY
   (2026-09-24). The console sends `style-src 'self'`, which blocks an inline <style> element for
   the same reason it blocks style="": both are inline styles. The block had been in
   pages/exchanges.cjs since the page was written, so the green "Do these now" card, the returns
   form's inputs and the preview box had never once rendered, and nothing failed — the markup was
   right and the rules were simply dropped. Same lesson as the per-person colour on 2026-09-07.

   The column widths (owner, same day: "im not a fan of how it runs off the page on the right ...
   make it look nicer"). They only bind on a table that asked for `fixed`, where the widths in the
   head row ARE the layout; on an auto table a browser would treat them as a suggestion and hand
   the space to whichever column held the longest tracking number instead.

   🛑 EACH TABLE'S OWN SET, AND EACH SET ADDS TO 100. The three tables share column renderers but
   not column counts, so one shared set of widths leaves a remainder, and a fixed layout gives the
   whole remainder to the last column: 53% of the Finished table landed on "Where it is" while the
   rest stayed narrow. Adding a column here means re-balancing that table's row to 100 — which is
   the point, because that is the moment to decide what the new column is taking space from. */
   Two things set the floor and both are in every row, so they were measured rather than guessed:
   an order number is 182px and means nothing broken in half, and the "Exchange" chip is 69px and
   wrapped into "Exchan / ge" at 8%. Everything else was fitted around those two. Seven columns of
   14px side padding is 196px spent on gaps alone, which is most of a column, so a table this wide
   gives some of it back. */
.tbl.fixed th, .tbl.fixed td { padding-left:10px; padding-right:10px; }
.t-ready .c-which { width:10%; } .t-ready .c-who { width:13%; } .t-ready .c-order { width:19%; }
.t-ready .c-items { width:25%; } .t-ready .c-checked { width:15%; } .t-ready .c-next { width:18%; }
.t-moving .c-which { width:10%; } .t-moving .c-who { width:11%; } .t-moving .c-order { width:19%; }
.t-moving .c-items { width:20%; } .t-moving .c-where { width:14%; }
.t-moving .c-warehouse { width:12%; } .t-moving .c-next { width:14%; }
/* Delivered, waiting on the warehouse: the same seven columns as the travelling table with the
   carrier's delivery in place of "Where it is", which is the only thing anybody reads on this list. */
.t-landed .c-which { width:10%; } .t-landed .c-who { width:11%; } .t-landed .c-order { width:19%; }
.t-landed .c-items { width:18%; } .t-landed .c-landed { width:16%; }
.t-landed .c-warehouse { width:12%; } .t-landed .c-next { width:14%; }
.t-done .c-which { width:10%; } .t-done .c-who { width:18%; } .t-done .c-order { width:22%; } .t-done .c-where { width:50%; }
/* The creator tables. Same rule: each row of widths adds to 100. A tracking number is 20 digits
   and an order number is 16, so WHERE IT IS and ORDER are the two that were measured first. */
.t-find .c-when { width:10%; } .t-find .c-who { width:20%; } .t-find .c-which { width:10%; }
.t-find .c-items { width:20%; } .t-find .c-order { width:18%; } .t-find .c-where { width:22%; }
.t-sent .c-when { width:8%; } .t-sent .c-who { width:14%; } .t-sent .c-which { width:10%; }
.t-sent .c-items { width:16%; } .t-sent .c-order { width:15%; } .t-sent .c-addr { width:11%; }
.t-sent .c-where { width:17%; } .t-sent .c-act { width:9%; }
.t-filed .c-when { width:8%; } .t-filed .c-who { width:16%; } .t-filed .c-which { width:10%; }
.t-filed .c-items { width:18%; } .t-filed .c-order { width:15%; } .t-filed .c-where { width:16%; }
.t-filed .c-addr { width:8%; } .t-filed .c-act { width:9%; }
/* A button is nowrap everywhere else, which in a fixed column means it hangs out of its cell
   rather than fitting. In the one column that is nothing but a button, it may wrap. */
.c-act .mini { white-space:normal; }
/* The kind chip is one word and reads as nothing when broken ("Exchan / ge"). At 1000px with the
   sidebar the column is 81px, which left 61 for a 68px chip. Tighter padding on this one column
   and its chip makes the room; it still may wrap (chip-width.test.cjs forbids nowrap on a chip). */
.tbl.fixed td.c-which, .tbl.fixed th.c-which { padding-left:8px; padding-right:4px; }
.tbl.fixed .c-which .chip { padding-left:7px; padding-right:7px; }

/* ---------- Creators: the search the console's own cannot do (VA, 2026-09-25). One line, and the
   box takes the room, because what gets typed into it is a name or a 20-digit tracking number. */
.creator-find { display:flex; gap:8px; align-items:center; flex-wrap:wrap; margin:10px 0 4px; }
.creator-find input { flex:1 1 320px; min-width:0; font:inherit; font-size:14px; padding:8px 12px;
  border:1px solid var(--line); border-radius:999px; background:var(--card); }
.creator-find input:focus-visible { outline:none; box-shadow:var(--focus); border-color:var(--accent); }
/* The count keeps its own column so a seventy-character product title wraps UNDER ITSELF rather
   than under the number: "2x" at the head of a four-line paragraph is not something anyone finds,
   and a title that wraps back beneath it reads as a second item. */
.c-items .ln { display:grid; grid-template-columns:auto 1fr; gap:0 8px; margin-bottom:5px; }
.c-items .ln:last-child { margin-bottom:0; }
.qty { font-weight:700; font-variant-numeric:tabular-nums; }
.send-form select, .send-form input { font:inherit; font-size:12px; padding:3px; }
.send-form input[type=number] { width:96px; }
details.ack { display:inline-block; font-size:11px; margin-left:4px; } details.ack label { font-size:11px; }
pre.prev { white-space:pre-wrap; font-size:12px; background:var(--accent-soft); padding:8px; border-radius:var(--radius-sm); }
/* The one section that means somebody owes a customer something. It is the only thing on the page
   that is allowed to shout, so that it still works when the page is long. */
.card.needs-you { border-left:3px solid var(--ok); background:var(--ok-bg); }
.card.needs-you h2 { color:var(--ok); }
details.card > summary { cursor:pointer; font-size:15px; }
details.card[open] > summary { margin-bottom:10px; }

/* ---------- /ask, the owner's question page. Same story as the block above: these were a <style>
   element on the page and the browser dropped every one of them. */
.ask-form { display:flex; gap:8px; align-items:flex-start; margin:12px 0; }
.ask-form textarea { flex:1; font:inherit; padding:8px; }
pre.sql { background:var(--accent-soft); padding:10px; border-radius:var(--radius-sm); overflow-x:auto; font-size:12px; white-space:pre-wrap; }
.scroll-x { overflow-x:auto; }
.example { cursor:pointer; }
.linky { background:none; border:0; padding:0; text-align:left; cursor:pointer; text-decoration:underline; color:var(--accent); font:inherit; }


/* ---------- the queue type filter (owner, 2026-09-23: "the filter should be a button that
   dropdown all the different types. it should be before new ticket").
   Built like the date picker above: a <details> the browser opens by itself, so the filter keeps
   working when app.js is broken. Eighteen types as chips wrapped to two lines and pushed the
   queue down the page; as a menu they cost one button. */
details.type-pick { position:relative; }
details.type-pick > summary { list-style:none; cursor:pointer; }
details.type-pick > summary::-webkit-details-marker { display:none; }
summary.type-btn { display:inline-flex; align-items:center; gap:7px; padding:5px 12px; border:1px solid var(--line);
  border-radius:999px; background:var(--card); font-size:12.5px; font-weight:600; color:var(--fg); }
summary.type-btn:hover { background:var(--accent-soft); }
summary.type-btn .ico { color:var(--muted); }
summary.type-btn .caret { color:var(--muted); transition:transform .12s; }
details.type-pick[open] > summary.type-btn { border-color:var(--accent); box-shadow:var(--focus); }
details.type-pick[open] > summary.type-btn .caret { transform:rotate(180deg); }
/* A filter that is ON must be visible without reading the label, or a narrowed queue reads as a
   quiet one. Same treatment a chosen sort pill gets. */
details.type-pick[data-on] > summary.type-btn { background:var(--accent-soft); color:var(--accent); border-color:var(--accent); }
details.type-pick[data-on] > summary.type-btn .ico { color:var(--accent); }

/* right:0, not left:0 — the button sits at the right-hand end of the bar, so a left-anchored
   panel would hang off the page. Capped and scrollable: eighteen rows is taller than a laptop. */
.type-pop { position:absolute; top:calc(100% + 7px); right:0; z-index:6; width:max-content; min-width:250px;
  max-width:calc(100vw - 48px); padding:6px; background:var(--card); border:1px solid var(--line);
  border-radius:var(--radius); box-shadow:0 2px 6px rgba(16,16,20,.08), 0 14px 40px rgba(16,16,20,.14); }
.type-pop nav { display:flex; flex-direction:column; gap:1px; max-height:min(62vh, 460px); overflow:auto; }
.type-pop nav a { display:flex; align-items:center; justify-content:space-between; gap:18px; padding:6px 10px;
  border-radius:var(--radius-sm); font-size:13px; font-weight:600; color:var(--fg); text-decoration:none; }
.type-pop nav a:hover { background:var(--accent-soft); text-decoration:none; }
.type-pop nav a.on { background:var(--accent-soft); color:var(--accent); }
.type-pop nav a small { font-weight:600; font-size:12px; }
.type-pop > p { margin:7px 10px 3px; max-width:250px; line-height:1.45; }

/* ---------- correcting a return from the Exchanges page (owner, 2026-09-24: "there should be a
   way that you could edit the row on the console"). Narrower than the default modal because every
   field in it is short — a percent, a row number, a tracking number — and a 680px box makes five
   short boxes look like a form with something missing. Same shape as the address editor next to
   it, so the two do not read as different kinds of thing. */
.edit-return { width:min(520px, calc(100vw - 32px)); }
.edit-return .row.confirm { margin:16px -20px -18px; }
.edit-return .stack { margin-top:12px; }
.edit-return .stack label small { font-weight:500; line-height:1.45; }
/* The Save button sits at the end of the row, so the Cancel beside it must not read as the action. */
.edit-return .row.confirm { justify-content:flex-end; }

/* The queue pager. Only drawn past one page, so most queues never show it (2026-10-01). Centred
   on the page number rather than pushed to one side: with 300 rows above it, the thing you are
   looking for is which page you are on. */
.pager { display:flex; align-items:center; justify-content:center; gap:14px; margin:14px 0 2px; }

/* ---------------------------------------------------------------- Fixes (2026-10-01, rebuilt 10-02)

   🛑 ONE CARD, TWO COLUMNS, AND THE WINDOW HOLDS STILL (owner, 2026-10-02: "fix the UI of it and
   make it more consistent with everything else, like the buttons. Just make it look clean and more
   modern").

   What it looked like before, and why. Ship, Works, Undo and Send carried `primary` and `danger`
   WITHOUT `btn`, and those are variants, not controls: with nothing to vary they fell back to the
   browser's own chrome, so the four buttons on this page were the only unstyled ones in the
   console, and Send — the last child of a column flex — stretched into a bar the full width of the
   thread. The list, the thread and the composer all sat on the page's own white with no edges
   between them, so the left column read as one paragraph of run-on text (zeezy made the same point
   about the Home tiles the day before). And the thread kept a 60vh scroller inside a page that
   also scrolled, which is two scrollbars for one conversation.

   Now: both columns live in one bordered card, the list on a ground mixed the way the sidebar's is
   so it reads as a surface rather than a margin, and every control is the console's own .btn or
   .chip. Above 1100px the page itself stops scrolling and the thread is what moves, the same trick
   the case page uses (min-height:0 the whole way down the chain, or one long thread pushes the
   shell past the window and the page grows its scrollbar back). */
.fixes { display:grid; grid-template-columns:300px minmax(0, 1fr); min-height:66vh;
  background:var(--card); border:1px solid var(--line); border-radius:var(--radius); box-shadow:var(--shadow); overflow:hidden; }

/* The thread list. Its ground is the page pulled a few percent toward the ink — the same formula
   as the sidebar, so on every theme it is one shade off the card beside it and never a named colour. */
.fx-list { display:flex; flex-direction:column; min-width:0; padding:12px; gap:10px;
  background:color-mix(in srgb, var(--sheet, var(--bg)) 96%, var(--fg)); border-right:1px solid var(--line); }
.fx-rows { display:flex; flex-direction:column; gap:1px; overflow-y:auto; min-height:0; scrollbar-width:thin; }
a.fx-new { display:flex; align-items:center; justify-content:center; gap:6px; flex:0 0 auto; padding:8px 14px; font-size:13.5px; }
a.fx-new.on { background:var(--accent); color:var(--accent-ink); border-color:var(--accent); }
.fx-row { display:flex; flex-direction:column; gap:4px; padding:8px 10px; border-radius:var(--radius-sm); color:inherit; text-decoration:none; }
.fx-row:hover { background:color-mix(in srgb, var(--fg) 6%, transparent); text-decoration:none; }
.fx-row.on { background:color-mix(in srgb, var(--fg) 9%, transparent); }
.fx-title { font-size:13.5px; line-height:1.35; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.fx-row.on .fx-title { font-weight:600; }
/* The thread's number (#189) before its title, whole: the title is what gives way to it. */
.fx-top { display:flex; align-items:baseline; gap:6px; min-width:0; }
.fx-num { flex:0 0 auto; white-space:nowrap; }
.fx-meta { display:flex; align-items:center; gap:6px; min-width:0; }
.fx-meta small { white-space:nowrap; }
.fx-st { font-size:11px; padding:1px 8px; max-width:100%; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.fx-more { padding:8px 10px 2px; }
/* Everyone / Mine: two quiet tabs under New chat, the chosen one underlined, not a second button row. */
.fx-filter { display:flex; gap:14px; padding:0 4px; font-size:12.5px; flex:0 0 auto; }
.fx-who-pick { color:var(--muted); text-decoration:none; padding-bottom:2px; border-bottom:2px solid transparent; }
.fx-who-pick:hover { color:var(--fg); text-decoration:none; }
.fx-who-pick.on { color:var(--fg); border-bottom-color:var(--accent); font-weight:600; }
.fx-by { overflow:hidden; text-overflow:ellipsis; }
/* Clear finished (2026-10-26): at the right end of the Everyone / Mine row; a cleared thread, when she
   asks to see them, is quieter than the rest; on a finished thread's page, at the end of its heading. */
.fx-filter { align-items:center; flex-wrap:wrap; row-gap:6px; }
.fx-clear { margin-left:auto; }
.fx-clear .mini, .fx-clear-one .mini { padding:2px 9px; font-size:11.5px; }
.fx-row.cleared { opacity:.65; }
.fx-cleared-note { padding:8px 10px 2px; }
.fx-clear-one { margin-left:auto; }
/* A remove button on each finished row (2026-10-26: "an option per request"): top right of the row, on
   top of it, with the title kept clear of it. Always shown, since a phone has no hover. */
.fx-item { position:relative; }
.fx-item:has(.fx-row-x) .fx-row { padding-right:38px; }
.fx-row-x { position:absolute; top:6px; right:6px; margin:0; }
.fx-row-x .mini { padding:0 8px; font-size:14px; line-height:22px; color:var(--muted); }
.fx-row-x .mini:hover { color:var(--fg); }

/* Claude's proposals, between New chat and the threads: short cards, a ✓ and ✗ on each, never more than a third of the column. */
.fx-props { display:flex; flex-direction:column; gap:6px; flex:0 1 auto; min-height:0; padding:10px; border:1px solid var(--line); border-radius:var(--radius-sm); background:var(--card); }
.fx-props h3 { margin:0; font-size:12.5px; font-weight:600; color:var(--muted); text-transform:uppercase; letter-spacing:.04em; }
.fx-props h3 b { color:var(--fg); }
.fx-props-all { display:flex; align-items:baseline; gap:5px; color:inherit; text-decoration:none; }
.fx-props-see { margin-left:auto; font-size:12px; font-weight:500; text-transform:none; letter-spacing:0; color:var(--link); }
.fx-props-all:hover .fx-props-see { text-decoration:underline; }
/* Every proposal in one pop-up (owner, 2026-10-08: "a big popup ... drop-downs for each"): a row
   folded to its title and evidence, ✓ and ✗ outside the fold, the list scrolling inside the window. */
dialog.fx-props-pop { width:min(860px, calc(100vw - 32px)); }
.fx-pa-top { display:flex; align-items:center; gap:10px; padding:16px 20px 2px; }
.fx-pa-top h3 { margin:0; flex:1; font-size:18px; }
.fx-pa-top .fx-x { flex:0 0 auto; width:32px; height:32px; padding:0; border:1px solid var(--line); border-radius:50%; background:var(--card); color:var(--fg); font-size:20px; line-height:28px; cursor:pointer; }
.fx-pa-help { margin:0; padding:0 20px 12px; border-bottom:1px solid var(--line); }
.fx-pa-list { flex:1 1 auto; min-height:0; overflow-y:auto; padding:0 20px 8px; }
.fx-pa { padding:14px 0; border-top:1px solid var(--line); }
.fx-pa:first-child { border-top:0; }
.fx-pa-d > summary { cursor:pointer; line-height:1.4; }
.fx-pa-d > summary .chip { margin-left:6px; vertical-align:2px; }
.fx-pa-t { font-size:15.5px; font-weight:600; color:var(--fg); }
.fx-pa-ev { display:block; margin:4px 0 0 18px; font-size:13.5px; }
.fx-pa-body { padding:12px 0 0 18px; font-size:14px; }
.fx-pa-act { display:flex; gap:8px; margin:10px 0 0 18px; }
.fx-pa-act .mini { padding:4px 12px; }
.fx-pa-act .mini.ok { border-color:var(--ok); color:var(--ok); }
.fx-prop-list { display:flex; flex-direction:column; gap:8px; overflow-y:auto; max-height:34vh; scrollbar-width:thin; }
.fx-prop { display:flex; flex-direction:column; gap:3px; padding:8px 0 0; border-top:1px solid var(--line); }
.fx-prop:first-child { border-top:0; padding-top:0; }
.fx-prop.on .fx-prop-title { font-weight:600; }
.fx-prop-title { color:var(--fg); font-size:13.5px; line-height:1.35; text-decoration:none; }
.fx-prop-title:hover { text-decoration:underline; }
.fx-prop small { font-size:12px; line-height:1.4; }
.fx-prop-act { display:flex; gap:6px; margin-top:3px; }
.fx-prop-act .mini { padding:3px 10px; }
.fx-prop-act .mini.ok { border-color:var(--ok); color:var(--ok); }
/* One proposal opened, in the thread's place. */
.fx-prop-view { padding:18px 20px; overflow-y:auto; min-height:0; flex:1 1 auto; max-width:780px; font-size:14px; line-height:1.55; }
.fx-prop-view p { margin:0 0 12px; }
.fx-prop-form { display:flex; flex-direction:column; gap:8px; margin-top:18px; }
.fx-prop-form textarea { width:100%; font:inherit; font-size:14px; line-height:1.5; }
.fx-prop-btns { display:flex; flex-wrap:wrap; align-items:center; gap:10px; }
.fx-prop-done { padding:10px 12px; border-radius:var(--radius-sm); background:color-mix(in srgb, var(--fg) 6%, transparent); }
/* A proposal, in its pop-up or on its own page (2026-10-07: "more scannable ... instead of paragraphs"):
   four short lists under plain headings, the request folded until she wants to change it. */
.fx-pp-form { display:flex; flex-direction:column; min-height:0; }
.fx-pp { font-size:14.5px; line-height:1.5; }
.fx-pp-head { display:flex; align-items:flex-start; gap:10px; margin-bottom:6px; }
.fx-pp-head h3 { margin:0; flex:1; font-size:17px; line-height:1.3; }
.fx-pp-head .fx-x { position:static; flex:0 0 auto; }
.fx-pp-ev { margin:0 0 12px; color:var(--muted); }
.fx-pp section { margin:0 0 12px; }
.fx-pp h4 { margin:0 0 4px; font-size:12.5px; font-weight:600; color:var(--muted); text-transform:uppercase; letter-spacing:.04em; }
.fx-pp ul { margin:0; padding-left:20px; }
.fx-pp li { margin:2px 0; }
.fx-pp-safety { padding:8px 12px; border-radius:var(--radius-sm); background:color-mix(in srgb, var(--ok) 8%, transparent); }
.fx-pp-edit { margin-top:8px; }
.fx-pp-edit > summary { cursor:pointer; font-size:13px; color:var(--muted); width:max-content; }
.fx-pp-edit textarea { width:100%; margin-top:6px; font:inherit; font-size:14px; line-height:1.5; }
.fx-prop-view .row.confirm { display:flex; gap:10px; margin-top:12px; padding:12px 0 0; border-top:1px solid var(--line); align-items:center; }
.fx-prop-view .row.confirm .grow { flex:1; }
/* Claude asking to remember something (2026-10-07), under its message: Remember or No for a manager. */
.fx-mem { margin-top:8px; padding:8px 10px; border-radius:var(--radius-sm); background:var(--tint); font-size:13px; }
.fx-mem ul { margin:4px 0 0; padding-left:18px; }
.fx-mem-row { display:flex; flex-wrap:wrap; align-items:center; gap:6px 10px; margin:3px 0; }
.fx-mem-row > span { flex:1 1 220px; }
.fx-mem-act { display:inline-flex; gap:6px; }
.fx-mem-act .mini.ok { border-color:var(--ok); color:var(--ok); }
/* Claude's rulebook (2026-10-07): the text to read or change, and beside it what waits and every version. */
.fx-new-row { display:flex; gap:8px; align-items:center; }
.fx-new-row a.fx-new { flex:1 1 auto; }
.fx-rules { white-space:nowrap; }
.fx-rb { display:grid; grid-template-columns:minmax(0, 1fr) 300px; gap:16px; align-items:start; }
.fx-rb-main, .fx-rb-side { display:flex; flex-direction:column; gap:10px; min-width:0; }
.fx-rb-intro, .fx-rb-meta { margin:0; font-size:13px; }
.fx-rb-form { display:flex; flex-direction:column; gap:8px; }
.fx-rb-form textarea { width:100%; font:inherit; font-size:14px; line-height:1.55; }
.fx-rb-save { display:flex; gap:8px; }
.fx-rb-save input { flex:1; min-width:0; }
.fx-rb-text { margin:0; padding:12px 14px; white-space:pre-wrap; overflow-wrap:anywhere; font:inherit; font-size:14px; line-height:1.55;
  border:1px solid var(--line); border-radius:var(--radius-sm); background:var(--card); }
.fx-rb-box { padding:10px 12px; border:1px solid var(--line); border-radius:var(--radius-sm); background:var(--card); }
.fx-rb-box h3 { margin:0 0 6px; font-size:12.5px; font-weight:600; color:var(--muted); text-transform:uppercase; letter-spacing:.04em; }
.fx-rb-box h3 b { color:var(--fg); }
.fx-rb-wait, .fx-rb-hist { margin:0; padding-left:18px; font-size:13px; display:flex; flex-direction:column; gap:6px; }
.fx-rb-fixed > summary { cursor:pointer; }
.fx-rb-fixed > summary h3 { display:inline; margin:0; }
.fx-rb-fixed .fx-rb-text { margin-top:8px; font-size:13px; }
.fx-rb-theirs > summary { cursor:pointer; font-size:13px; color:var(--muted); margin-bottom:6px; }
@media (max-width: 860px) { .fx-rb { grid-template-columns:minmax(0, 1fr); } }
/* Exchange/Return filter: which kind, and which list. Same quiet underline as Everyone / Mine, wrapping on a phone. */
.x-filter { display:flex; flex-wrap:wrap; gap:8px 28px; margin:4px 0 16px; font-size:13px; }
.x-pick { display:flex; flex-wrap:wrap; gap:6px 16px; }
.x-pick a { color:var(--muted); text-decoration:none; padding-bottom:2px; border-bottom:2px solid transparent; white-space:nowrap; }
.x-pick a:hover { color:var(--fg); text-decoration:none; }
.x-pick a.on { color:var(--fg); border-bottom-color:var(--accent); font-weight:600; }
.x-pick a b { font-weight:600; color:var(--fg); }

/* The thread. A band at the top that holds still, the conversation, then the actions and the box. */
.fx-main { display:flex; flex-direction:column; min-width:0; min-height:0; }
.fx-head { display:flex; align-items:center; gap:10px; flex-wrap:wrap; flex:0 0 auto; padding:15px 20px; border-bottom:1px solid var(--line); }
.fx-head h2 { margin:0; font-size:17px; min-width:0; overflow-wrap:anywhere; }
#fix-log { display:flex; flex-direction:column; align-items:flex-start; gap:14px; padding:18px 20px; overflow-y:auto; min-height:0; flex:1 1 auto; scrollbar-width:thin; }
/* 🛑 A flex item shrinks by default, so given a fixed height to scroll inside, the column squeezed
   the messages instead of scrolling past them. Each message keeps its own height; the column moves. */
#fix-log > * { flex:0 0 auto; max-width:100%; }
/* A short thread sits on the composer rather than stranded at the top of a 600px white field. An
   auto margin and not justify-content:flex-end, which in a scroller puts the first message above
   the top of the box where it cannot be scrolled back to; with no free space an auto margin is 0. */
#fix-log > *:first-child { margin-top:auto; }

/* Her message and Claude's, told apart by side and by ground rather than by a stripe: the same
   shape the Ask panel uses, so the two conversations in this console read as one idea. */
.fx-msg { max-width:min(720px, 88%); padding:9px 13px; border-radius:var(--radius-sm); font-size:14px; }
.fx-msg.fx-claude { align-self:flex-start; background:var(--card); border:1px solid var(--line); border-top-left-radius:4px; }
.fx-msg.fx-user { align-self:flex-end; background:var(--accent-soft); border-top-right-radius:4px; }
.fx-who { display:flex; align-items:baseline; gap:6px; font-size:11.5px; color:var(--muted); }
.fx-who b { font-size:12px; font-weight:700; color:var(--fg); }
.fx-who:empty { display:none; }
.fx-text { margin-top:3px; white-space:normal; overflow-wrap:anywhere; }
.fx-text a { text-decoration:underline; text-underline-offset:2px; }
/* Claude's reasoning, folded under the short answer (2026-10-07). Quieter than the answer, so the answer leads. */
.fx-more { margin-top:8px; border-top:1px solid var(--line); padding-top:6px; }
.fx-more > summary { cursor:pointer; font-size:12.5px; color:var(--muted); width:max-content; }
.fx-more > summary:hover { color:var(--fg); }
.fx-more .fx-text { margin-top:6px; font-size:13px; }
/* What the console did: centred, quiet, one line. "Geng clicked Ship. · 6m ago". */
.fx-msg.fx-system { align-self:center; max-width:100%; display:flex; align-items:baseline; gap:6px; flex-wrap:wrap;
  background:none; border:0; padding:0; font-size:12.5px; color:var(--muted); }
.fx-msg.fx-system .fx-text { margin:0; }
.fx-msg.fx-system small::before { content:"· "; }
.fx-imgs { display:flex; flex-wrap:wrap; gap:6px; margin-top:7px; }
.fx-imgs img, #fix-thumbs img { max-height:160px; max-width:100%; border-radius:8px; border:1px solid var(--line); display:block; }
.fx-ctx { display:block; margin-top:5px; color:var(--muted); }
/* Nothing said yet: the empty thread explains the box under it, and leaves the moment one lands. */
.fx-empty { align-self:center; margin:auto 0; max-width:420px; text-align:center; color:var(--muted); }
.fx-empty b { display:block; color:var(--fg); font-size:15px; margin-bottom:4px; }
.fx-empty p { margin:0; font-size:13.5px; line-height:1.5; }
.fx-empty:not(:only-child) { display:none; }

.fx-foot { flex:0 0 auto; padding:0 20px 18px; }
#fix-actions { display:flex; gap:8px; align-items:center; flex-wrap:wrap; padding:14px 0 0; }
#fix-actions:empty { display:none; }
/* Claude at work, live (owner, 2026-10-22: "as close as possible to Claude in this chat"): one line
   above the message box, like that chat's status line, with every step folded under it. Wraps rather
   than sliding sideways on a phone; the step list scrolls inside itself. */
.fx-live { margin-top:12px; padding:8px 12px; background:var(--card); border:1px solid var(--line); border-radius:var(--radius-sm); font-size:13px; min-width:0; }
.fx-live[hidden] { display:none; }
.fx-live-head { display:flex; align-items:baseline; gap:6px; flex-wrap:wrap; min-width:0; }
.fx-live-now { flex:1 1 180px; min-width:0; color:var(--muted); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.fx-live-now::before { content:"· "; }
.fx-spin { flex:0 0 auto; align-self:center; width:8px; height:8px; border-radius:50%; background:var(--accent); animation:fx-pulse 1.2s ease-in-out infinite; }
@keyframes fx-pulse { 0%, 100% { opacity:.25; } 50% { opacity:1; } }
@media (prefers-reduced-motion: reduce) { .fx-spin { animation:none; } }
.fx-live-steps { margin-top:6px; }
.fx-live-steps > summary { cursor:pointer; font-size:12.5px; color:var(--muted); width:max-content; }
.fx-live-steps > summary:hover { color:var(--fg); }
.fx-live-steps ol { margin:6px 0 0; padding-left:22px; max-height:30vh; overflow:auto; color:var(--muted); overflow-wrap:anywhere; }
.fx-live-steps li.fx-live-text { color:var(--fg); font-style:italic; }
/* The composer is one box that takes the focus ring as a whole, so the textarea inside it drops its
   own border: two nested rounded rectangles around one field is what made this look bolted on. */
.fx-compose { margin-top:14px; padding:10px 12px; background:var(--card); border:1px solid var(--line);
  border-radius:var(--radius); transition:border-color .12s, box-shadow .12s; }
.fx-compose:focus-within { border-color:var(--accent); box-shadow:var(--focus); }
.fx-compose textarea { border:0; padding:0; background:none; resize:none; min-height:62px; max-height:40vh; }
.fx-compose textarea:focus { outline:none; border:0; box-shadow:none; }
.fx-send { display:flex; align-items:center; gap:12px; margin-top:8px; }
.fx-hint { flex:1; min-width:0; }
#fix-send { flex:0 0 auto; padding:8px 20px; }
#fix-thumbs { display:flex; gap:8px; flex-wrap:wrap; }
#fix-thumbs:not(:empty) { margin-bottom:10px; }
#fix-thumbs span { position:relative; }
#fix-thumbs img { max-height:76px; }
/* A document she attached (owner, 2026-10-03): a card with its name, in the thread a download link
   with what it is and its size. The same shape in both places, so the thing she queued is visibly
   the thing that arrived. */
.fx-file { display:inline-flex; flex-direction:column; gap:1px; max-width:260px; padding:7px 11px; border:1px solid var(--line);
  border-radius:8px; background:var(--card); font-size:13px; color:var(--fg); text-decoration:none; overflow-wrap:anywhere; }
a.fx-file:hover { border-color:var(--accent); text-decoration:none; }
.fx-file b { font-weight:600; }
.fx-file small { color:var(--muted); font-size:11.5px; }
#fix-thumbs .fx-file { padding-right:18px; }
/* The money confirmation (owner, 2026-10-03): the reviewer's account in the console's modal, then a
   tick, then Ship. Only the type inside is set here; the frame is dialog.modal's. */
dialog.fx-money h3 { margin:0 0 8px; }
dialog.fx-money h4 { margin:14px 0 4px; font-size:13px; }
dialog.fx-money ul { margin:4px 0; padding-left:18px; }
dialog.fx-money .row.confirm { gap:8px; }
.fx-money-ok { display:flex; gap:8px; align-items:center; font-weight:600; font-size:13px; }
/* A template list (2026-10-05): one line a template, opened to read it; the wording keeps its own
   line breaks and wraps, so a long reply never scrolls sideways. Wider than the money dialog: these
   are whole emails, read side by side with what they replace. */
dialog.fx-templates { width:min(860px, calc(100vw - 32px)); }
.fx-tpl { border-top:1px solid var(--line); padding:6px 0; }
.fx-tpl summary { cursor:pointer; font-size:13px; }
.fx-tpl-body { white-space:pre-wrap; overflow-wrap:anywhere; background:var(--tint); border:1px solid var(--line); border-radius:6px; padding:8px 10px; font-size:13px; }
/* The x on a pasted screenshot, top right (owner, 2026-10-01). */
#fix-thumbs .fx-x { position:absolute; top:-6px; right:-6px; width:22px; height:22px; padding:0; border-radius:50%; border:1px solid var(--line); background:var(--card); color:var(--fg); font-size:15px; line-height:20px; cursor:pointer; box-shadow:var(--shadow); }
#fix-thumbs .fx-x:hover { background:var(--stop-bg); color:var(--stop); }

/* The app shell. 1100px is where the 300px list, a thread wide enough to read and the collapsed
   sidebar still stand side by side; under it the grid is one column anyway and the page scrolls
   the ordinary way, which is the right answer on a phone. */
@media (min-width:1100px) {
  body.fixshell { height:100vh; overflow:hidden; display:flex; flex-direction:column; }
  body.fixshell > .shell { flex:1 1 auto; min-height:0; }
  body.fixshell .page { display:flex; flex-direction:column; min-height:0; }
  body.fixshell .page > .banner { flex:0 0 auto; }
  /* width:100%, because a flex item with margin:0 auto shrinks to its content. */
  body.fixshell .page > main { flex:1 1 auto; min-height:0; width:100%; display:flex; flex-direction:column; padding-top:20px; padding-bottom:20px; }
  body.fixshell main > .banner { flex:0 0 auto; margin-bottom:14px; }
  /* 🛑 THE ROW TOO, NOT ONLY THE CARD (owner, 2026-10-07: "why is the bottom like cut off?"). A
     grid's implicit row is `auto`, as tall as the taller column's content, so a thread list longer
     than the window grew the row past the card: the card was clipped at the bottom of the screen
     and the box and Send went with it (2,600px down in an 800px window). minmax(0, 1fr) makes the
     row the card's height, and min-height:0 lets the list's column shrink to it and scroll. */
  body.fixshell .fixes { flex:1 1 auto; min-height:0; grid-template-rows:minmax(0, 1fr); }
  body.fixshell .fx-list { min-height:0; }
  /* 🛑 THE ASK BUBBLE IS FIXED TO THE BOTTOM-RIGHT OF EVERY PAGE, and in the shell the composer is
     the thing underneath it: 56px of circle sat on top of Send. The action row keeps that corner
     empty instead, so the bubble lands beside the button rather than over it. Only in the shell —
     anywhere else the page scrolls and main's own bottom padding already clears it. */
  body.fixshell .fx-send { padding-right:64px; }
}
@media (max-width:860px) {
  .fixes { grid-template-columns:1fr; }
  .fx-list { border-right:0; border-bottom:1px solid var(--line); }
  .fx-rows { max-height:30vh; }
  .fx-msg { max-width:100%; }
}

/* The happiness headline on Metrics (owner, 2026-10-25): the one sentence the card leads with. */
p.lead { font-size:17px; margin:0 0 8px; overflow-wrap:anywhere; }
