/* Autoscribe components. Everything the shell renders at rest, in the order a page
   meets it: chrome, rail, controls, feedback, states, login. Nothing here hard-codes a
   colour; every value is a token from tokens.css. */

/* Reduced motion means none: every transition is zero, not "nearly zero" (R2f asserts
   `transition-duration: 0s` on a sample of elements), and every animation runs one
   frame and stops. Nothing in this app listens for `transitionend`, so no handler is
   left waiting for an event that will not arrive. */
@media (prefers-reduced-motion:reduce){*,*::before,*::after{
  animation-duration:.01ms!important;animation-iteration-count:1!important;
  transition-duration:0s!important;scroll-behavior:auto!important}}

/* The first thing in the tab order, seen only when it has the focus. It is a button
   rather than an `<a href="#main">`: this UI routes on the hash, so an anchor to an id
   would be read by router.js as an address and land on "Nothing here". It is here and
   not in base.css because it is rendered by app.js, which runs long after this sheet. */
.skip{position:fixed;top:8px;left:8px;z-index:60;transform:translateY(-200%);
  padding:8px 12px;border-radius:var(--r-2);border:1px solid var(--line-2);
  background:var(--surface);color:var(--ink);font:500 13px/18px var(--sans);cursor:pointer;
  box-shadow:var(--shadow)}
.skip:focus-visible{transform:none}
#main:focus{outline:none}

/* ---------------------------------------------------------------- icons + buttons */
.ic{width:15px;height:15px;stroke:currentColor;fill:none;stroke-width:1.6;
  stroke-linecap:round;stroke-linejoin:round;flex:none}
.ic.sm{width:13px;height:13px}
.btn{all:unset;box-sizing:border-box;cursor:pointer;display:inline-flex;align-items:center;
  justify-content:center;gap:var(--sp-2);height:32px;padding:0 var(--sp-3);
  border-radius:var(--r-3);background:var(--ink);color:var(--paper);
  font:500 13px/18px var(--sans);transition:transform var(--t) var(--ease),opacity var(--t) var(--ease)}
.btn:hover{opacity:.92}
.btn:active{transform:scale(.98)}
.btn[disabled]{opacity:.5;cursor:default;transform:none}
.btn.quiet{background:transparent;color:var(--ink-2);border:1px solid var(--line)}
.btn.quiet:hover{color:var(--ink);border-color:var(--line-2)}
.ib{all:unset;cursor:pointer;width:28px;height:28px;display:grid;place-items:center;
  border-radius:var(--r-2);color:var(--ink-2);
  transition:background var(--t) var(--ease),color var(--t) var(--ease)}
.ib:hover{background:var(--glass-2);color:var(--ink)}
.field{width:100%;height:36px;padding:0 var(--sp-3);border:1px solid var(--line-2);
  border-radius:var(--r-3);background:var(--surface);color:var(--ink);font:14px/20px var(--sans);
  transition:border-color var(--t) var(--ease)}
.field:hover{border-color:var(--ink-2)}
.field::placeholder{color:var(--ink-2)}
.field[aria-invalid="true"]{border-color:var(--rec)}
.fieldrow{display:grid;gap:5px}
.hint{font-size:12px;line-height:17px;color:var(--ink-2);font-weight:300}

/* ------------------------------------------------------------------------ appbar */
/* FIX (plan 028 finding A1): the bar padded a flat 16 px while every page below it
   pads `--gutter` (16/24/32), so its right cluster stopped short of the right edge of
   every heading and row under it -- 16 px at 1280, 4 px at 400. The right side *is*
   the gutter now, at every width, so the bar and the content share one right edge.
   The left stays --sp-4: that is where the wordmark lines up with the rail's links. */
.appbar{height:var(--appbar-h);display:grid;grid-template-columns:auto minmax(0,1fr) auto;
  align-items:center;gap:var(--sp-4);padding-inline:var(--sp-4) var(--gutter);
  border:0;border-bottom:1px solid var(--line);box-shadow:none;position:sticky;top:0;z-index:3}
.wordmark{display:flex;align-items:center;gap:9px;font:500 14px/20px var(--sans);
  letter-spacing:-.01em;text-decoration:none;white-space:nowrap}
.wordmark i{width:8px;height:8px;border-radius:50%;background:var(--accent);
  box-shadow:0 0 0 3px var(--accent-soft);flex:none}
.search{justify-self:center;width:min(560px,100%);height:32px;display:flex;align-items:center;
  gap:var(--sp-2);padding-inline:10px;border:1px solid var(--line);border-radius:var(--r-3);
  background:var(--glass-2);color:var(--ink-2);
  transition:border-color var(--t) var(--ease)}
.search:hover,.search:focus-within{border-color:var(--line-2)}
.search input{all:unset;flex:1;min-width:0;font:13px/18px var(--sans);color:var(--ink)}
.search input::placeholder{color:var(--ink-2)}
.appbar .right{display:flex;align-items:center;gap:var(--sp-3);font-size:13px;color:var(--ink-2)}
.acct{display:flex;align-items:center;gap:var(--sp-2);max-width:220px}
.acct b{font-weight:500;color:var(--ink);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
@media (max-width:700px){
  .appbar{gap:var(--sp-2);padding-inline:var(--sp-3) var(--gutter)}
  /* FIX (plan 028 finding A2): the Light/System/Dark control took 176 px of a 400 px
     bar, which left the search 124 px and cut its placeholder to "Search wor". The
     control is not lost -- Settings › Appearance carries the same one -- and the box
     it was squeezing is the only thing on this bar that has nowhere else to be. */
  .appbar .seg{display:none}
  /* Hidden, not removed: the wordmark is a link, and a link whose only child is a
     decorative dot has no accessible name at all (axe link-name, R2f). */
  .appbar .wordmark span{position:absolute;width:1px;height:1px;margin:-1px;padding:0;
    overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}
  /* the hint for a key there is no keyboard for, and a name with nowhere to sit */
  .search kbd{display:none}
  .acct b{display:none}
}

/* -------------------------------------------------------------------------- rail */
.rail{padding:var(--sp-3) 10px;display:flex;flex-direction:column;gap:1px;
  border:0;border-right:1px solid var(--line);box-shadow:none;min-width:0}
.rail a{display:flex;align-items:center;gap:10px;height:30px;padding-inline:10px;
  border-radius:var(--r-2);color:var(--ink-2);font-size:13px;text-decoration:none;
  cursor:pointer;white-space:nowrap;overflow:hidden;
  transition:background var(--t) var(--ease),color var(--t) var(--ease)}
.rail a span.t{overflow:hidden;text-overflow:ellipsis}
.rail a:hover{background:var(--glass-2);color:var(--ink)}
.rail a[aria-current="page"]{background:var(--surface);color:var(--ink);
  box-shadow:inset 0 1px 0 var(--hl),var(--shadow)}
.rail .n{margin-left:auto;font:12px/16px var(--mono);font-variant-numeric:tabular-nums;color:var(--ink-2)}
.rail .lbl{padding:var(--sp-3) 10px var(--sp-1)}
.rail .dot{width:7px;height:7px;border-radius:50%;background:var(--h,var(--ink-3));flex:none}
/* The padding and the `margin-top:auto` moved to `.rail-foot`, which is what is pushed
   to the bottom now (plan 029 item 2). */
.rail .tot{display:grid;gap:2px}
.rail .tot b{font:500 18px/22px var(--sans);letter-spacing:-.02em;font-variant-numeric:tabular-nums}
.rail .tot>span{font:11px/16px var(--mono);color:var(--ink-2)}

/* FIX (plan 029 items 1-3): the rail was a stretched item of the `.frame` grid, so the
   `margin-top:auto` under the total and the vitals pushed them to the bottom of the
   *page column*, not of the screen -- on Settings at 1280x860 the vitals sat at
   y=1272..1399 of a 1415 px page, out of sight until you scrolled to the very end. The
   machine's numbers are the one thing in this app you read without looking for it, so
   the rail is its own viewport-high box now: sticky under the app bar (itself sticky at
   top 0), exactly one screen tall, and scrolling inside itself when the links do not
   fit -- at 1280x420 they do not.

   `align-self:start` is not optional: a stretched grid item fills the row and has no
   room left in which to stick. `dvh` is declared after `vh` so a mobile browser's
   retracting toolbar cannot leave the foot under the chrome, with the plain `vh` as the
   fallback for engines that do not know it.

   The rail's own bottom padding goes to zero and moves onto `.rail-foot`: 12 px of
   padding under a bottom-sticky foot parks it that far off the floor at the end of the
   scroll, and the foot's bottom edge has to *be* the rail's bottom edge. */
@media (min-width:901px){
  .rail{position:sticky;top:var(--appbar-h);align-self:start;
    height:calc(100vh - var(--appbar-h));height:calc(100dvh - var(--appbar-h));
    padding-bottom:0;overflow-y:auto;overscroll-behavior:contain;scrollbar-width:none}
  .rail::-webkit-scrollbar{width:0;height:0}
  /* The box has a fixed height now, so its children are flex items with somewhere to
     shrink to: at 1280x420 the 30 px links quietly squashed to 15 and the rail reported
     no overflow at all. They keep their height and the rail scrolls instead -- the same
     reason the strip under 900 px pins its links with `flex:none`. */
  .rail>*{flex:none}
}
/* The foot: the part of the rail that may never scroll out of reach. The negative inline
   margin spans it across the rail's padding box -- with 20 px of padding putting its
   text back on the links' 20 px text edge -- so that when it does have to cover
   something it covers it from edge to edge.

   `.over` (toggled by rail.js when the rail overflows) carries both the opaque ground
   and the hairline, because both exist for the same one reason: links passing behind.
   A rail that fits needs neither, and a plinth of `--surface` under a rail with nothing
   scrolling behind it is a band of a second colour explaining nothing. */
.rail-foot{margin-top:auto;position:sticky;bottom:0;
  margin-inline:-10px;padding:var(--sp-3) 20px var(--sp-4);
  display:grid;gap:2px}
.rail-foot.over{background:var(--surface);border-top:1px solid var(--line)}

/* Under 900 px the rail is a strip of links above the content, not a column beside it.
   FIX (plan 028 finding A3): it used to *wrap*, and with tags in it that was two or
   three rows -- ~90 px of chrome under a 56 px bar, which put the page title at y≈185
   on a phone before a word of the page had been drawn. One row that scrolls sideways
   instead; components/rail.js brings the current link back into view on every route
   change, so the screen you are on is never the one off the right edge.

   The mask is the "there is more" cue, and it is two layers on purpose: the first
   fades the last 32 px of the strip, the second holds the bottom pixel row -- the
   hairline under the bar -- at full opacity the whole way across, because a rule that
   separates chrome from content may not fade out halfway. The trailing padding is that
   same 32 px, so the last link comes to rest just clear of the fade rather than half
   under it; rail.js leaves the same gap when it scrolls a link back into view. */
@media (max-width:900px){
  .rail{flex-direction:row;flex-wrap:nowrap;align-items:center;align-self:start;
    gap:var(--sp-2);border-right:0;border-bottom:1px solid var(--line);
    padding:var(--sp-2) 32px var(--sp-2) var(--gutter);
    overflow-x:auto;overscroll-behavior-x:contain;scrollbar-width:none;
    -webkit-mask-image:linear-gradient(90deg,#000 calc(100% - 32px),transparent),linear-gradient(#000,#000);
    mask-image:linear-gradient(90deg,#000 calc(100% - 32px),transparent),linear-gradient(#000,#000);
    -webkit-mask-size:100% calc(100% - 1px),100% 1px;
    mask-size:100% calc(100% - 1px),100% 1px;
    -webkit-mask-position:left top,left bottom;
    mask-position:left top,left bottom;
    -webkit-mask-repeat:no-repeat;mask-repeat:no-repeat}
  .rail::-webkit-scrollbar{width:0;height:0}
  /* FIX (plan 029 item 4): `.rail-foot` joins the list. Hiding only what is inside it
     would leave the wrapper itself in the row as an empty flex item with a gap on
     either side -- and, worse, a second scroller in a strip whose sideways
     `scrollLeft` (rail.js) has to stay the nav element's alone. */
  .rail .lbl,.rail .vitals,.rail .tot,.rail .rail-foot{display:none}
  /* `flex:none`: a link in a scroller must keep its own width rather than be squeezed
     to fit a width it is not trying to fit. */
  .rail a{height:28px;flex:none}
}

/* ------------------------------------------------------------------ rail vitals */
.vitals{display:grid;gap:5px;margin-top:10px;padding-top:10px;border-top:1px solid var(--line)}
.vl{display:grid;grid-template-columns:50px minmax(0,1fr) auto;gap:var(--sp-2);align-items:center;
  font:11px/15px var(--sans);color:var(--ink-2)}
.vl .mono{font-size:10px;text-align:right;color:var(--ink-2);white-space:nowrap}
.tk{display:block;height:3px;border-radius:2px;background:var(--line-2);overflow:hidden}
.tk b{display:block;height:100%;width:calc(var(--v,0)*100%);background:var(--h,var(--ink));
  opacity:.6;border-radius:2px;transition:width var(--t-2) var(--ease)}
.vsub{font:10px/14px var(--mono);color:var(--ink-2);margin-top:2px;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.vstate{display:flex;align-items:center;gap:6px;min-width:0}
.vstate i{width:6px;height:6px;border-radius:50%;background:var(--h,var(--ink-3));flex:none}
.vstate.live i{animation:breathe 2.4s var(--ease) infinite}
@keyframes breathe{0%,100%{opacity:1}50%{opacity:.35}}

/* ------------------------------------------------------- seg (segmented control) */
.seg{position:relative;display:inline-grid;grid-auto-flow:column;grid-auto-columns:1fr;
  padding:2px;border:1px solid var(--line);border-radius:var(--r-3);background:var(--glass-2);
  isolation:isolate}
.seg .thumb{position:absolute;top:2px;bottom:2px;left:2px;width:calc((100% - 4px)/var(--n));
  transform:translateX(calc(var(--i)*100%));border-radius:var(--r-2);background:var(--surface);
  box-shadow:0 1px 2px light-dark(rgba(20,21,23,.12),rgba(0,0,0,.4)),inset 0 1px 0 var(--hl);
  transition:transform var(--t-2) var(--ease);z-index:0}
.seg button{all:unset;cursor:pointer;position:relative;z-index:1;padding:4px 12px;text-align:center;
  font:500 12px/18px var(--sans);color:var(--ink-2);white-space:nowrap;
  transition:color var(--t) var(--ease)}
.seg button[aria-checked="true"]{color:var(--ink)}
.seg button:focus-visible{outline-offset:-2px}
.seg.sm button{padding:2px 9px;font-size:11px}

/* -------------------------------------------------------------------- chip + kbd */
.chip{display:inline-flex;align-items:center;gap:7px;height:26px;padding:0 9px;
  border:1px solid var(--line);border-radius:var(--r-2);background:var(--glass-2);
  font:500 12px/16px var(--sans);color:var(--ink-2);white-space:nowrap;text-decoration:none;
  transition:background var(--t) var(--ease),color var(--t) var(--ease),border-color var(--t) var(--ease)}
button.chip{cursor:pointer}
.chip:hover{border-color:var(--line-2);color:var(--ink)}
.chip .dot{width:7px;height:7px;border-radius:50%;background:var(--h,var(--ink-3));flex:none}
/* No opacity: 75 % of --ink-2 on glass is 3.6:1, and a count is text (R2f). The count
   reads as secondary from the monospace and the size instead. */
.chip .n{font:11px/16px var(--mono)}
.chip[aria-pressed="true"],.chip.on{background:var(--ink);color:var(--paper);border-color:var(--ink)}
/* A chip that carries a hue and is *on* wears the hue, with the ink that wins on it:
   --v{n}i for a derived voice colour, a computed black or white for a painted tag
   (components/ui.js sets both from colors.js). */
.chip.on.hued{background:var(--h);border-color:var(--h);color:var(--i)}
.chip.on.hued .dot{background:var(--i);opacity:.7}
.chip[disabled]{opacity:.5}
kbd{font:11px/16px var(--mono);color:var(--ink-2);background:var(--glass-2);
  border:1px solid var(--line-2);border-radius:var(--r-1);padding:0 5px;white-space:nowrap}

/* ------------------------------------------------------------------------ strip */
/* The diarisation signature: one cell per 64th of the recording, in voice colour.
   Decorative by itself, so it is aria-hidden and carries a text alternative. */
.stripwrap{width:100%;min-width:0}
.stripwrap .strip{height:100%}
.strip{display:flex;width:100%;height:100%;gap:1px;min-width:0;border-radius:2px}
.strip i{display:block;height:100%;flex:var(--f,1);background:var(--h,var(--ink-3));opacity:.8;
  border-radius:1px}
.strip i.gap{background:var(--line-2);opacity:.5}
.strip i:first-child{border-radius:99px 1px 1px 99px}
.strip i:last-child{border-radius:1px 99px 99px 1px}

/* -------------------------------------------------------------- skeleton + empty */
.skel{display:block;border-radius:var(--r-1);background:var(--line);
  width:var(--w,100%);height:var(--h,12px);animation:pulse 1.6s var(--ease) infinite}
@keyframes pulse{0%,100%{opacity:1}50%{opacity:.45}}
.empty{display:grid;gap:var(--sp-2);justify-items:start;align-content:start;
  padding:var(--sp-6) var(--sp-5);border-radius:var(--r-4);max-width:62ch}
.empty h2{font-size:17px;line-height:24px}
.empty p{color:var(--ink-2);font-weight:300;font-size:14px;line-height:21px}
.empty .lbl{color:var(--accent-t)}

/* ---------------------------------------------------------------- toast + dialog */
/* The container is always in the page (it is the live region), so it must not sit over
   what is under it while it is empty: the box ignores the pointer, the toasts in it
   do not. */
.toasts{position:fixed;left:50%;bottom:var(--sp-5);transform:translateX(-50%);z-index:20;
  display:grid;gap:var(--sp-2);justify-items:center;pointer-events:none;
  width:min(520px,calc(100vw - 2*var(--gutter)))}
.toast{display:flex;align-items:center;gap:var(--sp-3);padding:10px var(--sp-3);pointer-events:auto;
  border-radius:var(--r-4);font-size:13px;animation:rise var(--t-2) var(--ease) both;max-width:100%}
.toast .msg{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.toast.bad{color:var(--rec-t)}
@keyframes rise{from{transform:translateY(6px);opacity:0}to{transform:none;opacity:1}}
dialog{padding:0;border:0;background:transparent;color:var(--ink);max-width:min(560px,92vw);
  max-height:86vh;overflow:auto}
dialog::backdrop{background:light-dark(rgba(20,21,23,.28),rgba(0,0,0,.55))}
.sheet{padding:var(--sp-5);border-radius:var(--r-5);display:grid;gap:var(--sp-3)}
.sheet h2{font-size:17px;line-height:24px}
.sheet .foot{display:flex;justify-content:flex-end;gap:var(--sp-2);margin-top:var(--sp-2)}

/* ------------------------------------------------------------------- key overlay */
.keys{display:grid;gap:var(--sp-4)}
.keygrp{display:grid;gap:var(--sp-1)}
.keyrow{display:grid;grid-template-columns:96px minmax(0,1fr);gap:var(--sp-3);align-items:baseline;
  font-size:13px;color:var(--ink-2);padding:3px 0}
.keyrow .k{display:flex;gap:4px;justify-content:flex-end}

/* ----------------------------------------------------------------------- unlock */
/* The one screen in front of the app (plan 020 W1): the QR a phone scans, the token
   form, the name step and the "not encrypted yet" card, all in one frosted card. It
   replaced 019's `.login`; the class changed with the component, so a stale rule
   cannot style a screen that no longer exists. */
.unlock{min-height:100vh;display:grid;place-items:center;padding:var(--sp-5) var(--gutter)}
.unlock .card{width:min(420px,100%);padding:var(--sp-5);border-radius:var(--r-5);
  display:grid;gap:var(--sp-3)}
.unlock .wordmark{font-size:16px;margin-bottom:var(--sp-1)}
.unlock h1{font-size:20px;line-height:26px;letter-spacing:-.02em}
.unlock h2.or{font-size:14px;line-height:20px;font-weight:500;margin-top:var(--sp-2);
  padding-top:var(--sp-4);border-top:1px solid var(--line)}
.unlock p{font-size:13px;line-height:19px;color:var(--ink-2);font-weight:300}
.unlock .err{color:var(--rec-t);font-weight:400;display:flex;gap:7px;align-items:baseline}
.unlock .foot{display:flex;align-items:center;justify-content:space-between;gap:var(--sp-3)}
.unlock .foot.signedin{padding-top:var(--sp-3);border-top:1px solid var(--line)}
.unlock .step{font:11px/14px var(--mono);color:var(--ink-2);letter-spacing:.06em;text-transform:uppercase}
.unlock .tokenform{display:grid;gap:var(--sp-3)}
.unlock .note{font-size:12px;line-height:17px}

/* ---------------------------------------------------------------- the QR itself */
.unlock .pair{display:grid;gap:var(--sp-3);justify-items:center;text-align:center}
/* A QR is a machine target, not a surface: the modules segno draws are near-black and
   they have to sit on something near-white in *both* themes or a phone cannot read
   them. This is the one plate in the app that is white on purpose, and it is why the
   square has a light ground under a dark card. */
.unlock .qr{background:#fff;padding:12px;border-radius:var(--r-4);
  box-shadow:inset 0 0 0 1px rgba(0,0,0,.06)}
.unlock .qrimg{display:block;width:240px;height:240px;max-width:100%}
.unlock .qrimg.blank{width:240px;height:240px;background:
  repeating-linear-gradient(45deg,rgba(0,0,0,.05) 0 8px,transparent 8px 16px)}
.unlock .paircode{display:flex;align-items:center;gap:var(--sp-3)}
.unlock .paircode b{font:500 24px/28px var(--mono);letter-spacing:.14em;
  font-variant-numeric:tabular-nums}
/* The only amber on this screen, and the only thing on it that moves. */
.unlock .ring{width:24px;height:24px;display:block;flex:none}
.unlock .ring svg{width:100%;height:100%;transform:rotate(-90deg)}
.unlock .ring circle{fill:none;stroke-width:3}
.unlock .ring .track{stroke:var(--line-2)}
.unlock .ring .arc{stroke:var(--accent);stroke-linecap:round;
  stroke-dasharray:100.53;stroke-dashoffset:calc(100.53 * (1 - var(--p,0)));
  transition:stroke-dashoffset var(--t) linear}

/* "This archive has not been encrypted": no QR, no form, one command. */
.unlock .explain{display:grid;gap:var(--sp-3)}
.unlock .cmd{margin:0;padding:10px var(--sp-3);border-radius:var(--r-2);
  background:var(--glass-2);border:1px solid var(--line);font-size:12px;line-height:18px;
  overflow-x:auto}

@media (max-width:420px){
  .unlock .qrimg,.unlock .qrimg.blank{width:min(240px,62vw);height:min(240px,62vw)}
}

/* ------------------------------------------------- web sessions (Settings, W1) */
/* Lives here rather than beside the rest of Settings because a session row is chrome
   about the key, not a settings widget -- and because one class must live in one
   sheet (web/unit/css-collisions.test.js). */
.sesslist{display:grid;gap:2px}
.sessrow{display:flex;flex-wrap:wrap;align-items:center;gap:var(--sp-2) var(--sp-3);
  padding:6px 8px;margin-inline:-8px;border-radius:var(--r-2)}
.sessrow:hover{background:var(--glass-2)}
.sessrow .nm{flex:1 1 140px;min-width:0;font-size:13px;display:flex;align-items:center;
  gap:var(--sp-2);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.sessrow .n{font:11px/16px var(--mono);white-space:nowrap}
.sessrow .chip{margin-left:auto}
.sessrow.is-current{background:var(--glass-2)}

/* The idle chip in the appbar: it appears only in the last ten minutes of a session,
   and clicking it is a request, which is what pushes the deadline out again. */
.idle{font:11px/16px var(--mono);color:var(--accent-t);white-space:nowrap}
.idle .dot{background:var(--accent)}

/* ===================================================================== LIBRARY */
/* The toolbar, the ledger, the list, the selection and the bulk bar. Names follow
   docs/UI-DESIGN.md section 4 ("class names"): the mock's four collisions are
   .toolbar / .tk, .grouph / .gaugeh, .stdot / .status. */

/* ---------------------------------------------------------------- the toolbar */
.toolbar{display:grid;gap:var(--sp-2);margin-bottom:var(--sp-3)}
.chips{display:flex;align-items:center;gap:var(--sp-2);flex-wrap:wrap;min-width:0}
.chips .spacer,.main-head .spacer{flex:1}
.rangebar{display:flex;align-items:center;gap:var(--sp-2);flex-wrap:wrap}
.dates{display:flex;align-items:center;gap:6px;color:var(--ink-2)}
.dates .field{width:auto;height:26px;padding:0 6px;border-radius:var(--r-2);
  font:11px/16px var(--mono);color:var(--ink-2);background:var(--glass-2)}
/* The count ("7 recordings · 10 m") sits beside the title in `.main-head`, the way the
   search screen's hit count already did. FIX (plan 028 findings A4a/A5): it used to
   own a right-aligned row of its own between the toolbar and the list -- 24 px of
   nothing but one number, on every library, archive and trash screen. */
.count{font:12px/16px var(--mono);color:var(--ink-2)}

/* ----------------------------------------------------------------- the ledger */
/* One day per row, one block per recording, at its hour and as wide as it was long.
   `--daycol` is the date column, `--grid` the hour gridline spacing: both are derived
   (the gridlines from the axis span, in ledger.js) rather than fixed at twelve hours. */
.ledger{--daycol:88px;border-top:1px solid var(--line)}
.axis{display:grid;grid-template-columns:var(--daycol) minmax(0,1fr);height:22px;align-items:end}
.axis .ticks{position:relative;height:22px}
.tick{position:absolute;bottom:4px;transform:translateX(-50%);white-space:nowrap;
  font:11px/14px var(--mono);color:var(--ink-2)}
.tick.start{transform:none}
.tick.end{transform:translateX(-100%)}
.weekh{padding:var(--sp-4) 0 var(--sp-1);border-top:1px solid var(--line)}
.day{display:grid;grid-template-columns:var(--daycol) minmax(0,1fr);border-top:1px solid var(--line)}
.day .d{padding:12px 10px;display:flex;align-items:baseline;gap:6px;min-width:0}
.day .d b{font:500 18px/20px var(--sans);letter-spacing:-.02em;font-variant-numeric:tabular-nums}
.day .d span{font:12px/16px var(--sans);color:var(--ink-2);
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.day.today .d b{color:var(--accent-t)}
.day .lane{position:relative;min-height:66px;
  background-image:repeating-linear-gradient(90deg,var(--line) 0 1px,transparent 1px var(--grid,16.667%))}
/* A day with nothing on it is one line and no axis: there is nothing to place. */
.day.gap{grid-template-columns:minmax(0,1fr)}
.day.gap .d{padding:6px 10px}
.day.gap .d span{font-size:11px}
.day.gap .lane{display:none}

.blk{position:absolute;height:46px;border-radius:var(--r-3);display:grid;
  grid-template-rows:1fr 3px;overflow:hidden;cursor:pointer;
  transition:transform var(--t) var(--ease),box-shadow var(--t) var(--ease),
    border-color var(--t) var(--ease)}
.blk:hover{transform:translateY(-1px);box-shadow:inset 0 1px 0 var(--hl),var(--shadow-2)}
.blk .h{display:flex;align-items:center;gap:8px;padding:0 10px;min-width:0}
/* The title takes whatever the dot and the length leave, and takes it first: at the
   180 px minimum that is about 115 px, which is seventeen characters before the
   ellipsis. `flex:1 1 auto` rather than nothing, so a short title does not let the
   length drift into the middle of the block. */
.blk .h b{font:500 13px/16px var(--sans);flex:1 1 auto;min-width:0;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.blk .h .at,.blk .h .du{font:11px/16px var(--mono);color:var(--ink-2);flex:none}
.blk .h .du{margin-left:auto}
.blk .stripwrap{height:3px;margin-inline:10px;width:auto}
.blk .stdot{width:6px;height:6px;border-radius:50%;background:var(--accent);flex:none}
/* Still being worked on: one slow breath, no spinner (principle 6). */
.blk.live{border-color:color-mix(in srgb,var(--rec) 45%,transparent);
  animation:halo 2.4s var(--ease) infinite}
.blk.live .h .du{color:var(--rec-t)}
.blk.fail{border-style:dashed}
.blk.fail .stdot{background:var(--rec)}
.blk.fail .h .du{color:var(--rec-t)}
/* Selected is raised glass: two pixels up, the deeper shadow, and the blur that every
   other block does without. */
.blk.sel{transform:translateY(-2px);border-color:var(--line-2);z-index:1;
  box-shadow:inset 0 1px 0 var(--hl),var(--shadow-2)}
.blk.sel:hover{transform:translateY(-3px)}
.picker:focus-visible .blk.cur,.picker:focus-visible .row.cur{
  outline:2px solid var(--accent);outline-offset:-2px}

/* ------------------------------------------------------------------- the list */
.list{border-top:1px solid var(--line)}
.grouph{display:flex;align-items:baseline;gap:10px;padding:var(--sp-4) 0 var(--sp-2)}
.grouph b{font:500 14px/20px var(--sans);letter-spacing:-.01em}
/* Fixed columns, not `auto`: two rows whose speakers have different name lengths must
   still line their durations and clocks up, because that is what makes a list scannable. */
.row{display:grid;grid-template-columns:18px 92px minmax(0,1fr) 196px 24px 56px 64px 26px;
  gap:var(--sp-3);align-items:center;min-height:52px;padding-inline:6px;
  border-top:1px solid var(--line);border-radius:var(--r-3);cursor:pointer;
  transition:background var(--t) var(--ease)}
.row:hover{background:var(--glass-2)}
.row.sel{background:var(--accent-soft)}
.row .sig{min-width:0;display:flex;align-items:center;height:10px}
.row .t{min-width:0;display:grid;gap:0}
.row .t b{font:500 14px/19px var(--sans);display:flex;align-items:center;gap:var(--sp-1);min-width:0}
.row .t b .tx{min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.row .t>span{font:300 13px/17px var(--sans);color:var(--ink-2);
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.row .du,.row .at{font:12px/16px var(--mono);color:var(--ink-2);text-align:right;white-space:nowrap}
/* The speaker chips, inside their own 196 px column and never outside it.
   FIX: this was `justify-content:flex-end` on a *non-wrapping* flex line with
   `overflow:hidden`, so three chips that did not fit overflowed towards the inline
   *start* -- off the left edge of the cell, under the title -- and the clip ate the
   first chip's first letter ("Speaker 0 81%" read as "peaker 0 81%"). Wrapping is the
   fix: a line that runs out of room starts another one instead of sliding out of the
   box, and `align-content:center` keeps one or two lines vertically on the row's
   baseline. A single chip longer than the whole column is held by `max-width` and
   ellipsises its own name rather than the cell clipping it. */
.row .who{display:flex;flex-wrap:wrap;align-content:center;justify-content:flex-end;
  gap:4px;min-width:0;overflow:hidden}
.row .who .chip{height:22px;padding:0 7px;font-size:11px;max-width:100%;min-width:0}
.row .who .chip>span:not(.n){min-width:0;overflow:hidden;text-overflow:ellipsis}
.row .rename{height:28px;font-size:14px}
.tagdots{display:flex;gap:4px}
.tagdots i{width:7px;height:7px;border-radius:50%;background:var(--h,var(--ink-3));flex:none}
.status{font:500 11px/14px var(--sans);letter-spacing:.06em;text-transform:uppercase;
  color:var(--accent-t);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.status.bad{color:var(--rec-t)}
/* Mouse affordances. None of them is focusable at all: the row is a listbox option, and
   an option with focusable children is not an option any more (components/rows.js says
   why, and names the key each one duplicates). They show on hover and on the row the
   cursor is on, so a keyboard reader can see what the keys are about to do. */
.pick{all:unset;width:16px;height:16px;border:1px solid var(--line-2);border-radius:var(--r-1);
  display:grid;place-items:center;color:transparent;cursor:pointer;opacity:0;
  transition:opacity var(--t) var(--ease),background var(--t) var(--ease)}
.row:hover .pick,.row.cur .pick,.row.sel .pick{opacity:1}
.row.sel .pick{background:var(--ink);border-color:var(--ink);color:var(--paper)}
.ib.pen{width:22px;height:22px;opacity:0;flex:none}
.row:hover .ib.pen,.row.cur .ib.pen{opacity:1}
.ib.star{color:var(--ink-2)}
.ib.star.on{color:var(--accent)}
.ib.star.on svg{fill:currentColor}

/* -------------------------------------------------------- selection + actions */
/* The listbox holds the focus but never wears the ring itself: the ring belongs on
   the row or block under the cursor, which is the thing the keys are about to act on. */
.picker{min-width:0;outline:none}
.loadlist{display:grid;gap:var(--sp-2);padding-top:var(--sp-3)}
/* Above the toasts, because an undo is about to appear under it. */
.bulkbar{position:fixed;left:50%;bottom:calc(var(--sp-5) + 52px);transform:translateX(-50%);
  z-index:19;display:flex;align-items:center;gap:var(--sp-2);padding:8px 10px;
  border-radius:var(--r-4);width:max-content;max-width:calc(100vw - 2*var(--gutter));
  overflow-x:auto;animation:rise var(--t-2) var(--ease) both}
.bulkbar b{font:12px/16px var(--mono);padding-inline:var(--sp-1);white-space:nowrap}
.bulkbar .btn{height:28px}
.btn.danger{background:var(--rec);color:#fff}
.btn.danger.quiet{background:transparent;color:var(--rec-t);border-color:var(--line)}
.tagform{display:flex;gap:var(--sp-2);align-items:center}

@keyframes halo{
  0%,100%{box-shadow:inset 0 1px 0 var(--hl),var(--shadow),0 0 0 0 color-mix(in srgb,var(--rec) 0%,transparent)}
  50%{box-shadow:inset 0 1px 0 var(--hl),var(--shadow),0 0 0 4px color-mix(in srgb,var(--rec) 12%,transparent)}
}

/* ----------------------------------------------------------------- the search */
.row.hit{grid-template-columns:92px minmax(0,1fr) 24px 56px 64px;align-items:start;
  padding-block:var(--sp-2)}
.row.hit .sig{margin-top:4px}
.snip{font:300 13px/18px var(--sans);color:var(--ink-2);white-space:normal;
  display:-webkit-box;-webkit-line-clamp:2;line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
.snip mark{background:var(--accent-soft);color:inherit;border-radius:2px;padding:0 1px}
.tlink{color:var(--accent-t);text-decoration:none}
.tlink:hover{text-decoration:underline}
.chip.rm{cursor:pointer}
.chip.rm .x{font-size:14px;line-height:1;opacity:.7}
.cheat{display:grid;gap:6px;margin:0}
.cheat div{display:grid;grid-template-columns:170px minmax(0,1fr);gap:var(--sp-3);align-items:baseline}
.cheat dt,.cheat dd{margin:0}
.cheat dd{color:var(--ink-2);font-weight:300;font-size:13px}

/* -------------------------------------------------------------------- narrower */
@media (max-width:1100px){
  .row{grid-template-columns:18px 92px minmax(0,1fr) 24px 56px 64px 26px}
  .row .who{display:none}
}
@media (max-width:700px){
  .ledger{--daycol:56px}
  /* No room for "Tue" beside the date; the gap row's whole label stays. */
  .day:not(.gap) .d span{display:none}
  .row{grid-template-columns:18px 64px minmax(0,1fr) 52px 26px}
  .row .tagdots,.row .at{display:none}
  .row.hit{grid-template-columns:64px minmax(0,1fr) 60px}
  .row.hit .tagdots,.row.hit .at{display:none}
  .cheat div{grid-template-columns:minmax(0,1fr)}
  .bulkbar{bottom:calc(var(--sp-3) + 52px)}
  /* FIX (plan 028 finding A4b): the sort control had already been pushed onto a row of
     its own by the range bar, but the `.spacer` kept it pinned to the right end of it,
     so a left-aligned toolbar finished with one control floating alone on the far
     side. On a phone it takes the row it is on -- and the spacer goes, or it claims a
     wrapped line of its own between the range bar and the control. */
  .toolbar .seg{width:100%}
  .toolbar .spacer{display:none}
}

/* --------------------------------------------------------------- touch targets */
/* Below 700 px there is no hover and no pointer finer than a finger, so every control
   that can take the focus grows to at least 40 px tall (plan 019 R2f; WCAG 2.5.8 asks
   for 24, this design asks for 40). Two things are deliberately *not* here: affordances
   inside a library row, which are not focusable at all and whose 52 px row is the real
   target, and times and citations inside running text, which 2.5.8 exempts as inline.
   web/e2e/narrow.spec.js measures every control on every route and names the offenders. */
@media (max-width:699px){
  .skip{min-height:40px}
  .wordmark{min-height:40px}
  /* 44, not 40: the box has a border, and it is the *input* inside it that has to be
     40 px tall, not the frame around it. */
  .search{height:44px}
  .search input{align-self:stretch}
  .rail a{height:40px}
  button.chip,a.chip,summary.chip{min-height:40px;padding-inline:12px}
  .btn{min-height:40px}
  .ib{width:40px;height:40px}
  .field,select.field{height:40px}
  .dates .field{height:40px}
  /* FIX (plan 028 finding A4c): the 40 px target is the *button*, so the 4+4 (2+2 on
     `.sm`) padding it carries on a desktop was being stacked on top of it and a
     segmented control came out 54 px tall, not 46. Both selectors, because `.seg.sm
     button` is the more specific one and would otherwise keep its own padding. */
  .seg button,.seg.sm button{min-height:40px;display:grid;place-items:center;padding-block:0}
  /* The bulk bar's buttons are inside a fixed strip; it grows with them. */
  .bulkbar .btn{height:auto;min-height:40px}
  /* A search hit's time is a link at the end of a row, not a word in a sentence. */
  .row .tlink{min-height:40px;display:inline-flex;align-items:center;justify-content:flex-end}
}
