/* TMS - manga translation editor. Light theme "the light table, three pencils": the scan lies on lit glass,
   the rows are white script sheets, the chrome is the desk around them ruled in non-repro blue. Ink is the
   work and a commitment, blue pencil is structure and the machine's voice, red pencil a correction owed,
   orange "you are here" (selection only), amber "changed, not final". No external resources: the one
   webfont is bundled under fonts\ with its OFL.txt. */

@font-face{
  font-family:"Sour Gummy TMS"; src:url(fonts/SourGummy-SemiBold.ttf) format("truetype");
  font-weight:600; font-style:normal; font-display:swap;
}

/* the materials, lit: the topbar is the desk; each column head one tonal step lighter (--desk-2, no rule) over what
   it acts on; the page lies on the brightest surface, lit glass. Three blues, one territory each: page strokes
   (--box / --box-ink), Translated (--commit), the pencil (--blue); the Cast tab's figure outlines are a teal of their
   own outside the three (#0E97A3, 3.5:1 on white, the letter #0b7d88 on a paper chip). The one bold element is the
   balloon tag, which carries the verdict: its outline is the verdict (Translated dashed), its fill the selection */
:root{
  --paper:#FFFFFF;      /* work surfaces, and the pressed/.on chip */
  --desk:#E7ECF0;       /* the topbar and the panel's tab line */
  --desk-2:#EFF3F6;     /* the column heads (pane head, rows head), the save log's foot, the Rules tab's code */
  --desk-hover:#DCE3E8; /* hover on desk and desk-2 */
  --glass:radial-gradient(120% 90% at 50% 38%, #FBFDFE, #EDF3F7 55%, #DCE5EB);   /* #imgPane: the lit glass */
  --well:#D6DEE4;       /* tool trough, keycaps, divider track, the screens behind the cards (#boot, #start, login) */
  --barh:44px;          /* the topbar: dialogs and sheets hang 16 px under it */
  --box-ink:#1F6FD6;    /* the tag fill (white numeral 4.9:1), the login wordmark, the tab icon (a literal) */
  --rule:#B9D9E6;       /* non-repro blue hairlines: the bars' edges and the row bottoms */
  --wash:#E6F3F8;       /* hover on paper (the steppers' too), the glossary chip, the saving pill, the grip's hover */
  --frame:#C3D0D8;      /* field and joined-control borders */
  --field-edge:#7C8791; /* a field's boundary at 3:1 or more (3.7 on paper, 3.3 on desk-2): the Projects page and
                           Change password only; the editor's other fields keep --frame */
  --blue:#2C6E86;       /* blue-pencil text: counts, presence, the machine's voice */
  --halo:rgba(44,110,134,.25);   /* --blue at 25 %: the focus halo of a field */
  --ink:#000000;
  --ink2:#4F5964;       /* graphite: UI text on the desk, meta, hints, ghost buttons */
  --ink3:#58626C;       /* placeholders, folder ids and the Typeset tab's disabled fields; meta reads --ink2 */
  /* the greys' contrast (WCAG AA is 4.5:1) on paper / desk / desk-2 / the hover desk / the well / the wash and every
     tint: --ink2 7.1 / 5.9 / 6.3 / 5.4 / 5.2 / 5.9 at least; --ink3 6.2 / 5.2 / 5.5 / 4.7 / 4.5 / 5.2 at least.
     Disabled text (a button at .55, #8A949C captions) stays at 3:1 or more and reads as off on purpose */
  --red:#C4362B;        /* red pencil: flagged, errors, Delete */
  --red-bg:#FDECEA;     /* the flagged chip, the error notice */
  --red-hover:#A52C23;  /* hover on a red fill (Delete) */
  --amber:#8A6400;
  --amber-bg:#FFEFB8;
  /* the review statuses: ink (the stamp's fill, the badge text, the tag's ring, the row's left edge), tint (the
     status chip; never a row); Final's ring is a Final-lock conflict card's bar */
  --final:#55514B;      /* warm "newsprint" grey: approved, set copy, the lock; apart from the cool desk */
  --final-bg:#EFEBE3;
  --final-ring:#CCC6BA;
  --edited:#28704A;     /* the editor has worked on it: the mint of the old ok */
  --edited-bg:#E8F5E9;
  --commit:#2F5BB7;     /* handed to the editor: a true blue, apart from --box, --blue and --wash */
  --commit-bg:#E6EEFB;
  /* contrast (WCAG), final / edited / commit: --ink2 on the tint 6.0 / 6.3 / 6.1:1, the status ink on it
     6.6 / 5.3 / 5.5:1, white on the ink 7.9 / 6.0 / 6.4:1 (floor 4.5:1); the selected row's keycap of the
     filled stamp button is that white on that ink too; the row's edge is the ink on paper (a non-text mark,
     floor 3:1) */
  --box:#2f80ed;
  --sel:#e8590c;
  --sel-wash:#FFF6EF;
  --lift:0 1px 2px rgba(16,48,64,.22);       /* "picked up": the paper chip */
  --float:0 12px 32px rgba(31,44,56,.22);    /* floating: dialogs, .pop */
  /* a paper sheet laid on the desk at its left edge: #panel, and the Projects page's ledger and side sheet */
  --sheet:-1px 0 0 rgba(31,80,104,.10), -6px 0 14px -8px rgba(31,80,104,.30);
  /* a translucent token (--sel, --red, --box at an alpha) is written as the rgba literal of its value: rgb(from ...)
     and color-mix() compute to color(srgb ...), and typeset_browser_test reads a box's border as rgba(...) */
  --font:"Segoe UI Variable Text", "Segoe UI", system-ui, -apple-system, "Helvetica Neue", Arial, sans-serif;
  --font-ja:"Yu Gothic UI", "Yu Gothic", Meiryo, "Hiragino Sans", "Hiragino Kaku Gothic ProN", "Noto Sans CJK JP", sans-serif;
  --font-tag:"Sour Gummy TMS", "Segoe UI Variable Display", "Segoe UI", system-ui, sans-serif;
}

*{box-sizing:border-box}
/* several containers below set display:flex/grid, which would beat the `hidden`
   attribute - keep hidden authoritative: it is the client's only way to show and hide a control (single writers:
   01_core.js's header). CSS hides text only (a word a narrow head drops, .vh, :empty, the words an unselected row
   does not show), never a control, except #chars under the Typeset tool and the Projects page's empty-state drop
   well (projects.css; docs\design\chrome.md) */
[hidden]{display:none !important}
html,body{height:100%}
body{
  margin:0; background:var(--desk); color:var(--ink);
  font:13px/18px var(--font);
  overflow:hidden;
}
button,input,select,textarea{font:inherit; color:inherit}
/* the base button is a ghost: graphite text, no frame at rest; the chosen one (`on`, aria-pressed) is a
   paper chip lifted off the desk, the one commit button (.primary) is an ink fill. `on` changes colour,
   never weight */
button{
  background:transparent; border:1px solid transparent; border-radius:3px; color:var(--ink2);
  padding:2px 8px; cursor:pointer;
}
button:hover:not(:disabled){background:var(--wash); color:var(--ink)}
/* a disabled button is faded to .55 with the not-allowed cursor; one that is not a fill or a chosen part draws its
   text in ink whatever its colour (Paint's red, MT's blue mean nothing while it is off), which .55 brings to 4.4:1
   on the well and 4.7 on paper (graphite at .55 would be 2.2-2.5), still fainter than the enabled graphite's
   5.2-7.1. A fill keeps its white text: on the ink .55 leaves 4.7:1, and a red or a Final grey fill fades only
   to .7, which leaves white on it 3.2-3.8:1 */
button:disabled{opacity:.55; cursor:not-allowed}
button:disabled:not(.primary, .danger, .approve, .on, [aria-pressed=true]){color:var(--ink) !important}
button:disabled:is(.danger, .approve){opacity:.7}
button.on,button[aria-pressed=true]{background:var(--paper); color:var(--ink); box-shadow:var(--lift)}
button.on:hover:not(:disabled),button[aria-pressed=true]:hover:not(:disabled){background:var(--paper)}
button.primary{background:var(--ink); border-color:var(--ink); color:#fff}
button.primary:hover:not(:disabled){background:#2B3238; color:#fff}
/* an action reads as one at rest: a ghost text button on paper (the rows, the side panel's tab bodies, the dialogs,
   the Projects page's sheet, the conflict cards) and the pane head's Undo, Redo and Page actions
   carry a 1-px frame, so it is told apart from a label without hovering. At a bare button's specificity, so a
   joined control, a tab, a list line or a chip that draws its own border keeps it; a chip on its own fill (the
   approve, page and new-message chips, the crop's ×) takes none */
:where(#rows, #panel .ptab, #panel .pbody, #conflictBox, dialog, #side .sbody, #side .sfoot)
  button:where(:not([role=tab], [role^=menuitem], .approve, .cpage, #chatNew, .cx)),
button:where(#histGroup *, #btnPageActs){border-color:var(--frame)}
/* faded to .55, --frame would all but vanish (1.1:1 on the desk): a disabled one's frame is a darker grey that
   fades to 1.16-1.37:1 on paper, the desk, the well and every tint, under the enabled frame's 1.21-1.57 on each
   but the well, where the two are equal (1.16) */
:where(#rows, #panel .ptab, #panel .pbody, #conflictBox, dialog, #side .sbody, #side .sfoot)
  button:where(:not([role=tab], [role^=menuitem], .approve, .cpage, #chatNew, .cx):disabled),
button:where(#histGroup *, #btnPageActs):where(:disabled){border-color:#B8C2CA}
/* the target floor (WCAG 2.2 target size): every button, select, filter chip and joined part is 24 px at least
   each way, the top bar's icon buttons 28. A checkbox or radio line, a slider and a field keep their own height:
   at 24 px each the Export dialog scrolled in a 1600 x 900 window. A control drawn smaller grows its hit area, not
   its glyph: its ::before reaches past its box (below). The Wand's ▾ is 24 px high and 16 wide, all its full slot
   has room for, 4 px clear of the next target (the size rule's spacing exception) */
button,select,#filters .fchip,#imgTools .tbend .toggle{min-height:24px}
button{min-width:24px}
#topbar :is(#btnNotify,#btnKeys,#btnBug,#btnVoice,#presence){min-width:28px; min-height:28px}
#topbar a.proj{padding:3px 0}
/* a thread message's Edit and Delete lie over its 16-px head line while shown: drawn 18 px, so they cover no more
   of the text under it, they reach the floor with a 24-px hit area */
.tacts:not(.tq) button{height:18px; min-height:0; position:relative}
.tacts:not(.tq) button::before{content:""; position:absolute; inset:-4px 0}
/* the crop's × keeps its 16-px chip on the crop's corner and reaches the floor the same way */
.citem .cx{min-width:0; min-height:0}
.citem .cx::before{content:""; position:absolute; inset:-4px}
/* a page link inside a chat message's sentence or head is an inline target (the size rule's exception), so it keeps
   its line's height */
.plink,.chead .cpage{min-width:0; min-height:0}
input[type=search],input[type=text],input[type=number],input[type=password]{
  border:1px solid var(--frame); border-radius:3px; padding:3px 7px; background:var(--paper); color:var(--ink);
}
input[type=search],input[type=text],input[type=password]{width:100%}
select{border:1px solid var(--frame); border-radius:3px; background:var(--paper); color:var(--ink)}
input[type=checkbox],input[type=radio]{accent-color:var(--ink)}
::placeholder{color:var(--ink3); opacity:1}
/* focus: an ink ring on controls (never orange: orange means selected); a text field turns its frame blue */
:focus-visible{outline:2px solid var(--ink); outline-offset:1px}
/* a pill (the save pill, presence with its count, the account) takes the ring at its own radius, 2 px clear, so it
   hugs the chip */
:is(.presence:has(> #presenceCount:not([hidden])), .pill, #btnAccount):focus-visible{outline-offset:2px}
input[type=search]:focus-visible,input[type=text]:focus-visible,input[type=number]:focus-visible,
input[type=password]:focus-visible,select:focus-visible,textarea:focus-visible{outline:2px solid var(--halo); outline-offset:0; border-color:var(--blue)}
/* the Japanese is set larger than the English, in a CJK-first stack */
textarea.src[lang=ja],#gloJa,.gitem .ja{font-family:var(--font-ja)}
/* the chrome controls change colour in 120 ms, nothing else moves (the reduced-motion rule closes this file) */
#topbar button,#topbar select,#filters button,#filters label,.banner button,.cstrip button,.pop .prow,
#imgTools button,.ptabs button,.pbody button,.conflicts button{
  transition:color .12s, background-color .12s, border-color .12s;
}
/* visually hidden, still read: a text part, never a control (#staleNote, #panelTitle while its tab is shown, the
   inpaint tray's caption; Render's " changed" in a narrow pane head) */
.vh{position:absolute !important; width:1px; height:1px; margin:-1px; padding:0; overflow:hidden;
    clip:rect(0 0 0 0); clip-path:inset(50%); white-space:nowrap; border:0}

/* ---- screens ---- */
.screen{
  position:fixed; inset:0; display:flex; align-items:center; justify-content:center;
  background:var(--well); z-index:50; color:var(--ink2);
}
.startcard{
  background:var(--paper); border-radius:8px;
  padding:26px 30px 24px; max-width:560px; box-shadow:var(--float); color:var(--ink);
}
.startcard h1{margin:0 0 6px; font:600 30px/36px var(--font-tag); color:var(--ink)}
.startcard .msg{margin:0 0 12px; color:var(--red)}
.startcard .msg:empty{display:none}
.hint{color:var(--ink2); font-size:12.5px; margin:0}
/* the login page (login.html): the start card as one column - the name, the tab pair, the message, one form. The
   card hangs from a fixed top, the one a centred Register card, the tallest state, would have (306 px: half its
   height and the screen's 16-px padding), so switching tabs never moves it; its screen scrolls, since Register is
   taller than a landscape phone */
.loginscreen{overflow-y:auto; align-items:flex-start; padding:16px 0}
.logincard{
  width:min(360px,calc(100vw - 32px)); margin:max(0px, calc(50vh - 306px)) auto auto; display:flex;
  flex-direction:column; gap:16px;
}
.logincard h1,.logincard p,.logincard .msg{margin:0}
.logincard .loginhead{display:flex; flex-direction:column; gap:2px}
/* the wordmark: the name as the editor's blue balloon tag at 44 px, its tail on a square corner like the tags'
   (below); white on --box-ink is 4.9:1. It outranks .startcard h1, which #start keeps. One line at any width: the
   name is about 9.1 em wide, and below the card's full 360 px its room is 100vw - 119px (the card's margin and
   padding, the balloon's own padding), so the size follows the viewport there and is 30 px above */
.logincard .loginhead h1{
  align-self:flex-start; position:relative; height:44px; padding:0 14px 0 13px; white-space:nowrap;
  font:600 clamp(16px, calc((100vw - 119px) / 9.4), 30px)/44px var(--font-tag);
  border-radius:22px 22px 22px 0; margin-bottom:11px; background:var(--box-ink); color:#fff;
}
.logincard .loginhead h1::after{
  content:""; position:absolute; left:0; top:calc(100% - 1px);
  border-top:10px solid var(--box-ink); border-right:9px solid transparent;
}
/* the tab pair on a desk track: the chosen one is the topbar's paper chip, which on the bare white card would not
   stand out from the other; the other's hover is the bars' one, since the paper's wash hardly shows on the desk */
.logincard .seg{background:var(--desk); align-self:stretch; height:32px}
.logincard .seg > button{flex:1}
.logincard .seg > button:not(.on):hover:not(:disabled){background:var(--desk-hover)}
.logincard form{display:flex; flex-direction:column; gap:14px}
.logincard .field{display:flex; flex-direction:column; gap:4px; color:var(--ink2)}
/* the rules and the errors under the fields are read before a first login, a size up from the editor's hints; a
   phone wraps the name's rule: no word left alone on its last line */
.logincard .hint,.logincard .err{font-size:13px; line-height:18px}
.logincard .hint{text-wrap:pretty}
.logincard input{padding:5px 8px}
/* a refused login frames both fields red until the next key (login.js) */
.logincard input[aria-invalid=true]{border-color:var(--red)}
/* Show/Hide in the password field's right end, over its frame, so it is as tall as the field; Edge's own reveal eye
   would be a second Show. The same in Change password (#passDlg, the editor's and the Projects page's) */
.logincard .pw,#passDlg .pw{position:relative; display:flex}
.logincard .pw input,#passDlg .pw input{padding-right:56px}
.logincard .pw input::-ms-reveal,#passDlg .pw input::-ms-reveal{display:none}
.logincard .pwshow,#passDlg .pwshow{position:absolute; top:0; right:0; bottom:0; padding:0 10px; border-radius:0 3px 3px 0}
/* its hover wash stays inside its transparent border, so the field's frame shows through; set on the hover itself,
   since the base hover's `background` shorthand resets the clip */
.logincard .pwshow:hover:not(:disabled),#passDlg .pwshow:hover:not(:disabled){background-clip:padding-box}
.logincard .pwshow:focus-visible,#passDlg .pwshow:focus-visible{outline-offset:-3px}
#passDlg .gfield > label{color:var(--ink2); font-size:13px; line-height:18px}
#passDlg .pwcaps{color:var(--ink2); font-size:12px; line-height:16px}
.logincard a{color:var(--blue)}
.logincard .err:empty{display:none}
/* the messages start lowercase (the server's and the client's alike): shown with a capital, the text unchanged */
.logincard .err::first-letter{text-transform:uppercase}
/* the one commit spans the column, one column with one commitment: the exception to "primary at the right end of
   its line". While its request runs it keeps its ink - busy, not unavailable */
.logincard .srowbtns button.primary{flex:1; min-height:32px}
.logincard button.primary:disabled{opacity:1; cursor:progress}
/* the way to the guest sandbox under the form: "New here?" between two hairlines, then a ghost of the card's ink
   button, which fills with ink under the pointer */
.logincard .tryway{display:flex; flex-direction:column; gap:12px}
.logincard .tryrule{display:flex; align-items:center; gap:10px; color:var(--ink2); font-size:12.5px}
.logincard .tryrule::before,.logincard .tryrule::after{content:""; flex:1; height:1px; background:var(--rule)}
.logincard a.trybtn{
  display:flex; align-items:center; justify-content:center; min-height:32px; border:1px solid var(--ink);
  border-radius:3px; background:var(--paper); color:var(--ink); font-weight:600; text-decoration:none;
  transition:background-color .12s, color .12s;
}
.logincard a.trybtn:hover{background:var(--ink); color:#fff}
/* touch: 16-px fields (iOS zooms into a smaller one) and 44-px targets */
@media (pointer:coarse){
  .logincard input{font-size:16px; min-height:44px}
  .logincard .seg{height:auto}
  .logincard .seg > button{min-height:44px}
  .logincard .srowbtns button.primary,.logincard a.trybtn{min-height:44px}
  .logincard .pwshow,#passDlg .pwshow{min-width:44px}
}

/* ---- app shell ---- */
/* topbar, demo strip, banner, conflict strip, dropped-note strip, orphan strip, main: one auto track per bar (the
   rows head is #rowsWrap's first child, inside #main) */
#app{display:grid; grid-template-rows:auto auto auto auto auto auto 1fr; height:100vh}
/* a demo server's --banner, on the editor under the topbar and in the login card, and in the editor a guest's deletion
   time and the sample page's credit: an amber band that no state of the real editor uses (white on --amber is 5.4:1),
   which wraps where the window is too narrow for one line */
.demostrip{
  padding:2px 12px; background:var(--amber); color:#fff; font-weight:600; line-height:18px; text-align:center;
  overflow-wrap:anywhere;
}
/* a guest's credit of the sample page after the notice, in regular weight; it breaks between its lines alone */
.demostrip .credit{font-weight:400}
.demostrip .credline{white-space:nowrap}

/* ---- the topbar: 44 px, one line at every width the headed suites check (in a narrow left zone PhotoCraft's word
   gives way) - the document and the session in three zones. Only the name shrinks (the grid's first track is
   minmax(0,1fr)); the centre stays centred between two equal 1fr sides ---- */
#topbar{
  display:grid; grid-template-columns:minmax(0,1fr) auto minmax(max-content,1fr); column-gap:16px; align-items:center;
  height:44px; padding:0 12px; background:var(--desk); border-bottom:1px solid var(--rule); color:var(--ink2);
  overflow-x:clip; position:relative; z-index:30;
}
#topbar .zl,#topbar .zc,#topbar .zr{display:flex; align-items:center; flex-wrap:nowrap; min-width:0}
#topbar .zl{gap:16px}
#topbar .zc{gap:8px}
#topbar .zr{gap:8px; justify-content:flex-end}
/* the name: 13/18 600 ink, the ellipsis; the title holds the whole name */
#topbar .proj{
  flex:0 1 auto; min-width:48px; font-weight:600; color:var(--ink); overflow:hidden; text-overflow:ellipsis;
  white-space:nowrap;
}
/* the name is the way back to the Projects page (a link, never a menu): the name's ink at rest, underlined under the
   pointer; the focus ring is the controls' ink ring */
#topbar a.proj{text-decoration:none}
#topbar a.proj::before{content:"‹ " / ""; color:var(--ink2)}     /* "back", out of the name (its aria-label says it) */
#topbar a.proj:hover,#topbar a.proj:focus-visible{text-decoration:underline; text-decoration-color:var(--ink3); text-underline-offset:3px}
/* the caller's level below edit ("Can review"), a quiet tag held 8 px from the name: it never shrinks, the name does */
#accessNote{
  flex:0 0 auto; margin-left:-8px; padding:0 6px; border-radius:9px; background:var(--wash); color:var(--ink2);
  font-size:12px; line-height:18px; white-space:nowrap;
}
#topbar button:not(.on,[aria-pressed=true]):hover:not(:disabled),#filters button:hover:not(:disabled){background:var(--desk-hover); color:var(--ink)}
#topbar button{white-space:nowrap}
/* keycaps (the page stepper, the Review jump, the selected row's stamp, the first memory match's Replace English,
   which Alt+T reaches as its card's Enter): the shortcut on screen while Alt alone is
   held (html.alt-held, 05_wiring.js wireAltKeys), generated from data-kbd (aria-keyshortcuts carries it for the
   reader; the empty alternative after the slash keeps the cap out of the control's name). A cap floats on its
   control's foot, centred, and takes no room, so holding Alt moves nothing; low enough to leave the label's
   descenders clear, so a top bar cap hangs a few px over the bar's edge (the bar clips only across); 12/16 bold
   white on an ink chip in a thin white ring, so it reads at a glance over any control. Its content is set at rest
   too (display:none), so a suite reads the generated text without Alt */
#topbar [data-kbd],.rbtns button[data-kbd],.memacts button[data-kbd]{position:relative}
#topbar [data-kbd]::after,.row.sel .rbtns button[data-kbd]::after,.cacts .rbtns button[data-kbd]::after,
.memacts button[data-kbd]::after{
  content:attr(data-kbd) / ""; display:none; position:absolute; left:50%; top:calc(100% - 4px);
  transform:translateX(-50%); margin:0; padding:0 5px; border-radius:3px;
  font:700 12px/16px var(--font); font-variant-numeric:tabular-nums; white-space:nowrap;
  background:var(--ink); color:#fff; box-shadow:0 0 0 1px rgba(255,255,255,.8); pointer-events:none;
}
:root.alt-held #topbar [data-kbd]::after,:root.alt-held .row.sel .rbtns button[data-kbd]::after,
:root.alt-held .cacts .rbtns button[data-kbd]::after,:root.alt-held .memacts button[data-kbd]::after{display:block}
/* ? (Shortcuts): a ghost button on the desk, 28 px high like the bell (transparent at rest, the hover desk under the
   pointer), 28 px square, not a keycap (the drawn keys are data-kbd's alone). Export… and PhotoCraft ↗ are the left
   zone's actions: paper in a frame like the steppers and the mode switch beside them, so no action there reads as
   bare text; the wash under the pointer. The trailing ↗ is only for a control that opens a new tab */
#btnExport,#btnPhotoCraft{border-color:var(--frame); background:var(--paper); color:var(--ink)}
#topbar #btnExport:hover:not(:disabled),#topbar #btnPhotoCraft:hover:not(:disabled){background:var(--wash)}
#btnExport{height:28px; padding:0 10px}
/* PhotoCraft ↗ beside it, the same ghost button: the arrow says "opens another tab". The left zone is a size
   container (its width is the grid's 1fr share, never its content's), so the word goes by the room the zone has, the
   page list's width included: under 432 px (the word needs 407 px of content with the name at its 48-px floor, 427
   with the fixture's whole name; a zone of 359 at 1280, 439 at 1440, measured) the button is a picture glyph and the
   arrow, never the arrow alone, 8 px from Export…; aria-label and title keep the name */
#app > #topbar .zl{container:tbleft / inline-size}     /* the editor's bar only: the Projects page's is a flex row */
#btnPhotoCraft{height:28px; padding:0 10px}
#btnPhotoCraft .pcglyph{display:none}
@container tbleft (width < 432px){
  #btnPhotoCraft{min-width:28px; padding:0 8px; margin-left:-8px; border-color:var(--frame)}
  #btnPhotoCraft .pcglyph{display:inline-block; margin-right:4px; vertical-align:-2px}
  #btnPhotoCraft .pcw{display:none}
  #btnPhotoCraft .pcarrow{font-size:16px; line-height:1; color:var(--ink)}     /* the steppers' 17-px chevrons' size */
}
#btnKeys{height:28px; min-width:28px; padding:0 8px}
#btnKeys .kq{font-size:16px; font-weight:600; line-height:1}     /* drawn at the bell's size, not the UI text's */
#btnBug{height:28px; min-width:28px; padding:0 6px; color:var(--ink2)}
/* the bell, ?, the bug and presence keep their 28-px (presence 26) layout and take a 32-px hit area from a
   transparent ::before 2 px past each edge (3 px above and below presence): the zone's 8-px gap keeps neighbours
   from overlapping */
#btnNotify,#btnKeys,#btnBug,#presence{position:relative}
#btnNotify::before,#btnKeys::before,#btnBug::before,#presence::before{content:""; position:absolute; inset:-2px}
#presence::before{inset:-3px -2px}
/* the bell (24_notify.js): the bug button's ghost square; its unread count is the Team tab's blue badge, over the
   bell's top-right corner inside the button, so it adds no width */
#btnNotify{position:relative; height:28px; min-width:28px; padding:0 6px; color:var(--ink2)}
#btnNotify .ubadge{
  position:absolute; top:-3px; right:-4px; min-width:16px; height:16px; padding:0 4px; border-radius:8px;
  background:var(--blue); color:#fff; font-size:12px; line-height:16px; text-align:center;
  font-variant-numeric:tabular-nums; pointer-events:none;
}
/* in a wide bar (a 1600-px window: 1570 px of viewport) the bell and ? say their names beside their glyphs; a
   narrower one keeps the glyphs and the titles */
#topbar .tbw{display:none}
@media (width >= 1560px){
  #topbar .tbw{display:inline; margin-left:6px}
  #btnNotify,#btnKeys{padding:0 10px}
}
.counter{
  color:var(--ink2); min-width:64px; text-align:center; font-size:14px; line-height:20px;
  font-variant-numeric:tabular-nums;
}
.toggle{display:flex; align-items:center; gap:4px; color:var(--ink2)}
.toggle.off{opacity:.55; color:var(--ink)}   /* a disabled checkbox line, as a disabled button */
/* [Raw | Inpainted | Result], line 1 of the pane head: a .scopeseg whose shown view is the paper chip */
#viewSelWrap > label{padding:0 8px; font-size:12px}
#viewSelWrap > label:not(:has(input:checked)):hover{background:var(--desk-hover); color:var(--ink)}
/* G3: the Result view of a page with no render shows the raw page: the chip reads amber (the group's title says why) */
#viewSelWrap.off > label:has(input:checked){background:var(--amber-bg); color:var(--amber)}

/* ---- joined controls (.seg: the page stepper, the mode switch, the Review jump; the zoom stepper; the Typeset
   tab's alignment and its Apply to, .scopeseg)
   one --frame outline, radius 6 on the outer corners, 1-px frame lines between the parts; none of these
   groups ever hides one of its own children, so first/last-child radii hold */
.seg{display:inline-flex; align-items:stretch; flex:0 0 auto; border:1px solid var(--frame); border-radius:6px}
.seg > *{border:0; border-radius:0; margin:0}
.seg > * + *{border-left:1px solid var(--frame)}
.seg > :first-child{border-radius:5px 0 0 5px}
.seg > :last-child{border-radius:0 5px 5px 0}
/* the chosen part is outlined inside as well as filled, so it never rests on colour alone: a paper chip takes a
   graphite line, the stamp's filled verdict a white one inside its ink. The focus ring replaces it while shown */
.seg > :is(button.on,button[aria-pressed=true]):not(:focus-visible),
.seg > label:has(> input:checked):not(:has(> input:focus-visible)),.rbtns button.on:not(:focus-visible){
  outline:1px solid var(--ink2); outline-offset:-3px;
}
.rbtns button.on:not(:focus-visible){outline-color:#fff; outline-offset:-2px}
#topbar .seg{height:28px}
/* the page stepper's 8 px and the zone's gap keep 16 px between it and the Review jump, so they never read as one bar */
#topbar .pagenav{margin-right:8px}
/* the Review jump is 170 px at least (to "To review [999]"), and while Typeset hides it the centre zone keeps its room
   as an empty 170-px ::after (the zone's 8-px gap before it stands for the jump's), so the zone is as wide in both
   modes: the page stepper, the mode switch and Export… stay put when the mode switches */
#commitGroup{min-width:170px}
#topbar .zc:has(> #commitGroup[hidden])::after{content:""; flex:0 0 170px}
@media (width < 1280px){
  #topbar .zl{gap:12px}     /* the name keeps 8 px more of its room */
}
/* the page stepper is paper: ‹ | 025.jpg 3 / 4 | › - "where am I", at the same place at every width;
   the Review jump beside it is paper too: ‹ Review 2 › - "where next" */
.pagenav,#commitGroup{background:var(--paper)}
.pagenav > button,#commitGroup > button{width:28px; padding:0; color:var(--ink); font-size:17px; line-height:1}
#topbar .pagenav > button:hover:not(:disabled),#topbar #commitGroup > button:hover:not(:disabled){background:var(--wash)}
/* the page select closed: #pageShort (the file name, then the position in graphite, then the caret) is the
   wrapper's in-flow content and so its width; the select lies over it, the same box, at opacity 0 (the styled-select
   pattern: no text of its own on screen, and no contrast check reads it), so a click, the keyboard, the native list
   and the accessible name and value are the select's. The label takes no pointer. The wrapper draws what the select
   cannot: the separator (the joined control's) and the focus ring. A forced-colours theme shows the select itself,
   with its full text, and no label */
.pagepick{position:relative; display:flex; align-items:stretch; min-width:0}
.pagepick:has(> #pageSel:focus-visible){outline:2px solid var(--ink); outline-offset:1px}
#pageSel{
  position:absolute; inset:0; width:100%; height:100%; margin:0; padding:0; border:0; opacity:0; cursor:pointer;
  font-size:15px; line-height:22px;
}
#pageShort{
  display:flex; align-items:center; gap:10px; max-width:300px; padding:0 8px 0 10px; color:var(--ink);
  font-size:15px; line-height:22px; white-space:nowrap; font-variant-numeric:tabular-nums; pointer-events:none;
}
/* the name and its hidden sizer share one grid cell, so the cell is the longest name's width */
#pageShort .psname{display:grid; font-weight:600; min-width:0}
#pageShort .psn,#pageShort .psw{grid-area:1 / 1; overflow:hidden; text-overflow:ellipsis; min-width:0}
#pageShort .psw{visibility:hidden}
#pageShort .pspos{flex:0 0 auto; color:var(--ink2); font-size:13px; text-align:right}
#pageShort::after{content:"▾" / ""; flex:0 0 auto; margin-left:-4px; color:var(--ink2); font-size:12px}
#pageSel option,#pageSel optgroup{font-weight:400; font-size:13px}
@media (forced-colors:active){
  #pageShort{display:none}
  .pagepick:has(> #pageSel:focus-visible){outline:0}     /* the select's own ring shows */
  #pageSel{position:static; opacity:1; padding:0 4px 0 10px; font-weight:600}
}
/* "To review" and the count as a badge in the Translated ink, so the jump never reads as a second page stepper (the
   words are generated, #commitCount's text stays the count updateCommitUI writes) */
#commitGroup .cgmid{
  display:flex; flex:1 1 auto; align-items:center; justify-content:center; gap:6px; padding:0 8px; color:var(--ink);
  white-space:nowrap;
}
#commitGroup .cgmid::before{content:"To review"}
#commitCount{
  min-width:20px; padding:0 6px; border-radius:999px; background:var(--commit-bg); color:var(--commit);
  font-size:12px; line-height:18px; text-align:center; font-weight:600; font-variant-numeric:tabular-nums;
}

/* ---- mode switch (translate | typeset): hides chrome, Typeset picks its tool; the chosen mode is a paper chip ---- */
#modeSel button{padding:0 12px}
#modeSel button:not(.on){color:var(--ink2)}

/* ---- the session: presence, the save slot ---- */
/* presence (who has the editor open): a ghost chip with a drawn glyph, a paper one while its list is open; the
   count shows only while someone else is in (renderPresence), and alone the glyph is a square chip */
.presence{display:inline-flex; align-items:center; gap:6px; height:26px; border-radius:999px; padding:0 10px 0 8px; white-space:nowrap}
.presence:has(> #presenceCount[hidden]){padding:0 6px; border-radius:3px}
.pglyph{width:16px; height:14px; fill:none; stroke:var(--ink2); stroke-width:1.3}
#presenceCount{color:var(--blue); font-weight:600; font-variant-numeric:tabular-nums}
/* blue on the hover desk is 4.4:1, so the count takes ink while the chip is hovered */
#presence:not(.on):hover #presenceCount{color:var(--ink)}
/* the pops hang 4 px under their opener (anchorPop writes top and left); the rule's top/right are a fallback */
.pop{
  position:fixed; top:48px; right:12px; z-index:40; min-width:190px; max-width:400px;
  background:var(--paper); border-radius:8px; padding:4px; color:var(--ink);
  box-shadow:var(--float);
}
.pop .prow{
  display:flex; gap:18px; justify-content:space-between; width:100%; padding:5px 8px; border-radius:3px;
  color:var(--ink); text-align:left;
}
.pop .prow .ppage{color:var(--ink2); font-variant-numeric:tabular-nums}
.pop .prow.none{color:var(--ink2)}
.pop .prow.go{cursor:pointer}
#topbar .pop .prow.go:hover,#topbar .pop :is(.plogout, .ppass, .ptut):hover:not(:disabled),
#topbar #pillLog .cp:hover:not(:disabled){background:var(--wash)}
/* the account's items (renderAccount, --login only) */
.pop :is(.plogout, .ppass, .ptut){display:block; width:100%; text-align:left; padding:5px 8px; border-radius:3px}
/* one head for the bell's, the people's and the account's pops (popHead): the title, its count, the actions at the
   right */
.pop .nphead{display:flex; align-items:center; gap:4px; padding:4px 4px 6px 8px}
.pop .nptitle{margin-right:auto; font-weight:600; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap}
.pop .npcount{color:var(--blue); font-variant-numeric:tabular-nums}
.pop .nphead button{height:24px; padding:0 8px; font-size:12px}
/* the account (--login), in the editor's bar and the Projects page's: the name, cut to its slot (the whole name is in
   the title and the aria-label), and a caret */
#btnAccount{display:inline-flex; align-items:center; gap:4px; height:28px; max-width:140px; padding:0 8px;
  border-radius:3px; color:var(--ink); font-weight:600}
#btnAccount .acname{min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap}
#btnAccount .accaret{flex:0 0 auto; color:var(--ink2); font-size:12px; font-weight:400}
#topbar #btnAccount:hover,#btnAccount.on{background:var(--desk-hover)}
/* the save log: time, message, Copy; its foot on desk-2 */
#pillLog{width:380px}
#pillLog .lg{display:grid; grid-template-columns:auto minmax(0,1fr) auto; align-items:baseline; gap:10px; padding:6px 8px; border-radius:3px}
#pillLog .lg.none{display:block; color:var(--ink2)}
#pillLog .tm{font-size:12px; line-height:16px; color:var(--ink2); font-variant-numeric:tabular-nums}
#pillLog .tx{line-height:18px; overflow-wrap:anywhere}
#pillLog .lg.err .tx{color:var(--red)}
/* the head while saving fails (pillLogHead): the countdown and Save now, then the last save and what is unsent */
#pillLog .lghead{display:flex; flex-direction:column; gap:2px; margin:0 0 4px; padding:4px 8px 6px;
  border-bottom:1px solid var(--rule); font-size:12px; line-height:16px; color:var(--ink2)}
#pillLog .lghead .lgline{display:flex; align-items:center; gap:6px; min-height:24px}
#pillLog .lgnext{color:var(--ink); font-weight:600; font-variant-numeric:tabular-nums}
#pillLog .lghead button{height:24px; padding:0 10px; font-size:13px; color:var(--ink)}
#pillLog .lgstate{font-variant-numeric:tabular-nums}
#pillLog .cp{height:24px; padding:0 6px; font-size:12px}
#pillLog .lgfoot{
  display:flex; align-items:center; justify-content:space-between; gap:8px; margin:4px -4px -4px; padding:6px 8px 6px 12px;
  background:var(--desk-2); border-radius:0 0 8px 8px; color:var(--ink2); font-size:12px;
}
#pillLog .lgfoot button{height:26px; padding:0 10px; font-size:13px; color:var(--ink)}
/* the notifications' list (24_notify.js): the head, then one full-width button per notification: who and the
   message on the main line, where (volume · page · block), when and the voice on a quieter one; an unread row has a
   blue dot and its text in ink, a read one is quiet. The list scrolls inside the pop; Load older is its foot */
#notifyPop{width:380px}
#notifyList{max-height:min(60vh,480px); overflow-y:auto}
#notifyPop .nrow{
  display:flex; flex-direction:column; gap:2px; width:100%; padding:6px 8px 6px 20px; position:relative;
  border-radius:3px; text-align:left; color:var(--ink2);
}
#notifyPop .nrow.unread::before{
  content:""; position:absolute; left:7px; top:12px; width:7px; height:7px; border-radius:999px; background:var(--blue);
}
#topbar #notifyPop .nrow:hover{background:var(--wash)}
#notifyPop .nmain{font-size:13px; line-height:18px; overflow-wrap:anywhere;
  display:-webkit-box; -webkit-box-orient:vertical; -webkit-line-clamp:3; line-clamp:3; overflow:hidden}
#notifyPop .nwho{font-weight:600; margin-right:6px}
#notifyPop .nrow.unread .nmain{color:var(--ink)}
#notifyPop .nhead{display:flex; align-items:baseline; gap:6px; min-width:0; font-size:12px; line-height:16px}
#notifyPop .nwhen{flex:0 0 auto; font-variant-numeric:tabular-nums}
#notifyPop .nhead .tvoice{margin-left:auto}
#notifyPop .nwhere{min-width:0; font-variant-numeric:tabular-nums; overflow:hidden; text-overflow:ellipsis;
  white-space:nowrap}
#notifyPop .nnone{padding:6px 8px; color:var(--ink2)}
#notifyMore{display:block; width:100%; margin-top:2px; padding:5px 8px; text-align:left}

/* ---- the save pill: a fixed 140-px slot (the whole text stays in the node and its title, the save log keeps the
   last five), a dot in the state colour; saved is quiet, an error is the loudest thing on screen ---- */
.pill{
  display:block; flex:0 0 140px; width:140px; height:24px; border-radius:999px; padding:0 11px 0 9px;
  font-size:12px; line-height:24px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; cursor:pointer;
}
.pill::before{
  content:""; display:inline-block; width:7px; height:7px; border-radius:50%; background:currentColor;
  margin-right:6px; vertical-align:1px;
}
/* saved is the desk itself: a dot and the word, no slab (a white 140-px slot at rest read as an empty field) */
.pill.saved{background:transparent; color:var(--ink2)}
.pill.saved:hover{background:var(--desk-hover); color:var(--ink)}
.pill.unsaved{background:var(--amber-bg); color:var(--amber)}
.pill.saving{background:var(--wash); color:var(--blue)}
.pill.error,.pill.reload{background:var(--red); color:#fff}
/* a refused save whose edits are kept (showBanner's warning): amber, nothing is lost */
.pill.stale{background:var(--amber-bg); color:var(--amber)}
/* every failure leads with a "!" in place of the dot, so it is never told by colour alone */
.pill.error::before,.pill.reload::before,.pill.stale::before{
  content:"!" / ""; width:auto; height:auto; border-radius:0; background:none; margin-right:5px; vertical-align:0;
  font-weight:700;
}
.pill[aria-expanded=true]{box-shadow:var(--lift)}

/* ---- the notice line: one anatomy for the banner (reload, a login that ended), the conflict strip and the
   dropped-note strip - a 3-px edge and the state's wash, ink text that wraps, the action at the right end
   (the dropped-note strip's #lostMsg can grow to 30vh) ---- */
.banner,.cstrip{
  display:flex; align-items:center; gap:12px; min-height:31px; padding:5px 12px 5px 15px;
  line-height:18px; color:var(--ink);
}
.banner{background:var(--red-bg); box-shadow:inset 3px 0 0 var(--red)}
.cstrip{background:var(--amber-bg); box-shadow:inset 3px 0 0 var(--amber)}
.cstrip.lost{background:var(--red-bg); box-shadow:inset 3px 0 0 var(--red); align-items:flex-start}
/* a disk change that keeps the unsaved edits is a warning: amber, its "!" on amber (white on --amber is 5.4:1) */
.banner.warn{background:var(--amber-bg); box-shadow:inset 3px 0 0 var(--amber)}
.banner.warn::before{background:var(--amber)}
.banner.warn button:hover:not(:disabled){background:rgba(138,100,0,.14)}
#bannerMsg,#cstripMsg,#lostMsg{flex:1 1 auto; min-width:0}
/* the banner leads with its severity glyph, and its action is a framed button right after the sentence */
.banner::before{content:"!" / ""; flex:0 0 auto; width:18px; height:18px; border-radius:999px; background:var(--red);
  color:#fff; font-weight:700; line-height:18px; text-align:center}
#bannerMsg{flex:0 1 auto}
.banner button{border:1px solid var(--frame); background:var(--paper)}
.banner button,.cstrip button{height:24px; padding:0 10px; color:var(--ink); font-weight:600; flex:0 0 auto}
.cstrip button:hover:not(:disabled){background:rgba(138,100,0,.14); color:var(--ink)}
.banner button:hover:not(:disabled),.cstrip.lost button:hover:not(:disabled){
  background:rgba(196,54,43,.12); color:var(--ink);
}
/* capped: many or long quotes scroll inside the strip instead of squeezing #main's 1fr track; the 3-px top pad,
   (24 - 18) / 2, centres one line on the 24-px buttons while a wrapped or scrolled quote still starts at the top */
#lostMsg{user-select:text; white-space:pre-wrap; max-height:30vh; overflow:auto; padding-top:3px}
/* the orphan strip (renderOrphans): the conflict strip's amber, its list capped like #lostMsg */
#orphanStrip{align-items:flex-start}
#orphanMsg{flex:1 1 auto; min-width:0; padding-top:3px}
#orphanList{margin:4px 0 0; padding-left:18px; max-height:30vh; overflow:auto; user-select:text}
#orphanList li{white-space:pre-wrap}

/* #main is pinned to the 1fr track: the bars above it are auto-placed into the first four and a hidden one
   takes no track, so without the pin #main landed in an auto row sized by the page image and left an empty
   band at the bottom whenever the image was shorter than the window. A new bar adds an auto track to #app
   and moves this pin, or with every bar shown the last one drops below #main */
#main{display:flex; min-height:0; overflow:hidden; grid-row:7}
#cols{flex:1 1 auto; min-width:0; display:flex; overflow:hidden}

/* ---- dividers: the image/rows one is a well track; the two column ones show their blue hairline only under the
   pointer or a drag (the row heads span the columns, so a line at rest would cross them). The widths and the
   hairline's left:2px are geometry (04_panel.js). A touch drags them, never pans (touch-action, like the grip), and
   each is a separator the keyboard moves (keyDivider) ---- */
.divider{flex:0 0 6px; background:var(--well); cursor:col-resize; position:relative; touch-action:none}
.divider:hover,.divider.drag{background:#C3CDD5}
.divider.vdiv{position:absolute; top:30px; bottom:0; width:6px; z-index:3; background:transparent}
.divider.vdiv::after{content:""; position:absolute; left:2px; top:0; bottom:0; width:1px; background:transparent}
.divider.vdiv:hover::after,.divider.vdiv.drag::after{background:#7FB3C9; width:2px}
/* keyboard focus: one 2-px line, no ring (a ring would box the whole column edge): the page divider draws it in ink
   in its well; a column divider's hairline, which crosses the row heads, is the hover one in the darker --blue */
.divider:not(.vdiv):focus-visible{outline:0; background:linear-gradient(var(--ink),var(--ink)) 2px 0 / 2px 100% no-repeat, var(--well)}
.divider.vdiv:focus-visible{outline:0}
.divider.vdiv:focus-visible::after{background:var(--blue); width:2px}
body.dragging{cursor:col-resize; user-select:none}

/* ---- left column: the pane head over the page on its glass ---- */
#left{display:flex; flex-direction:column; min-width:180px; background:var(--paper)}
/* ---- the pane head (#imgTools): 61 px always, both modes - two 30-px lines of 24-px controls that never wrap,
   plus a 1-px pad; a tonal step, no rule. Line 1 the page: zoom, history, View, then [Render | ↻ n | Auto] at its
   right end. Line 2 the mode's hand: the Page actions menu button (Translate), or the tool trough (Typeset). A narrow head drops words, not controls, down to 419 px (the @container rules below) ---- */
#imgTools{
  flex:0 0 61px; height:61px; padding:0 8px 1px; background:var(--desk-2); color:var(--ink2);
  container-type:inline-size; overflow:clip;
}
#imgTools .tbrow{display:flex; flex-wrap:nowrap; align-items:center; gap:8px; height:30px; white-space:nowrap; min-width:0}
#imgTools button{height:24px; padding:0 8px; min-width:24px; white-space:nowrap}
#imgTools button:not(.on,[aria-pressed=true]):hover:not(:disabled){background:var(--desk-hover); color:var(--ink)}
#imgTools .toggle{white-space:nowrap; height:24px; padding:0 4px}
/* its checkbox's focus ring hugs the box, so it clears the label across the 4-px gap */
#imgTools .toggle input:focus-visible{outline-offset:0}
/* groups of separate actions (#histGroup): ghost buttons 2 px apart, never joined; a group whose buttons the mode
   hides all takes no room */
#imgTools .tools{display:flex; align-items:center; gap:2px}
#imgTools .tools:not(:has(button:not([hidden]))){display:none}
/* the zoom stepper keeps one frame, no gap; the fit button's text is the zoom label, "59% fit" (layoutImage) */
#imgTools .seg{height:26px; gap:0}
#imgTools .seg > button{height:auto}
#imgTools .zoom{background:var(--paper)}
#imgTools .zoom > button{padding:0 7px; color:var(--ink)}
#btnZoomFit{font-variant-numeric:tabular-nums}
/* "Undo text edit #3 (alice)": the label (.hl) shows only where the head has room for it; the title keeps it. From a
   600-px head, where it shows, the history group gives way before the render group does: it shrinks (min-width 0)
   and each button cuts its label with the ellipsis down to 60 px, "Undo…" (the verb 32, the ellipsis 10, its
   padding 16 and its borders 2), which fits a 600-px head with the 660 rule's narrower words */
#histGroup button{max-width:230px; overflow:hidden; text-overflow:ellipsis}
#histGroup .hl{display:none}
@container (min-width:600px){
  #histGroup .hl{display:inline}
  #histGroup{min-width:0}
  #histGroup button{min-width:60px}
}
/* [Render | ↻ n | Auto]: one joined control at line 1's right end; Render turns amber, "Render changed", while the
   page is stale (updateStaleNote writes the class and the word); ↻ is Refresh all stale, its count blue pencil at 600
   (updateStaleCount hides it at 0: nothing waits) */
#imgTools .tbend{
  margin-left:auto; display:flex; align-items:stretch; height:26px; flex:0 0 auto;
  border:1px solid var(--frame); border-radius:6px; background:var(--paper);
}
#imgTools .tbend > #renderGroup{display:flex; gap:0}
#imgTools .tbend #btnRenderPage{height:24px; border-radius:5px 0 0 5px; padding:0 10px; color:var(--ink)}
#btnRenderPage .rcap{font-weight:400}
#imgTools .tbend #btnRenderPage.stale{background:var(--amber-bg); color:var(--amber); font-weight:600}
#imgTools .tbend #btnRenderPage.stale:hover:not(:disabled){background:#FBE3A0; color:#7A5800}
#imgTools .tbend #btnRenderStale{
  height:24px; border-radius:0; border-left:1px solid var(--frame); padding:0 7px; color:var(--ink); font-size:15px;
}
.scount{margin-left:4px; font-size:12px; font-weight:600; color:var(--blue); font-variant-numeric:tabular-nums}
button:disabled .scount{color:inherit}
#imgTools .tbend #optAutoRenderWrap{
  height:24px; gap:6px; padding:0 9px 0 8px; border-left:1px solid var(--frame); border-radius:0 5px 5px 0; cursor:pointer;
}
#optAutoRender,#optAutoOcr{margin:0}
/* Page actions (Translate's line 2): a ghost button whose caret says it opens a menu; a paper chip (button.on)
   while the menu is open */
#imgTools #btnPageActs{display:inline-flex; align-items:center; gap:6px; padding:0 8px 0 10px}
#btnPageActs .pacaret{font-size:12px; color:var(--ink2)}
/* the tool trough (Typeset): one well in the order the typesetter works - Text box, Select, the inpaint tray, then
   the options slot, always reserved (so choosing a tool moves nothing). The active tool is a paper chip lifted out
   of it (button.on) */
#imgTools .toolsets{display:flex; align-items:center; gap:0; height:28px; padding:2px; border-radius:6px; background:var(--well)}
#imgTools .toolsets > * + *{margin-left:6px}
#imgTools .toolsets .tools{gap:0}
#imgTools .toolset.inpaint{display:flex; align-items:center; height:24px; border-radius:4px; background:rgba(255,255,255,.38)}
#toolGroup button{padding:0 8px; color:var(--ink2)}
/* the chosen tool: the paper chip with a 2-px graphite outline inside it (never an ink fill: chrome.md, "Tried and
   rejected"), so it reads at a glance among four paper-coloured neighbours */
#toolGroup button.on{color:var(--ink); box-shadow:var(--lift), inset 0 0 0 2px var(--ink2)}
/* a tool's key, as a cap while Alt alone is held (the keycaps' rule above), on the button's top edge, not its foot:
   #imgTools clips at line 2's bottom */
#toolGroup button[data-kbd]{position:relative}
#toolGroup button[data-kbd]::after{
  content:attr(data-kbd) / ""; display:none; position:absolute; left:50%; bottom:calc(100% - 6px);
  transform:translateX(-50%); padding:0 4px; border-radius:3px; z-index:1;
  font:600 12px/16px var(--font); white-space:nowrap;
  background:var(--well); color:var(--ink2); box-shadow:0 0 0 1px var(--frame); pointer-events:none;
}
:root.alt-held #toolGroup button[data-kbd]::after{display:block}
#toolGroup button:not(.on):hover:not(:disabled){background:rgba(255,255,255,.55)}
#imgTools .topts{
  display:flex; align-items:center; width:110px; height:24px; flex:0 0 110px; padding:0 6px;
  border-radius:4px; background:rgba(255,255,255,.28);
}
/* the reserved slot while no tool has options: no well, so its line (or nothing) reads as text, not a control */
#imgTools .topts:not(:has(#brushWWrap:not([hidden]), #wandWrap:not([hidden]))){background:transparent}
#imgTools .topts .thint{
  min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; font-size:12px; line-height:16px;
  color:var(--ink2);
}
/* "Brush [slider] 30 px" and "Tol. [slider] 32 ▾": the caption is a word a narrow head drops (the 516 rule below);
   the inputs keep their own names */
#imgTools .topts .tcap{display:none; font-size:12px; line-height:16px; color:var(--ink2)}
#imgTools .topts .tunit{margin-left:-2px; font-size:12px; line-height:16px; color:var(--ink2)}
@container (min-width:516px){
  #imgTools .topts{width:136px; flex-basis:136px}
  #imgTools .topts .tcap{display:inline}
}
#imgTools #brushWWrap{height:20px; padding:0; gap:4px}
#brushWWrap input[type=range]{width:40px; margin:0; accent-color:var(--ink2)}
/* a slider's number (05_wiring.js wireSliderNum): the readout at rest, tabular and frameless; a field on hover and
   focus (the field ring of the base rules); "255" and "200" whole. Its focus restates the blue border, which
   input.snum:hover would otherwise beat (same specificity, later) */
input.snum{
  width:30px; height:20px; padding:0 3px; border:1px solid transparent; border-radius:3px; background:transparent;
  color:inherit; font-size:12px; line-height:18px; text-align:right; font-variant-numeric:tabular-nums;
}
input.snum:hover{border-color:var(--frame); background:var(--paper)}
input.snum:focus{border-color:var(--blue); background:var(--paper); color:var(--ink)}
/* the Wand's slot: tolerance slider, its number and the ▾ (44 + 4 + 30 + 4 + 16 = 98 of the slot's 98 inner px);
   the ▾ is no tool (no data-tool), it opens #wandPop and carries a dot while Grow / shrink or Selects is off its
   default (syncWandUI writes .set) */
#imgTools #wandWrap{display:flex; align-items:center; gap:4px; height:20px}
#imgTools #wandWrap .toggle{height:20px; padding:0; gap:4px}
#wandTol{width:44px; margin:0; accent-color:var(--ink2)}
#wandRangeLabel{width:48px}
#imgTools #btnWandOpts{
  width:16px; min-width:16px; height:24px; padding:0; border-radius:3px; position:relative;
  font-size:15px; line-height:20px;
}
#btnWandOpts.set::after{
  content:""; position:absolute; top:1px; right:0; width:4px; height:4px; border-radius:999px; background:var(--ink2);
}
/* the options pop (body level: #imgTools' container query contains a fixed child and its clip would cut it off) */
#wandPop{width:248px; padding:6px 8px}
#wandPop .wrow{display:flex; align-items:center; gap:8px; padding:5px 0}
#wandPop .wcap{font-size:12px; color:var(--ink2); min-width:72px; white-space:nowrap}
#wandPop .scopeseg > label{padding:0 8px}
#wandPop .whint{padding:2px 0 3px; font-size:12px; line-height:16px; color:var(--ink2); white-space:normal}
#wandRange{width:96px; margin:0; accent-color:var(--ink2)}
/* the menus (body level, as #wandPop): Page actions and the selected row's More. One item a line, hairlines between
   the groups; a disabled item is faded as every button is and keeps its title. Delete block… is in red pencil */
#pageActsPop{min-width:200px}
#rowMorePop{min-width:150px}
:is(#pageActsPop,#rowMorePop) [role=menuitem]{
  display:flex; align-items:center; width:100%; height:28px; padding:0 12px; border-radius:3px;
  color:var(--ink); text-align:left; white-space:nowrap;
}
:is(#pageActsPop,#rowMorePop) [role=menuitem]:hover:not(:disabled),
:is(#pageActsPop,#rowMorePop) [role=menuitem]:focus-visible{background:var(--wash)}
:is(#pageActsPop,#rowMorePop) [role=menuitem]:focus-visible{outline-offset:-2px}
#rowMorePop #btnRowDelete,#rowMorePop #btnRowDelete:hover:not(:disabled){color:var(--red)}
#pageActsPop .msep{height:1px; margin:4px 6px; background:var(--rule)}
/* MT page's second line: what the active MT backend leaves out (12_mt.js mtCapsNote) */
#btnMtPage:has(.mtnote){flex-direction:column; align-items:flex-start; justify-content:center; height:auto; padding:5px 12px}
#btnMtPage .mtnote{font-size:11px; color:var(--ink2)}
/* the brush's footprint (brushRing places it): two tones, 1 px black inside 1 px white, so one of them shows on any
   art, ink or paper; under the top bar, and nothing in it transitions */
#brushRing{
  position:fixed; z-index:20; box-sizing:border-box; border:1px solid #000; border-radius:50%;
  box-shadow:0 0 0 1px #fff; pointer-events:none;
}
/* the wide bar a press on a slider opens (showSliderZoom places it; body level, as #wandPop): a floating paper
   strip with the value (and the setting's name) under its thumb, the rail in the well, the part from 0 (the left
   end for the sliders that start at 0) to the value and the thumb in the sliders' graphite. It takes the pointer's
   capture, never the focus, and nothing in it transitions */
#sliderZoom{
  position:fixed; z-index:45; padding:6px 12px 18px; background:var(--paper); border-radius:8px;
  box-shadow:var(--float); cursor:ew-resize; touch-action:none; user-select:none;
}
#sliderZoom .szval{
  position:absolute; bottom:2px; font-size:12px; line-height:16px; font-weight:600; color:var(--ink);
  font-variant-numeric:tabular-nums; white-space:nowrap;
}
#sliderZoom .szcap{font-weight:400; color:var(--ink2); margin-right:4px}
#sliderZoom .szrail{display:block; position:relative; height:20px}
#sliderZoom .szrail::before{
  content:""; position:absolute; left:0; right:0; top:8px; height:4px; border-radius:999px; background:var(--well);
}
#sliderZoom .szfill{position:absolute; left:0; top:8px; height:4px; border-radius:999px; background:var(--ink2)}
#sliderZoom .szthumb{
  position:absolute; top:3px; width:14px; height:14px; margin-left:-7px; border-radius:999px; background:var(--ink2);
  box-shadow:0 0 0 2px var(--paper);
}
/* the composer's @ list (24_notify.js renderMentionPick places it): a small floating list under the box, the chosen
   row on the wash; the box keeps the focus */
#mentionPick{
  position:fixed; z-index:45; min-width:160px; max-width:280px; padding:4px; background:var(--paper);
  border-radius:8px; box-shadow:var(--float); color:var(--ink);
}
#mentionPick .mopt{padding:4px 8px; border-radius:3px; cursor:pointer; overflow:hidden; text-overflow:ellipsis;
  white-space:nowrap}
#mentionPick .mopt:hover,#mentionPick .mopt[aria-selected=true]{background:var(--wash); color:var(--blue)}
/* the narrow head (#left at 1280, or dragged narrow) drops words, not a control. Line 1 is what needs room (the
   widths are the head's content box, measured with the page stale, Undo and Redo with long labels and a three-digit
   count; the View is as wide on every view): about 638 px with every word and both labels at 60 px; then at 660 px
   Render's " changed" (clipped as .vh, so it stays in Render's name; the amber fill still says it) and the View's
   labels at 6 px; then line 1's gaps at 6 px; then at 520 px Undo and Redo show their arrows; then tighter buttons
   and gaps, about 419 px at the 433 rule. 1280, the panel closed or open, is a 419-px head. Line 2 (Page actions, or
   the tool trough) fits as it is, except the trough, whose button padding the rules at 484, 430 and 411 px take to
   4, 3 and 2 px. A head narrower
   than line 1's floor cuts it at its right end, which the page column's PAGE_HEAD (04_panel.js) keeps from happening */
@container (max-width:660px){
  #btnRenderPage .rcap{position:absolute; width:1px; height:1px; margin:-1px; padding:0; overflow:hidden;
    clip:rect(0 0 0 0); clip-path:inset(50%); white-space:nowrap}
  #viewSelWrap > label{padding:0 6px}
}
/* the zoom label's " fit" (the button's name says "Fit width") */
@container (max-width:538px){
  #zoomLabel .zfit{display:none}
  #imgTools .tbrow:first-child{gap:6px}
  #imgTools .zoom > button{padding:0 6px}
  #histGroup button{padding:0 6px}
}
/* Undo and Redo give their words to the arrows; the word stays the name (font-size 0 keeps it in the tree) */
@container (max-width:520px){
  #imgTools #histGroup button{font-size:0; padding:0}
  #histGroup button::before{font-size:15px}
  #btnUndo::before{content:"↶" / ""}
  #btnRedo::before{content:"↷" / ""}
}
/* line 2's tool trough: below 485 px its buttons and gaps take 4 px */
@container (max-width:484px){
  #toolGroup button{padding:0 4px}
  #imgTools .toolsets > * + *{margin-left:4px}
}
@container (max-width:471px){
  #imgTools .tbrow,#imgTools .tbrow:first-child{gap:4px}
  #toolGroup button{padding:0 4px}
  #imgTools .tbend #btnRenderPage{padding:0 8px}
  #imgTools .tbend #optAutoRenderWrap{padding:0 6px}
}
@container (max-width:443px){
  #imgTools .tbrow:first-child{gap:3px}
  #imgTools .zoom > button{padding:0 4px}
  #imgTools .tbend #btnRenderPage{padding:0 6px}
  #imgTools .tbend #btnRenderStale{padding:0 5px}
  #btnRenderStale .scount{margin-left:2px}
  #imgTools .tbend #optAutoRenderWrap{padding:0 5px 0 4px; gap:4px}
}
/* padding only: the View's labels and a little of each group's */
@container (max-width:433px){
  #imgTools .tbrow:first-child{gap:2px}
  /* Auto is its checkbox alone, centred in the 24-px target floor: the word stays its name (clipped as .vh), the
     label's title says what it does */
  #optAutoRenderWrap .aw{position:absolute; width:1px; height:1px; margin:-1px; padding:0; overflow:hidden;
    clip:rect(0 0 0 0); clip-path:inset(50%); white-space:nowrap}
  #imgTools .zoom > button{padding:0 3px}
  #imgTools .tbend #btnRenderPage{padding:0 4px}
  #imgTools .tbend #btnRenderStale{padding:0 4px}
  #imgTools .tbend #optAutoRenderWrap{min-width:24px; padding:0 3px; gap:3px; justify-content:center}
}
/* a head under 430 px (1280's is 419): every tool takes 3 px. The 4-px trough is wider than 1280's 419-px head (a
   step that began at 418 let a few px of text advance overflow line 2), so the step starts well above it and the
   trough is about 410 px */
@container (max-width:430px){
  #toolGroup button{padding:0 3px}
}
/* the trough's last step (padding only), so it fits wherever line 1 does */
@container (max-width:411px){
  #toolGroup button{padding:0 2px}
}
/* the page lies on lit glass (fixed to the pane, not the stage): the brightest surface on screen */
/* touch-action: one finger scrolls natively, but the browser's own pinch zoom is off so a two-finger pinch reaches
   wirePinch (05_wiring.js) as pointer events and zooms the page, not the UI */
/* a Tab stop (index.html) whose ring is its own 2-px border, glass until :focus-visible inks it: an outline inside
   would be painted over by the positioned #stage and the scrollbar, one outside is off screen or under its neighbours,
   and a border lies outside both the scrollbar and the padding box the pane clips its page to. 4 + 2 px keep the 6-px
   inset of the page; fitScale and fitPageScale (03_render.js) read clientWidth, which leaves the border out */
#imgPane{
  flex:1 1 auto; overflow:auto; padding:4px; border:2px solid transparent; background:var(--glass) border-box;
  touch-action:pan-x pan-y;
}
#imgPane:focus-visible{outline:0; border-color:var(--ink)}
#stage{
  position:relative; margin:0 auto; background:var(--paper);
  box-shadow:0 0 0 1px rgba(31,80,104,.16), 0 3px 12px rgba(31,80,104,.22);
}
#pageImg{display:block; width:100%; height:100%; user-select:none; -webkit-user-drag:none}

/* ---- paint tools (brush / erase / rect / wand; the Wand's hover preview is drawn on #paint, which stays click-through) ---- */
#paint{position:absolute; inset:0; width:100%; height:100%; touch-action:none; pointer-events:none; z-index:5}
#stage.tool-brush #paint,#stage.tool-erase #paint{pointer-events:auto}
#stage:is(.tool-brush,.tool-erase,.tool-rect,.tool-wand){cursor:crosshair}
/* the Wand over a selection too big to inpaint (wandDraw writes the class): a click would be refused */
#stage.tool-wand.wand-big{cursor:not-allowed}
#stage:is(.tool-brush,.tool-erase,.tool-rect,.tool-wand) #boxes{pointer-events:none}
/* the Typeset tool: the text box and its grips are what one handles, so the detection boxes take no pointer either
   (a press beside a grip reached a neighbour's box and changed the block); the balloon tags take the click instead
   (the twin of the drawing tools' tag rule below) and the other boxes go quiet (the box rules below) */
#stage.tool-typeset #boxes{pointer-events:none}
#stage.tool-typeset{cursor:default}

/* ---- typeset text box: 1.5-px ink dashes over a 1-px white halo, so it reads on any art and is never mistaken
   for a page stroke ---- */
.tsbox{
  position:absolute; z-index:6; border:1.5px dashed #000; background:rgba(255,255,255,.04);
  outline:1px solid rgba(255,255,255,.85); box-shadow:inset 0 0 0 1px rgba(255,255,255,.85);
  cursor:move; touch-action:none; min-width:4px; min-height:4px; transform-origin:50% 50%;
}
.tsbox .handle{
  position:absolute; width:9px; height:9px; background:#fff; border:1.5px solid #000;
  border-radius:2px; touch-action:none;
}
.tsbox .handle.n{left:50%; top:-5px; margin-left:-5px; cursor:ns-resize}
.tsbox .handle.s{left:50%; bottom:-5px; margin-left:-5px; cursor:ns-resize}
.tsbox .handle.w{top:50%; left:-5px; margin-top:-5px; cursor:ew-resize}
.tsbox .handle.e{top:50%; right:-5px; margin-top:-5px; cursor:ew-resize}
.tsbox .handle.nw{left:-5px; top:-5px; cursor:nwse-resize}
.tsbox .handle.ne{right:-5px; top:-5px; cursor:nesw-resize}
.tsbox .handle.sw{left:-5px; bottom:-5px; cursor:nesw-resize}
.tsbox .handle.se{right:-5px; bottom:-5px; cursor:nwse-resize}
/* the rotate grips (startTsRotate): a 24-px square 20 px outside each corner, under the corner's own handle
   (appended after them), so what turns the box is a 15-px L around the handle; the cursor is an inline arrow.
   The .deg readout shows the angle while a rotation drag runs, upright at the box centre (paintTsBox sets
   --tsa to the box's angle) */
.tsbox .rot{
  position:absolute; width:24px; height:24px; touch-action:none;
  cursor:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 20 20'%3E%3Cg fill='none' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M15.5 10A5.5 5.5 0 1 1 13.9 6.1M10.9 6.1H13.9V3.1' stroke='white' stroke-width='4'/%3E%3Cpath d='M15.5 10A5.5 5.5 0 1 1 13.9 6.1M10.9 6.1H13.9V3.1' stroke='black' stroke-width='1.5'/%3E%3C/g%3E%3C/svg%3E") 10 10, crosshair;
}
.tsbox .rot.nw{left:-20px; top:-20px}
.tsbox .rot.ne{right:-20px; top:-20px}
.tsbox .rot.sw{left:-20px; bottom:-20px}
.tsbox .rot.se{right:-20px; bottom:-20px}
.tsbox .deg{
  position:absolute; left:50%; top:50%; transform:translate(-50%,-50%) rotate(calc(-1 * var(--tsa, 0deg)));
  padding:0 5px; font:12px/16px var(--font); font-variant-numeric:tabular-nums; white-space:nowrap;
  color:var(--ink); background:var(--paper); border:1px solid var(--frame); border-radius:3px;
  box-shadow:var(--lift); pointer-events:none;
}

/* ---- the detection box of the selected block (the Select tool, 06_boxes.js) ----
   The body is click-through: the selected block's own .box underneath takes the press that moves it, and
   a smaller box inside keeps its click. Only the eight edge zones take the pointer - four strips along the
   whole side, four corners - reaching 6 px beyond the orange line and at most min(3px, 25%) inside it.
   The white squares are ::after marks that take no pointer, so they never widen a zone. */
.detbox{
  position:absolute; z-index:6; border:2px solid var(--sel); background:rgba(232,89,12,.06);
  pointer-events:none; min-width:4px; min-height:4px;
}
.detbox .handle{position:absolute; pointer-events:auto; touch-action:none}
.detbox .handle.n,.detbox .handle.s{left:0; right:0; height:calc(8px + min(3px, 25%)); cursor:ns-resize}
.detbox .handle.w,.detbox .handle.e{top:0; bottom:0; width:calc(8px + min(3px, 25%)); cursor:ew-resize}
.detbox .handle.nw,.detbox .handle.ne,.detbox .handle.sw,.detbox .handle.se{
  width:calc(8px + min(3px, 25%)); height:calc(8px + min(3px, 25%));
}
.detbox .handle.n,.detbox .handle.nw,.detbox .handle.ne{top:-8px}
.detbox .handle.s,.detbox .handle.sw,.detbox .handle.se{bottom:-8px}
.detbox .handle.w,.detbox .handle.nw,.detbox .handle.sw{left:-8px}
.detbox .handle.e,.detbox .handle.ne,.detbox .handle.se{right:-8px}
.detbox .handle.nw,.detbox .handle.se{cursor:nwse-resize}
.detbox .handle.ne,.detbox .handle.sw{cursor:nesw-resize}
.detbox .handle::after{
  content:''; position:absolute; box-sizing:border-box; width:9px; height:9px; background:#fff;
  border:1.5px solid var(--sel); border-radius:2px; pointer-events:none;
}
.detbox .handle.n::after,.detbox .handle.s::after{left:50%; margin-left:-5px}
.detbox .handle.w::after,.detbox .handle.e::after{top:50%; margin-top:-5px}
.detbox .handle.n::after,.detbox .handle.nw::after,.detbox .handle.ne::after{top:3px}
.detbox .handle.s::after,.detbox .handle.sw::after,.detbox .handle.se::after{bottom:3px}
.detbox .handle.w::after,.detbox .handle.nw::after,.detbox .handle.sw::after{left:3px}
.detbox .handle.e::after,.detbox .handle.ne::after,.detbox .handle.se::after{right:3px}
.detbox .reset{
  position:absolute; right:-2px; top:-26px; font-size:12px; line-height:16px; padding:1px 7px; pointer-events:auto;
  background:var(--paper); color:var(--ink); border:1.5px solid var(--sel); border-radius:3px; box-shadow:var(--lift);
  cursor:pointer; white-space:nowrap;
}
.detbox.near-top .reset{top:6px; right:6px}   /* #imgPane{overflow:auto} clips above #stage; clear of the zones */

/* ---- the block boxes: page strokes are --box (one of the three blues), orange selected, red pencil flagged; only
   a box waiting for the editor (Translated) is dashed, so "waiting" reads on the page. A border-box that starts exactly
   at its inline left/top, no margin, no transform and no z-index (layoutImage orders them largest first, so a
   smaller box paints and clicks above a bigger one). --tagc is the colour of its tag, --tagv its verdict ink */
.box{
  position:absolute; border:1.5px solid var(--box); background:rgba(47,128,237,.08);
  cursor:pointer; border-radius:2px; --tagc:var(--box-ink); --tagv:transparent;
}
.box:hover{background:rgba(47,128,237,.2)}
.box.sel{border-color:var(--sel); border-width:2.5px; background:rgba(232,89,12,.16);
         box-shadow:0 0 0 2px rgba(232,89,12,.28); --tagc:var(--sel)}
/* Select: the page drafts a new block, the selected block's own box is the move grip */
#stage.tool-select{cursor:crosshair}
#stage.tool-select .box.sel{cursor:move}
/* the selected box keeps its own colour, so flag/verdict only paint unselected boxes */
.box.flag:not(.sel){border-color:var(--red); background:rgba(196,54,43,.08); --tagc:var(--red)}
.box.flag:not(.sel):hover{background:rgba(196,54,43,.2)}
.box[data-st=commit]:not(.sel){border-style:dashed}
.box[data-st=final]{--tagv:var(--final)}
.box[data-st=edited]{--tagv:var(--edited)}
.box[data-st=commit]{--tagv:var(--commit)}
/* a row the filters hide fades its box (syncBoxOut) */
.box.out{opacity:.3}
/* under the Typeset tool the other blocks' boxes are quiet: no fill, the outline at 45 % of --box / --red
   (rgba literals, :root says why), the dashes kept; the tags keep their colour. The selected box goes clear: the
   text box is its frame there */
#stage.tool-typeset .box:not(.sel){background:transparent; border-color:rgba(47,128,237,.45)}
#stage.tool-typeset .box.flag:not(.sel){border-color:rgba(196,54,43,.45)}
#stage.tool-typeset .box.sel{background:transparent; border-color:transparent; box-shadow:none}
/* the Result view shows the lettering: the unselected boxes step back to their tags (syncViewSel) */
#stage.view-result .box:not(.sel):not(:hover){background:transparent; border-color:transparent}
/* the bold element, the balloon tag, carries the verdict. The block number as a small speech balloon on the box's
   top-left corner, its tail on the corner, always shown in both modes: its OUTLINE says the verdict (Translated a
   dashed commit-blue ring, Reviewed green, Final graphite; numeral in the verdict ink on paper, every one AA as text)
   and its FILL says the selection (orange, ink numeral). Flagged with no verdict is red pencil, no status is
   --box-ink with a white numeral (4.9:1). It takes no pointer, so it never widens its box's hit area or covers a
   neighbour's; only the selected block's tag does, as part of that box's move grip; under the Typeset tool every
   tag does (its #boxes takes none) and a click on it selects its block. Only the selected tag has a z-index (1),
   so it sits over a neighbour's (under the Wand every tag has 6, above #paint's preview); every box and tag paints over #chars (z auto, before #boxes in the DOM). The
   numeral is the UI face in bold: at page size the display face's 7 reads as a 1. The corner above the tail is
   square and the tail starts 1 px inside the balloon */
.box .bnum{
  position:absolute; left:-2px; bottom:calc(100% + 5px); display:block; pointer-events:none;
  min-width:18px; height:16px; padding:0 5px; font:700 12px/16px var(--font); font-variant-numeric:tabular-nums;
  text-align:center; white-space:nowrap;
  background:var(--tagc); color:#fff; border-radius:8px 8px 8px 0;
}
.box .bnum::after{
  content:""; position:absolute; left:0; top:calc(100% - 1px);
  border-top:5px solid var(--tagc); border-right:4px solid transparent;
}
/* a verdict, not selected: paper fill, the numeral and a 1.5-px ring in the verdict ink (Translated dashed) */
.box[data-st]:not(.sel) .bnum{
  background:var(--paper); color:var(--tagv); border:1.5px solid var(--tagv); line-height:13px; padding:0 3.5px;
}
.box[data-st=commit]:not(.sel) .bnum{border-style:dashed}
.box[data-st]:not(.sel) .bnum::after{left:-1.5px; top:calc(100% + .5px); border-top-color:var(--tagv)}
/* selected: the orange fill with an ink numeral (5.9:1); a verdicted block keeps its ring outside the fill */
.box.sel .bnum{color:var(--ink); z-index:1}
.box.sel[data-st] .bnum{box-shadow:0 0 0 1.5px var(--paper), 0 0 0 3px var(--tagv)}
/* a box near the page top (layoutImage's near-top): #imgPane{overflow:auto} would clip the tag above #stage, so it
   hangs mirrored inside the box's top edge, its tail pointing up at the same corner (the square corner on top) */
.box.near-top .bnum{bottom:auto; top:4px; border-radius:0 8px 8px 8px}
.box.near-top .bnum::after{top:auto; bottom:calc(100% - 1px); border-top:0; border-bottom:5px solid var(--tagc)}
.box.near-top[data-st]:not(.sel) .bnum::after{top:auto; bottom:calc(100% + .5px); border-bottom-color:var(--tagv)}
/* hover pairing (pairHot): the row under the pointer and its box ring each other's tag in --box (no motion) */
.box.hot .bnum{outline:2px solid var(--box); outline-offset:1px}
.box.sel .bnum{pointer-events:auto}
/* a drawing tool owns the stage: the twin of #stage.tool-brush|erase|rect #boxes above, which an `auto` on the
   selected tag would otherwise override */
#stage:is(.tool-brush,.tool-erase,.tool-rect,.tool-wand) .box.sel .bnum{pointer-events:none}
/* the Wand's red preview (#paint, z 5) lies right against the selected box: that box takes the quiet box ink and the
   tags (still no pointer) rise over the preview, so the orange tag alone marks the block */
#stage.tool-wand .box.sel{border-color:var(--box)}
#stage.tool-wand .box .bnum{z-index:6}
/* the reverse of that twin: the Typeset tool gives every tag the pointer its #boxes lost, and a click on a number
   bubbles to its box's onclick and selects that block; a 24-px hit area around the small tag */
#stage.tool-typeset .box .bnum{pointer-events:auto; cursor:pointer}
#stage.tool-typeset .box .bnum::before{content:""; position:absolute; inset:-4px -3px}

/* ---- the character overlay of the Cast tab (11_cast.js) ----
   z-index auto and before #boxes in the DOM: positioned boxes at z 0 or auto paint and take clicks in DOM order, so
   every block box and its tag (the selected tag's z 1 too) sit over the outlines, and .draft (4), #paint (5), .tsbox
   and .detbox (6) stay above. The overlay itself is click-through and an outline takes a click only where no box
   covers it; one covered whole is reached by Tab and Enter (drawChars). z auto, not 0: #chars makes no stacking
   context, so the focus ring of an outline with the keyboard, drawn by its ::after at z 3 (under .draft), paints
   over the boxes and tags while the outline itself stays under them and a box keeps its click (the ring takes no
   pointer). The paper halo keeps the ink ring off the art's black panel borders */
#chars{position:absolute; inset:0; z-index:auto; pointer-events:none}
#chars .cbox:focus-visible{outline:0}
#chars .cbox:focus-visible::after{
  content:""; position:absolute; inset:-4px; z-index:3; pointer-events:none;
  outline:2px solid var(--ink); box-shadow:0 0 0 2px var(--paper), inset 0 0 0 2px var(--paper);
}
#chars .cbox{
  position:absolute; border:2px solid #0E97A3; border-radius:2px;
  pointer-events:auto; cursor:pointer; background:rgba(14,151,163,.06);
}
#chars .cbox:hover{background:rgba(14,151,163,.18)}
#chars .clab{
  position:absolute; left:2px; top:1px; font:700 12px/1 system-ui; color:#0b7d88;
  background:var(--paper); padding:0 2px; border-radius:2px;
}
/* a drawing tool owns the stage: a pointerdown on a .cbox bubbles to #stage and must not open #castDlg over
   the rectangle or stroke just drawn. The twin of #stage.tool-brush|erase|rect #boxes above; it names .cbox,
   which declares its own pointer-events (a rule on #chars alone never reached it). So does the Typeset tool: its
   block choice is the balloon tags, and an outline left from Translate's Cast tab has no use there. Select leaves
   a .cbox press to its click. The pointer only: Tab and Enter still name it, except under the Typeset tool,
   which hides the overlay (visibility, so no Tab stops either) and keeps the outlines for a switch back. */
#stage:is(.tool-brush,.tool-erase,.tool-rect,.tool-wand,.tool-typeset) #chars .cbox{pointer-events:none}
#stage.tool-typeset #chars{visibility:hidden}

/* ---- the draft rectangle: a new block (Select) or the rect tool ---- */
.draft{
  position:absolute; border:2px dashed var(--sel); background:rgba(232,89,12,.10);
  border-radius:2px; pointer-events:none; z-index:4;
}

/* ---- rows region (middle + right) ---- */
#rowsWrap{flex:1 1 auto; min-width:0; position:relative; background:var(--paper)}
/* the rows are the container (`rows`) of the row rules' @container queries below; each head is its own
   (`rowhead`), so what its line holds answers to the head's width */
#rows{position:absolute; inset:30px 0 0; overflow-y:auto; overflow-x:hidden; --midw:33%; container:rows / inline-size}

/* ---- the rows head (#filters, #rowsWrap's first child): 30 px of desk-2 over the column it filters, in both
   modes, no rule. Translate: "Show only" and the chips with their counts, then the block counter, the shown count
   and Show all; Typeset: the block counter only (MODE_UI). It never wraps: a narrow head drops words (below) ---- */
#filters{
  position:absolute; top:0; left:0; right:0; height:30px; z-index:4;
  display:flex; align-items:center; gap:4px; padding:0 8px 0 12px; overflow:hidden;
  background:var(--desk-2); color:var(--ink2); white-space:nowrap; container:rowshead / inline-size;
}
#filters #fltLead{margin-right:4px}
/* a chip is a toggle: a frame at rest, a paper chip with a tick when chosen; its checkbox is still there for the
   keyboard and the reader */
#filters .fchip{
  position:relative; display:inline-flex; align-items:center; gap:5px; flex:0 0 auto; height:24px; padding:0 9px;
  border:1px solid var(--frame); border-radius:999px; cursor:pointer;
}
#filters .fchip:hover{background:var(--desk-hover); color:var(--ink)}
#filters .fchip:has(input:checked){background:var(--paper); color:var(--ink); border-color:var(--ink2); box-shadow:var(--lift)}
#filters .fchip:has(input:checked)::before{content:"✓" / ""; font-size:12px; margin-right:-1px}
#filters .fchip input{position:absolute; left:0; top:0; width:1px; height:1px; margin:0; opacity:0; pointer-events:none}
#filters .fchip:has(input:focus-visible){outline:2px solid var(--ink); outline-offset:1px}
#filters .fcnt{font-size:12px; line-height:16px; font-variant-numeric:tabular-nums; color:var(--ink2)}
#filters .fchip:has(input:checked) .fcnt{color:var(--ink)}
#filters .spacer{flex:1 1 auto; min-width:8px}
#filters #blockCount{flex:0 0 auto; min-width:0; margin-right:8px}
#filters #fltCount{font-size:12px; line-height:16px; font-variant-numeric:tabular-nums; margin-right:4px}
#filters #btnFltAll{height:24px; padding:0 8px; flex:0 0 auto}
/* the panel's tab strip, docked here while the panel is closed (dockTabs): the closed sheet's tabs, each a --desk
   tab standing on the rows (the open panel's tab line is --desk too), so they read as the panel's tabs, not as more
   filters */
#filters .ptablist{align-self:flex-end; flex:0 1 auto; min-width:0; margin-left:12px; gap:2px; overflow:auto hidden; scrollbar-width:none}
#filters .ptablist button{height:24px; background:var(--desk); color:var(--ink2)}
#filters .ptablist button:hover:not(:disabled){background:var(--desk-hover); color:var(--ink)}
/* the compact rows head gives way in steps, each at the width (the head's content box) where what it holds stops
   fitting, measured with every word at 1280 plus 12 px for longer counts: (1) the chips' padding (and the docked
   tabs'), (2) "Show only", (3) the whole shown count (it keeps its " shown" while it stays), (4) the block
   counter's "Block " - so two bare "n of m" never stand side by side, and Typeset's counter, the head's only
   item there, always keeps its word - then (5), with the tabs docked, the chips' words (.fw, clipped like .vh, so
   each chip's name keeps its word). The chips and Show all stay. A ticked chip (Show all shown) needs about 86 px
   more and the four docked tabs about 275 (their margin included), so the same steps come earlier. Step (1) takes
   the docked tabs to 1-px sides and 12-px words, where they need about 185; whatever is still left over (the Team
   tab's badge among it) scrolls inside the docked tab strip (the item that yields), never in #filters. Typeset's
   head holds only the block counter and its two tabs, so its tabs keep the panel's size there */
@container rowshead (max-width:496px){ #filters .fchip{padding:0 6px} }
@container rowshead (max-width:478px){ #filters #fltLead{display:none} }
@container rowshead (max-width:411px){ #filters #fltCount{display:none} }
@container rowshead (max-width:352px){ .mode-translate #filters .bcw{display:none} }
@container rowshead (max-width:582px){ #filters:has(> #btnFltAll:not([hidden])) .fchip{padding:0 6px} }
@container rowshead (max-width:564px){ #filters:has(> #btnFltAll:not([hidden])) #fltLead{display:none} }
@container rowshead (max-width:497px){ #filters:has(> #btnFltAll:not([hidden])) #fltCount{display:none} }
@container rowshead (max-width:414px){ .mode-translate #filters:has(> #btnFltAll:not([hidden])) .bcw{display:none} }
@container rowshead (max-width:783px){
  #filters:has(> .ptablist) .fchip{padding:0 6px}
  .mode-translate #filters:has(> .ptablist) .ptablist button{padding:0 1px; font-size:12px}
}
/* the Team tab's badge (up to "@99+", about 34 px) takes step (1) that much earlier, so the strip still fits at 1280 */
@container rowshead (max-width:819px){
  #filters:has(> .ptablist .ubadge:not([hidden])) .fchip{padding:0 6px}
  .mode-translate #filters:has(> .ptablist .ubadge:not([hidden])) .ptablist button{padding:0 1px; font-size:12px}
}
@container rowshead (max-width:917px){
  #filters:has(> .ptablist .ubadge:not([hidden])):has(> #btnFltAll:not([hidden])) .fchip{padding:0 6px}
  .mode-translate #filters:has(> .ptablist .ubadge:not([hidden])):has(> #btnFltAll:not([hidden])) .ptablist button{padding:0 1px; font-size:12px}
}
@container rowshead (max-width:676px){ #filters:has(> .ptablist) #fltLead{display:none} }
@container rowshead (max-width:609px){ #filters:has(> .ptablist) #fltCount{display:none} }
@container rowshead (max-width:551px){ .mode-translate #filters:has(> .ptablist) .bcw{display:none} }
@container rowshead (max-width:515px){ #filters:has(> .ptablist) .fw{position:absolute; width:1px; height:1px; margin:-1px; overflow:hidden; clip-path:inset(50%); white-space:nowrap} }
@container rowshead (max-width:881px){
  #filters:has(> .ptablist):has(> #btnFltAll:not([hidden])) .fchip{padding:0 6px}
  .mode-translate #filters:has(> .ptablist):has(> #btnFltAll:not([hidden])) .ptablist button{padding:0 1px; font-size:12px}
}
@container rowshead (max-width:774px){ #filters:has(> .ptablist):has(> #btnFltAll:not([hidden])) #fltLead{display:none} }
@container rowshead (max-width:707px){ #filters:has(> .ptablist):has(> #btnFltAll:not([hidden])) #fltCount{display:none} }
@container rowshead (max-width:626px){ .mode-translate #filters:has(> .ptablist):has(> #btnFltAll:not([hidden])) .bcw{display:none} }
@container rowshead (max-width:590px){ #filters:has(> .ptablist):has(> #btnFltAll:not([hidden])) .fw{position:absolute; width:1px; height:1px; margin:-1px; overflow:hidden; clip-path:inset(50%); white-space:nowrap} }

/* a row is a white script sheet ruled off by a non-repro blue hairline, no gaps and no cards. Three custom
   properties carry its state to its parts: --tint is its background and the only background of its three text
   fields (the field rule at the end of this section), --tagc the fill of its balloon tag and --tagv its verdict
   ink (the tag's ring). It is positioned for its grip (the manual order, below) and for nothing else: no
   z-index, no stacking context. The head (.rowhead, the row's first grid child) spans the source and the
   English, its verdict (who gave it, then the stamp) at its right end over the English, so the Japanese and the
   English start on one line under it */
.row{
  position:relative; display:grid; grid-template-columns:var(--midw) minmax(0,1fr); grid-template-rows:auto auto 1fr;
  padding:8px 0 10px; border-bottom:1px solid var(--rule);
  --tint:var(--paper); --tagc:var(--box-ink); --tagv:transparent; background:var(--tint);
}
.rowhead{grid-column:1 / 3; grid-row:1; display:flex; align-items:center; gap:8px; min-height:26px; min-width:0;
  margin-bottom:8px; padding:0 12px 0 14px; white-space:nowrap; container:rowhead / inline-size}
.cell{padding:0 12px; min-width:0}
.cell.mid{grid-column:1; grid-row:2 / span 2; padding-left:14px}
.cell.right{grid-column:2; grid-row:2}
/* translate (#rows.notes; `notes` and `nosrc` are mutually exclusive, syncModeUI writes both): the block's thread
   (20_thread.js) under the selected row's English, in the row (decision 0060: no thread column) */
.cell.notes{grid-column:2; grid-row:3; padding-top:6px}
#rows.notes .tlist{max-height:120px}
/* the source cell: the crop down the left, the Japanese, the glossary chips and the block actions in the column
   beside it. The cell is its own container (`src`), so the crop's cap is a share of the source column (#divM's),
   never of the rows: 28 % keeps the crop at 110 while the cell's content box is at least 393 px (the 1600 default
   is --midw 431, a 405-px box); the share's 54-px floor holds under a 193-px box (--midw 219), so on the 1280 rows
   with the panel open (a 166-193-px box) the crop stays at main's 54 and the Japanese keeps 104 px or more; and the
   third term (100 % - 108) binds only under a 162-px box (--midw 188), so the Japanese keeps 100 px at the 170-px
   floor (144 - 36 - the 8-px gap). That column is the block actions' too: OCR / Inpaint (95 px, on its -5 margin)
   fit in it at every width */
#rows.notes .cell.mid{display:grid; grid-template-columns:auto minmax(0,1fr); column-gap:8px; row-gap:6px;
  align-content:start; align-items:start; container:src / inline-size}
#rows.notes .srcwrap{display:contents}
#rows.notes .cell.mid canvas.thumb{grid-column:1; grid-row:1 / span 3;
  max-width:min(110px, max(28cqw, 54px), calc(100cqw - 108px)); height:auto !important}
#rows.notes .cell.mid > :not(.srcwrap){grid-column:2; margin:0}
#rows.notes .cell.mid textarea.src{grid-column:2; grid-row:1}
#rows.notes .cell.mid .rtools{margin-left:-5px}
/* the composer is one line (30 px) at least: grow() measures a hidden box as 0 and leaves it 2 px high, which would
   show as a sliver when its row is selected, until the next regrow */
.cell.notes textarea.tcomp{min-height:30px}
/* ---- an unselected row (decision 0060; applyRowMode hides its actions): one band of the tag, the crop, the
   Japanese and the English, read as text (the same fields, frameless: a press selects the row and puts the caret
   where it landed, pressRow), its open suggestions read-only under the English (.sugs, applySugMode), and its facts
   at the right end: the status chip (.vtext), .rside (the thread chip, the speaker and panel as words) and an open
   flag's score (.badges, syncRowFlag: flagged and no status). The head lends its parts to
   the row's grid (display:contents), so its line goes. The source column keeps --midw, so the English starts where
   the selected row's does, and the tag stands in the source cell's left padding (it is positioned, so it paints
   over the cell). The facts stand in a column of one width (130 px, a long name cut), so every such row's English
   is one width; on a narrow rows column they go under the English (below). The crop is at most 64 px high. The
   verdict's edge is the row's, as on any row */
#rows.notes .row:not(.sel){grid-template-columns:var(--midw) minmax(0,1fr) 130px; grid-template-rows:auto auto auto 1fr auto}
#rows.notes .row:not(.sel) > .rowhead,#rows.notes .row:not(.sel) .verdict{display:contents}
#rows.notes .row:not(.sel) .rowhead .num{grid-column:1; grid-row:1; justify-self:start; align-self:start; margin-left:14px}
#rows.notes .row:not(.sel) .cell.mid{grid-row:1 / -1; padding-left:58px}
#rows.notes .row:not(.sel) .cell.right{grid-row:1 / 5}
#rows.notes .row:not(.sel) .cell.notes{grid-row:5; padding-top:0}
#rows.notes .row:not(.sel) .vtext{grid-column:3; grid-row:1}
#rows.notes .row:not(.sel) .rside{grid-column:3; grid-row:2}
#rows.notes .row:not(.sel) .badges{grid-column:3; grid-row:3}
#rows.notes .row:not(.sel) :is(.vtext,.rside,.badges){justify-self:end; align-self:start; min-width:0;
  max-width:calc(100% - 16px); margin:0 12px 4px 4px}
#rows.notes .row:not(.sel) .cell.mid canvas.thumb{grid-row:1; width:auto !important; height:auto !important;
  max-width:min(96px, max(28cqw, 48px), calc(var(--tw, 108px) + 2px)); max-height:64px}
.row:not(.sel) :is(textarea.src,textarea.tl){border-color:transparent}
.row:not(.sel) textarea.tl{min-height:0}
/* the English as one paragraph: a textarea draws a stored line break whatever its white-space, so an unselected
   Translate row reads its twin (.tlr, syncTwin: each break a faint ↵), drawn with the field's box and type. The field
   lies over it, transparent and click-through, so it keeps its place in the focus order and its name, and a press
   lands on the twin (pressRow). A field that kept the focus while its row was let go (Alt+H, a box clicked on the page) shows
   itself in the twin's place, so what is typed is seen */
.tlwrap{position:relative}
.tlr{min-height:34px; padding:5px 8px; border:1px solid transparent; font-size:15px; line-height:22px;
  color:var(--ink); white-space:pre-wrap; overflow-wrap:break-word; cursor:text}
#rows.notes .row:not(.sel) .tlwrap > textarea.tl:not(:focus){position:absolute; inset:0; height:100% !important; opacity:0;
  pointer-events:none}
#rows.notes .row:not(.sel) .tlwrap:has(> textarea.tl:focus) > .tlr{display:none}
.row.locked .tlr{color:var(--ink2); cursor:default}
.tlr .tbrk{color:var(--ink3)}
/* its score when flagged (syncRowFlag shows .badges only then): the ≠ re-read badge is the selected row's (text
   only) */
.row:not(.sel) .badges{height:auto}
.row:not(.sel) .badges::before{content:none}
.row:not(.sel) .badges .badge:not(.bad){display:none}
/* the status chip: "Translated · mika" in the verdict's ink on its tint, one line, cut at the name; Translated's
   frame is dashed, as its tag's ring is. The badge's give-way flex parts read inline here, in their order; "by"
   (.vby) gives way to the dot */
.row:not(.sel) .vtext{display:block; flex:none; height:auto; overflow:visible; contain:none}
.row:not(.sel) .vtext::before,.row:not(.sel) .vtext .vlead::before{content:none}
.row:not(.sel) .vtext > .badge.review{
  display:block; height:auto; max-width:min(180px, 100%); margin:0; padding:0 8px; overflow:hidden; text-overflow:ellipsis;
  white-space:nowrap; border:1px solid color-mix(in srgb, currentColor 35%, transparent); background:var(--paper);
  font-size:12px; line-height:16px;
}
.row:not(.sel) .vtext > .badge.review.final{background:var(--final-bg)}
.row:not(.sel) .vtext > .badge.review.edited{background:var(--edited-bg)}
.row:not(.sel) .vtext > .badge.review.commit{background:var(--commit-bg); border-style:dashed;
  border-color:color-mix(in srgb, currentColor 60%, transparent)}
.row:not(.sel) .vtext > .badge.review *{display:inline; height:auto; overflow:visible}
.row:not(.sel) .vtext > .badge.review .vby{display:none}
.row:not(.sel) .vtext .vword::after{content:"· "}
/* beside the selected row's stamp, which names the verdict, the text is who gave it: "by mika" */
.row.sel .vtext .vword{display:none}
.rside{display:flex; flex-direction:column; align-items:flex-end; gap:4px; max-width:100%}
.tchip{display:inline-flex; align-items:center; gap:3px; height:18px; padding:0 6px; border-radius:999px;
  font-size:12px; line-height:16px; color:var(--blue); font-variant-numeric:tabular-nums; white-space:nowrap}
.tchip .cglyph{width:12px; height:12px; vertical-align:0}
.tchip.me{background:var(--blue); color:#fff}
.tchip .tcme{margin-left:3px; font-weight:600}
.rinfo{max-width:100%; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; font-size:12px; line-height:16px;
  color:var(--ink2)}
/* the narrow rows column (1280 with the panel open): the head tightens its gaps (the verdict's text gives way to
   the room beside the stamp, not to this width: .verdict below) */
@container rows (max-width:560px){
  .rowhead{gap:6px}
  /* an unselected row's facts under its English, on one wrapping line each, so the English keeps the width */
  #rows.notes .row:not(.sel){grid-template-columns:var(--midw) minmax(0,1fr)}
  #rows.notes .row:not(.sel) .cell.right{grid-row:1}
  #rows.notes .row:not(.sel) .vtext{grid-column:2; grid-row:2}
  #rows.notes .row:not(.sel) .rside{grid-column:2; grid-row:3; flex-flow:row wrap; align-items:center; gap:2px 8px}
  #rows.notes .row:not(.sel) .badges{grid-column:2; grid-row:4}
  #rows.notes .row:not(.sel) :is(.vtext,.rside,.badges){justify-self:start; margin:2px 12px 0}
}

/* typeset mode: no source column. One grid: the tag over the panel box | the crop | the English (and a noted
   block's notes) | the block actions over the stamp. The head (and its verdict) and the source cell lend their
   parts to it */
/* column 1 is 40 px at least: the panel box (40) is wider than a two-digit tag (31.3), and every row's crop track
   and English start at one x (the crop is centred in its track) */
#rows.nosrc .row{grid-template-columns:minmax(40px, auto) 150px minmax(0,1fr) auto; grid-template-rows:auto auto 1fr; column-gap:12px; padding:10px 12px 10px 14px}
#rows.nosrc .rowhead,#rows.nosrc .verdict,#rows.nosrc .cell.mid,#rows.nosrc .srcwrap{display:contents}
/* the tag and the panel box share the head column's one cell, the box 10 px under the tag's tail (as a pair, never
   pushed apart by the English's height) */
#rows.nosrc .rowhead .num{grid-column:1; grid-row:1 / -1; align-self:start; justify-self:start}
#rows.nosrc .rowhead .pnum{grid-column:1; grid-row:1 / -1; align-self:start; margin-top:40px}
/* a crop at its drawn size (drawThumbs: at most 110 x 170 CSS px, --tw its width) inside 150 x 140 at any
   devicePixelRatio: the autos let max-height keep its shape, and the width cap (--tw and the 1-px frame) keeps a 2x
   bitmap from showing bigger */
#rows.nosrc canvas.thumb{grid-column:2; grid-row:1 / span 3; justify-self:center; align-self:start;
  width:auto !important; height:auto !important; max-width:min(150px, calc(var(--tw, 148px) + 2px)); max-height:140px}
#rows.nosrc .cell.right{grid-column:3; grid-row:1 / span 2; padding:0}
/* typeset: the thread cell under the English (the selected row's, or an unsent message's mark: applyThreadMode) */
#rows.nosrc .cell.notes{grid-column:3; grid-row:3; padding:6px 0 0}
/* the folded thread: its count as a quiet chip that opens it ("2 messages") */
button.tfold{height:24px; padding:0 8px; border:1px solid var(--frame); border-radius:999px; background:var(--paper);
  color:var(--blue); font-size:12px; font-variant-numeric:tabular-nums}
button.tfold:hover:not(:disabled){background:var(--wash)}
button.tfold.me{font-weight:600}
/* the opened thread (applyThreadMode's .wide) spans the row under the crop and the English, and Fold sits at its head */
#rows.nosrc .cell.notes.wide{grid-column:1 / -1; grid-row:4}
button.tunfold{display:block; height:24px; margin:0 0 4px auto; padding:0 8px; font-size:12px; color:var(--ink2)}
/* an unselected typeset row: its facts where the selected row's actions and stamp stand, in a column of one width,
   so every such row's English is one width too, and a crop at most 64 px high (decision 0060) */
#rows.nosrc .row:not(.sel){grid-template-columns:minmax(40px, auto) 150px minmax(0,1fr) 132px}
#rows.nosrc .row:not(.sel) :is(.vtext,.rside){min-width:0; max-width:100%}
#rows.nosrc .row:not(.sel) .vtext{grid-column:4; grid-row:1; justify-self:end; align-self:start}
#rows.nosrc .row:not(.sel) .rside{grid-column:4; grid-row:2; justify-self:end; align-self:start; margin-top:4px}
#rows.nosrc .row:not(.sel) canvas.thumb{max-height:64px}
#rows.nosrc .rtools{grid-column:4; grid-row:1; justify-self:end; align-self:start; margin:0}
#rows.nosrc .rowhead .rbtns{grid-column:4; grid-row:2; justify-self:end; align-self:start; margin:8px 0 0}
/* a narrow typeset column (1280 with the panel open): a smaller crop, the actions under the English */
@container rows (max-width:560px){
  #rows.nosrc .row{grid-template-columns:minmax(40px, auto) 96px minmax(0,1fr); grid-template-rows:auto auto auto 1fr}
  #rows.nosrc canvas.thumb{max-width:min(96px, calc(var(--tw, 94px) + 2px)); max-height:120px; grid-row:1 / span 4}
  #rows.nosrc .cell.right{grid-row:1}
  #rows.nosrc .rtools{grid-column:3; grid-row:2; justify-self:start; margin:6px 0 0 -5px}
  #rows.nosrc .rowhead .rbtns{grid-column:3; grid-row:3; justify-self:start; margin:4px 0 0}
  #rows.nosrc .cell.notes{grid-row:4}
  #rows.nosrc .cell.notes.wide{grid-row:5}
  #rows.nosrc .row:not(.sel){grid-template-columns:minmax(40px, auto) 96px minmax(0,1fr)}
  #rows.nosrc .row:not(.sel) .vtext{grid-column:3; grid-row:2; justify-self:start; margin-top:4px}
  #rows.nosrc .row:not(.sel) .rside{grid-column:3; grid-row:3; justify-self:start; align-items:flex-start}
}
/* one status encoding, no row tints: every row lies on paper, and a verdict (applyRowStatus, every mode) is a 3-px
   edge down the left in its ink, its worded chip and the tag's ring (--tagv). A flagged block's tag is red pencil
   while it has no verdict (syncRowFlag's rule): a verdict's ring replaces it */
.row:has(.badge.flag.bad):not(.sel,.st-final,.st-edited,.st-commit){--tagc:var(--red)}
.row.st-final{--tagv:var(--final); box-shadow:inset 3px 0 0 var(--final)}
.row.st-edited{--tagv:var(--edited); box-shadow:inset 3px 0 0 var(--edited)}
.row.st-commit{--tagv:var(--commit); box-shadow:inset 3px 0 0 var(--commit)}
/* the selection is the only tinted row: the warm wash, the orange tag and the orange edge. Written after the
   verdicts at the same (0,2,0), so on a selected row the selection's edge wins; its verdict still shows as the
   stamp's filled segment, the tag's ring and, for Final, the lock and the grey English. Pinned by browser_test.py */
.row.sel{--tint:var(--sel-wash); --tagc:var(--sel); box-shadow:inset 3px 0 0 var(--sel)}
/* ---- the manual order (05_wiring.js startRowDrag, Translate only). The grip is the head's band inside the row's
   14-px left padding: no column and no layout shift. 40 px tall; the .rtools line lies under the head, right of
   the crop (under it in a narrow source cell). Its 2x3 dots of 2 px start at x 4, so the 3-px selection or verdict
   edge always shows, and the middle pair sits on the tag's centre line (8 + 13); #7A848D is 3.8:1 on paper. Hover:
   graphite dots on a --wash from x 3. No transition anywhere in the gesture ---- */
.grip{position:absolute; left:0; top:0; width:12px; height:40px; cursor:grab; touch-action:none; color:#7A848D}
.grip::before{
  content:""; position:absolute; left:4px; top:15px; width:6px; height:10px;
  background:radial-gradient(circle at 1px 1px, currentColor 1px, transparent 1.3px) 0 0/4px 4px;
}
.grip:hover{color:var(--ink2); background:linear-gradient(90deg, transparent 3px, var(--wash) 3px)}
body.rowdrag,body.rowdrag *{cursor:grabbing !important; user-select:none}
/* the lifted row stays in place as a ghost: its tint and bar are the row's own, so they stay at full strength;
   its cells, grip's dots and the head's tag, panel and speaker fade to .7 under a dashed selection outline. Every
   enabled ghost text keeps 3:1 on the selection's wash (the placeholders, under it at rest too, and the disabled
   buttons aside): graphite is 3.15:1 at worst, the orange tag's numeral 3.5:1. What would fall under it at .7 stays
   whole: the glossary chips' blue pencil on --wash (2.9:1), and the head's badges and verdict (the ≠ badge, Flagged, the verdict's text, the stamp's keycaps) */
.row.lifting > :is(.cell.right,.cell.notes,.grip),.row.lifting > .cell.mid > :not(.chips){opacity:.7}
.row.lifting > .rowhead > :not(.badges,.verdict){opacity:.7}
.row.lifting{outline:1.5px dashed var(--sel); outline-offset:-2px}
/* the drop line: a static 2-px orange line, the selection bar turned 90 degrees, just inside the row's
   hairline - on the visible row above the gap, or on the first visible row for the top slot. Never on the
   lifted (selected) row: the slots next to it change nothing and get no line */
.row.drop-after{box-shadow:inset 0 -2px 0 var(--sel)}
.row.drop-before{box-shadow:inset 0 2px 0 var(--sel)}
/* the paper chip that follows the pointer (on document.body, never in #rows): the orange balloon with the
   block's number, "→ k" while a line shows, the English on one line (every whitespace run one space, as ws_norm
   does; the Japanese the same way if there is no English), cut by the ellipsis */
.rowchip{
  position:fixed; z-index:40; pointer-events:none; display:flex; align-items:center; gap:6px;
  height:30px; max-width:280px; padding:0 10px 0 6px; background:var(--paper); border-radius:3px;
  box-shadow:var(--lift),0 0 0 1px rgba(16,48,64,.12); font:13px/18px var(--font); color:var(--ink);
}
.rowchip .rcnum{
  position:relative; flex:0 0 auto; min-width:20px; height:20px; padding:0 5px;
  font:700 12px/20px var(--font); font-variant-numeric:tabular-nums; text-align:center;
  background:var(--sel); color:var(--ink); border-radius:10px 10px 10px 0;
}
.rowchip .rcnum::after{
  content:""; position:absolute; left:0; top:calc(100% - 1px);
  border-top:5px solid var(--sel); border-right:4px solid transparent;
}
.rowchip .rcto{flex:0 0 auto; color:var(--ink2); font-variant-numeric:tabular-nums; white-space:nowrap}
.rowchip .rcto:empty{display:none}
.rowchip .rctxt{min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap}
.rowchip .rctxt.ja{font-family:var(--font-ja); font-size:14px}
/* the balloon tag at the row's head: the page tag's twin and its grammar - --box-ink with a white numeral, red
   pencil flagged, the orange fill (ink numeral) selected; a verdict outlines it in the verdict ink (Translated
   dashed), and a selected verdicted row keeps the ring outside the fill. Its tail hangs into the head's margin,
   from a square corner and 1 px inside the balloon. The numeral is the UI face in bold, tabular */
.rowhead .num{
  position:relative; flex:0 0 auto; min-width:26px; height:24px; padding:0 7px;
  font:700 15px/24px var(--font); font-variant-numeric:tabular-nums; text-align:center;
  background:var(--tagc); color:#fff; border-radius:12px 12px 12px 0;
}
.rowhead .num::after{
  content:""; position:absolute; left:0; top:calc(100% - 1px);
  border-top:6px solid var(--tagc); border-right:5px solid transparent;
}
.row:is(.st-final,.st-edited,.st-commit):not(.sel) .rowhead .num{
  background:var(--paper); color:var(--tagv); border:1.5px solid var(--tagv); line-height:21px; padding:0 5.5px;
}
.row.st-commit:not(.sel) .rowhead .num{border-style:dashed}
.row:is(.st-final,.st-edited,.st-commit):not(.sel) .rowhead .num::after{left:-1.5px; top:calc(100% + .5px); border-top-color:var(--tagv)}
.row.sel .rowhead .num{color:var(--ink)}
.row.sel:is(.st-final,.st-edited,.st-commit) .rowhead .num{box-shadow:0 0 0 1.5px var(--paper), 0 0 0 3px var(--tagv)}
/* hover pairing (pairHot): the row under the pointer rings its tag in --box, as its box does on the page */
.row.hot .rowhead .num{outline:2px solid var(--box); outline-offset:1px}
/* the panel box sits next to the block number in every mode; it is page-level, not a block index. Wide enough for
   its placeholder, "panel", so it names itself as the speaker box does */
.rowhead .pnum{
  flex:0 0 auto; width:40px; height:24px; padding:0 2px; text-align:center;
  font-size:13px; line-height:22px; font-variant-numeric:tabular-nums; color:var(--ink);
  background:var(--paper); border:1px solid var(--frame); border-radius:3px;
}
/* the speaker box, after the panel box (hidden in Typeset); its large flex-shrink makes it give way to its 44-px
   floor before the chips do (the verdict's text gives way before it: it asks for no room, see .verdict). Past that
   the badges go (see .badges), they never spill */
.rowhead .spk{
  flex:0 200 96px; min-width:44px; height:24px; padding:0 6px; font-size:12.5px; line-height:22px;
  color:var(--ink); background:var(--paper); border:1px solid var(--frame); border-radius:3px;
}
/* "vertical, font 24" stays in the DOM (updateRow writes it, typeset_browser_test reads it) and in no mode's view */
.rowhead .meta{display:none}
.srcwrap{display:flex; gap:8px; align-items:flex-start}
.thumb{
  flex:0 0 auto; background:var(--paper); border:1px solid var(--frame);
  border-radius:0; display:block; max-width:110px;
}
.thumb.clickable{cursor:zoom-in}

/* ---- the head's facts over the source: an open flag's "Flagged 0.62" and the ≠ badge (who gave the verdict is
   the verdict's, .verdict below; renderBadges keeps any other score in the tag's title). One 20-px line: a badge the line cannot hold wraps onto a second line the clip
   hides, so it goes whole (≠ before the confidence). The 0-wide, 20-px ::before leads the line (its gap cancelled),
   so the first badge is never the line's first item and can go too (a flex line's first item never wraps; an
   empty one would make line 1 0 px tall and show line 2). It asks for its badges and never grows (the room left
   is the verdict's). Its ::after is slack after them (1 px and its gap): while the speaker box gives way (its
   flex-shrink is 200) the head's shrink takes a sub-pixel share from .badges, and the slack goes first, so no badge
   goes before the speaker box is at its floor ---- */
.badges{
  display:flex; flex:0 1 auto; flex-wrap:wrap; align-content:flex-start; height:20px; overflow:hidden;
  align-items:center; gap:6px; min-width:0;
}
.badges::before{content:""; flex:0 0 0; height:20px; margin-right:-6px}
.badges::after{content:""; flex:0 0 1px}
.badge{
  border-radius:999px; padding:0 8px; font-size:12px; line-height:18px;
  border:1px solid transparent; white-space:nowrap; font-variant-numeric:tabular-nums; flex:0 0 auto;
}
/* "OCR 1.00": caption graphite, meta not a state; "Flagged 0.62": red pencil on blush; ≠ amber */
.badge.plain{background:transparent; color:var(--ink2); padding:0 2px}
.badge.bad{background:var(--red-bg); color:var(--red); border-color:#F0B7B2}
.badge.warn{background:var(--amber-bg); color:var(--amber)}
/* the review badge names who ("Final by carol") as quiet text in the verdict's ink, no fill and no ring (the tag's
   ring and the stamp say the verdict): inline in the OCR review list's .revitem, laid out in the row head by .vtext
   (below). A value the client does not know stays neutral graphite, never red */
.badge.review{color:var(--ink2); background:transparent; border-color:transparent; padding:0 2px}
.badge.review.final{color:var(--final)}
.badge.review.edited{color:var(--edited)}
.badge.review.commit{color:var(--commit)}
.badge.mismatch{font-size:12px; padding:0 6px}
/* ---- the verdict (.verdict: who gave it, .vtext, then the stamp, .rbtns; only More, .rmore, comes after it) keeps
   to the English column wherever #divM is. It is the head's one growing item, at its right end before More, and
   never wider than the English field under it less More's room: the head's content box less --midw, plus 2 (the
   head's content starts 14 px into the row, the field at --midw + 12), less 36 (More's 28 and the head's 8-px gap),
   so its text never starts over the source. The head's left part still runs on
   over the English when it needs the room: the verdict asks for the stamp alone (.vtext is size-contained). Its
   floor is the stamp (min-width:max-content; the automatic minimum would be clamped by the cap), and a floor beats
   a cap: where the English field is narrower than the stamp (a selected row on rows under about 388 px, where the
   380 rule's tighter stamp is 185 px; at the narrowest rows, about 334 px, whose field is about 140, an unselected
   one too, 143 px) the stamp crosses #divM, still ends inside the head,
   and its text is gone. No clip on .verdict, and .vtext's clip holds only the text, so the stamp's focus ring stays
   whole. Typeset lends the stamp to the row's grid (display:contents, above) ---- */
.verdict{
  display:flex; align-items:center; flex:1 0 auto; margin-left:auto;
  min-width:max-content; max-width:calc(100% - var(--midw) - 34px);
}
/* the room beside the stamp, one 20-px line: the review badge fills it (the head's gap from the stamp) and, under
   its 3.5em, wraps onto the clipped second line and goes whole, never a stray glyph (the 20-px ::before holds
   line 1). applyRowMode hides it outside Translate */
.vtext{
  display:flex; flex:1 1 0; min-width:0; flex-wrap:wrap; align-content:flex-start; align-items:center;
  height:20px; overflow:hidden; contain:inline-size;
}
.vtext::before{content:""; flex:0 0 0; height:20px}
/* in the head the review badge gives way by what fits beside the stamp, no px threshold. Its name (.vname, or an
   answered commit's .vwho: the name and its ", answered") sits by the stamp and its lead (.vlead: the 🔒 and the
   verdict word with its " by ") left of it (row-reverse; the lead's order 1). The lead joins the line only with
   1.75em (the 🔒's room) beside the name, and inside it the word wraps onto a clipped second line when the whole
   sentence does not fit, so the name reads whole with its 🔒 ("🔒 jame"). With less room the lead goes, then the
   tail wraps out of .vwho whole ("alice"), then the name cuts with the ellipsis. Its spans keep their spaces (pre: a
   flex item drops a trailing space); the lead's 18-px ::before keeps the word from being its line's first item,
   which never wraps (the name leads .vwho's line, flex-end keeps it by the stamp) */
.vtext > .badge.review{
  display:flex; flex-direction:row-reverse; flex-wrap:wrap; align-content:flex-start; align-items:flex-start;
  flex:1 1 3.5em; min-width:0; height:20px; overflow:hidden; margin-right:8px;
}
.vtext .vname{flex:0 1 auto; min-width:0; overflow:hidden; text-overflow:ellipsis}
.vtext .vwho{
  display:flex; flex-wrap:wrap; justify-content:flex-end; align-content:flex-start;
  flex:0 1 auto; min-width:0; height:18px; overflow:hidden;
}
.vtext .vtail{flex:0 0 auto; white-space:pre}
.vtext .vlead{
  order:1; display:flex; flex-wrap:wrap; justify-content:flex-end; align-content:flex-start;
  flex:1 1 1.75em; min-width:0; height:18px; overflow:hidden;
}
.vtext .vlead::before{content:""; flex:0 0 0; height:18px}
.vtext .vlead > span{flex:0 0 auto; white-space:pre}
/* the narrow rows column's 6-px head gap between the text and the stamp too (after the rule it overrides) */
@container rows (max-width:560px){ .vtext > .badge.review{margin-right:6px} }
/* the block actions: ghost words, never joined (the mode hides some of them one by one, and two suites pin their
   order). Delete is not one of them: it is the More menu's (#rowMorePop), behind the ghost ⋯ at the head line's end,
   so no red word sits among the actions a reviewer presses on every block */
.rtools{display:flex; flex-wrap:wrap; align-items:center; gap:2px}
.rtools button{height:24px; padding:0 6px; font-size:13px; line-height:22px; color:var(--ink2)}
.rowhead .rmore{flex:0 0 auto; width:28px; height:24px; padding:0; font-size:15px; line-height:22px; color:var(--ink2)}
/* the row's MT (12_mt.js) under the English, a ghost word like the block actions and set in by their -5 px; while
   its press runs it reads "MT…" in blue pencil, the machine's voice (aria-busy), static. Final dims it as it dims
   OCR and Delete (disabled) */
.cell.right .mtbtn{height:24px; margin:4px 0 0 -5px; padding:0 6px; font-size:13px; line-height:22px;
  color:var(--ink2)}
.cell.right .mtbtn[aria-busy=true]{color:var(--blue)}
/* the busy label ("MT…") is a few px wider than the idle one: the row's button reserves the busy width, so a
   press moves nothing after it. It holds no number (a fixed reserve of 44 px did, with padding and border: the busy
   word measured 42.02, then 43 under Edge 155's text): it carries a hidden copy of its busy label, so the button is
   as wide as the wider of its two words whatever the font's advance (a flex column, which a button still sizes to
   fit; the copy has no height and `visibility:hidden` keeps it out of the name). The copy is mtBusy's label
   (12_mt.js): change both together. MT page ("MT page…" in the Page actions menu, whose width is its own) reads
   blue in its press as the row's does, and so does the menu's button, so a closed menu still shows it running */
.cell.right .mtbtn{display:flex; flex-direction:column; align-items:center; justify-content:center}
.cell.right .mtbtn::after{content:"MT…"; height:0; overflow:hidden; visibility:hidden}
#btnMtPage[aria-busy=true],body:has(#btnMtPage[aria-busy=true]) #btnPageActs{color:var(--blue)}
/* the verdict stamp at the verdict's (and the head's) right end: Translated | Reviewed | Final as one joined control
   (none of them is ever hidden on its own, so the first/last-child radii hold). At rest each word in its verdict
   ink on paper; the chosen one is filled with its ink and lands at once. Read left to right it is the workflow.
   The selected row shows its keys (1 2 3: Alt+digit) as caps inside the words. 12-px words and 4-px padding:
   "Translated | Reviewed" is longer than the "Commit | Edited" it replaced (decision 0033), and this keeps the stamp
   about as wide as before, so the review text beside it keeps its room (headed_ui's real-scrollbar checks) */
.rbtns{display:flex; flex-wrap:nowrap; flex:0 0 auto; border:1px solid var(--frame); border-radius:6px}
.rbtns button{
  height:24px; padding:0 4px; font-size:12px; line-height:20px; background:var(--paper);
  border:0; border-radius:0; margin:0;
}
.rbtns button + button{border-left:1px solid var(--frame)}
.rbtns button:first-child{border-radius:5px 0 0 5px}
.rbtns button:last-child{border-radius:0 5px 5px 0}
.rbtns button[data-status=commit]{color:var(--commit)}
.rbtns button[data-status=edited]{color:var(--edited)}
.rbtns button[data-status=final]{color:var(--final)}
.rbtns button:not(.on):hover:not(:disabled){background:var(--wash)}
.rbtns button[data-status=commit]:not(.on):hover:not(:disabled){color:var(--commit)}
.rbtns button[data-status=edited]:not(.on):hover:not(:disabled){color:var(--edited)}
.rbtns button[data-status=final]:not(.on):hover:not(:disabled){color:var(--final)}
.rbtns button.on{box-shadow:none; color:#fff}
.rbtns button[data-status=commit].on{background:var(--commit)}
.rbtns button[data-status=edited].on{background:var(--edited)}
.rbtns button[data-status=final].on{background:var(--final)}
.rbtns button.on:hover:not(:disabled){color:#fff}
.rbtns button[data-status=commit].on:hover:not(:disabled){background:var(--commit)}
.rbtns button[data-status=edited].on:hover:not(:disabled){background:var(--edited)}
.rbtns button[data-status=final].on:hover:not(:disabled){background:var(--final)}
/* the stamp's keycaps are the selected row's and the Review queue's selected item's only (the keys act on the
   selected block), while Alt is held (the keycap rule of the topbar section); a filled button's cap is white on its
   fill inside a 1-px white ring */
.row.sel .rbtns button[data-kbd]::after,.cacts .rbtns button[data-kbd]::after{padding:0 3px}
.row.sel .rbtns button.on[data-kbd]::after,.cacts .rbtns button.on[data-kbd]::after{
  background:var(--commit); box-shadow:inset 0 0 0 1px rgba(255,255,255,.5); color:#fff;
}
.row.sel .rbtns button[data-status=edited].on[data-kbd]::after,
.cacts .rbtns button[data-status=edited].on[data-kbd]::after{background:var(--edited)}
.row.sel .rbtns button[data-status=final].on[data-kbd]::after,
.cacts .rbtns button[data-status=final].on[data-kbd]::after{background:var(--final)}
/* a narrow head (at most 460 px): tighter chips and a 36-px speaker floor, so a row still holds its whole
   "Flagged 0.62" (its gaps are 6 already: such a head is a row of at most 486 px
   (the query measures the head's content box, 26 px of padding less), under the rows rule; a container never
   styles itself, so a .rowhead rule here could not match) */
@container rowhead (max-width:460px){
  .rowhead .spk{min-width:36px}
  .badge.bad,.badge.warn{padding:0 4px}
}
/* the narrowest head (the rows at the page column's W - 340 cap, the head the whole row, about 334 px):
   the stamp tightens its padding ("Translated | Reviewed | Final" is the longest it carries) and the speaker box
   gives way below its floor, so the stamp still ends inside the head, a scrollbar's 15 px and a two-digit tag too
   (the keycaps float and take no room). The English field (about 140 px) is narrower than the stamp at the
   narrowest (150 px), so .verdict's floor beats its cap: the review text is gone and the stamp crosses #divM. After
   the .spk and .rbtns rules, which it overrides */
@container rowhead (max-width:380px){
  .rowhead .spk{min-width:14px}
  .rbtns button{padding:0 3px}
}
/* the text fields: ink on the row's tint, a 1-px frame (grow() adds its 2 px), the English 15/22, the
   Japanese 16/24 in the CJK stack (the base rules), the note a pencil note in graphite italic */
textarea.src,textarea.tl{
  width:100%; min-width:0; resize:none; overflow:hidden;
  border:1px solid var(--frame); border-radius:3px; padding:5px 8px;
  line-height:1.5; color:var(--ink);
}
.srcwrap textarea.src{flex:1 1 auto; min-width:0}
textarea.src[lang=ja]{font-size:16px; line-height:24px}
textarea.tl{min-height:58px; font-size:15px; line-height:22px}
/* the thread (20_thread.js): the messages oldest first, in a list that keeps to its foot and scrolls past 190 px
   (pinThread);
   each one a head (the voice chip, who, when: the chat's .chead) over its text, upright in ink, since a message is
   read as the work is; italic is the system's lines only ("(edited)"). The voice chips are neutral (a role, not a
   status). The composer line is the voice switch ("as Translator ⇄"), the box (5 lines at most, 102 px, then it
   scrolls) and Send, on the selected row (applyThreadMode). An open suggestion is not in the list: its card under
   the English is its one display. The list scrolls only up and down: the texts wrap anywhere and a head too wide for
   its column wraps its time (.twho), so nothing needs a sideways scrollbar (which a narrow column would show for a
   sub-pixel) */
.tlist{max-height:190px; overflow:hidden auto}
.tmsgs{display:flex; flex-direction:column; gap:6px}
.tlist:has(.titem){margin-bottom:6px}
.thead{display:flex; flex-wrap:wrap; align-items:baseline; gap:0 6px; min-width:0; font-size:12px; line-height:16px;
  color:var(--ink2)}
.tvoice{flex:0 0 auto; padding:0 4px; border-radius:3px; font-size:12px; font-weight:600}
/* a voice is a role, not a status: a neutral graphite outline, so blue and green mean a verdict and nothing else */
.tvoice.v-tr,.tvoice.v-ed{border:1px solid var(--ink2); color:var(--ink2); background:none}
/* the author is never cut to make room: up to about 16 characters it keeps its width (a longer name ends in an
   ellipsis, whole in its title, whoSpan); the time gives way first (it wraps under the head here, it shrinks in a
   card's one-line .shead), then the voice chip */
.twho{flex:0 0 auto; max-width:16ch; font-weight:600; color:var(--ink); overflow:hidden; text-overflow:ellipsis;
  white-space:nowrap}
.twhen{flex:0 0 auto; font-variant-numeric:tabular-nums}
.shead .twhen{flex:0 100 auto; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap}
.shead .tvoice{flex-shrink:1; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap}
.ttext{font-size:13px; line-height:18px; color:var(--ink); white-space:pre-wrap; overflow-wrap:anywhere}
.tcompw{display:flex; align-items:flex-end; gap:4px}
.cell.notes textarea.tcomp{
  flex:1 1 auto; width:100%; min-width:0; resize:none; overflow-y:auto; max-height:102px;
  border:1px solid var(--frame); border-radius:3px; padding:5px 8px;
  font-size:13px; line-height:18px; color:var(--ink);
}
.cell.notes textarea.tcomp::placeholder{color:var(--ink3)}
/* the voice switch, at the composer's left: a ghost word and a swap glyph, neutral in both voices */
button.vsw{flex:0 0 auto; display:inline-flex; align-items:center; gap:4px; height:30px; padding:0 7px;
  border:1px solid var(--frame); border-radius:3px; background:var(--paper); color:var(--ink2); font-size:12px;
  white-space:nowrap}
button.vsw .vswg{color:var(--ink3)}
button.tsend{flex:0 0 auto; height:30px; padding:0 10px}
.tmark{margin-top:3px; font-size:12px; line-height:16px; color:var(--amber)}
/* suggested edits (22_suggest.js, decision 0045). Under the English: MT and Suggest (a ghost word like MT), then
   the strip, one card per open suggestion with its word diff: what goes struck through in graphite (<del>), what
   comes in ink at 600 on the amber wash (<ins>), so the two read apart without one ink for both. The proposer's box
   (textarea.sug) sits in its own card, so the English field never shows a proposal. In the thread a suggestion is
   an item with an amber rule down its left */
.enacts{display:flex; flex-wrap:wrap; align-items:center; gap:2px}
.cell.right .sugbtn{height:24px; margin:4px 0 0; padding:0 6px; font-size:13px; line-height:22px; color:var(--ink2)}
.sugs{display:flex; flex-direction:column; gap:6px; margin-top:6px}
.sugs:empty{display:none}
.sugcard{padding:5px 7px; border:1px solid var(--frame); border-left:3px solid var(--amber); border-radius:3px;
  background:var(--paper)}
.shead{display:flex; align-items:baseline; gap:6px; min-width:0; margin-bottom:3px; font-size:12px; line-height:16px;
  color:var(--ink2)}
.sstate{flex:0 1 auto; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
  color:var(--amber); font-weight:600}
.tstate{font-size:12px; line-height:16px; color:var(--amber); font-weight:600}
.sugcard[data-state=outdated] .sstate,.titem[data-state=outdated] .tstate,.titem[data-state=rejected] .tstate,
.titem[data-state=changed] .tstate{color:var(--ink2)}
.titem[data-state=accepted] .tstate{color:var(--edited)}
.sdiff{font-size:15px; line-height:22px; white-space:pre-wrap; overflow-wrap:anywhere; color:var(--ink)}
.titem .sdiff{font-size:13px; line-height:18px; font-style:normal}
.sdiff del{color:var(--ink2); text-decoration:line-through; text-decoration-thickness:1.5px}
.sdiff ins{color:var(--ink); background:var(--amber-bg); text-decoration:none; font-weight:600}
.sdiff del + ins{margin-left:2px}
.sugcard[data-state=outdated] .sdiff{color:var(--ink2)}
.sbtns{display:flex; flex-wrap:wrap; gap:6px; margin-top:5px}
.sbtns button{height:24px; padding:0 9px; border:1px solid var(--frame); border-radius:3px; font-size:12px}
.sbtns button.sacc:not(:disabled){background:var(--edited); border-color:var(--edited); color:#fff}
.sbtns button.sprop:not(:disabled){border-color:var(--ink)}
.sfrozen,.shint{margin-top:4px; font-size:12px; line-height:16px; color:var(--ink2)}
.shint.err{color:var(--red)}
.sugcard textarea.sug{display:block; width:100%; resize:none; overflow-y:auto; max-height:160px; margin:2px 0 4px;
  padding:4px 6px; border:1px solid var(--amber); border-radius:3px; font-size:15px; line-height:22px;
  color:var(--ink); background:var(--paper)}
.titem.tsug{padding-left:6px; box-shadow:inset 2px 0 0 var(--amber)}
/* a notification's landing (24_notify.js notifyFlash): the message lit on the amber of "look here", fading out; with
   reduced motion it stays lit (the last rule) until the next landing or the thread's next rebuild */
.titem.flash{animation:tflash 1.6s ease-out}
@keyframes tflash{from{background:var(--amber-bg)} to{background:transparent}}
.tdone{margin-top:2px; font-size:12px; line-height:16px; color:var(--ink2)}
.shead .tdone{margin-top:0; white-space:nowrap}
/* a card this browser just resolved (Accepted or Rejected, with Undo): the rule and the words in the state's colour,
   the diff greyed */
.sugcard.done[data-state=accepted]{border-left-color:var(--edited)}
.sugcard.done[data-state=accepted] .sstate{color:var(--edited)}
.sugcard.done[data-state=rejected]{border-left-color:var(--ink2)}
.sugcard.done[data-state=rejected] .sstate,.sugcard.done .sdiff{color:var(--ink2)}
.titem button.treopen{height:24px; margin-top:3px; padding:0 8px; border:1px solid var(--frame); border-radius:3px;
  font-size:12px}
/* an own message's Edit and Delete (20_thread.js msgActs, decision 0049): over the head's right end on the row's
   tint, shown while the item is hovered or holds the focus. Transparent, not hidden, so Tab still reaches them; laid
   over the head, so a narrow column keeps the name. The delete's question is a line of its own under the text */
.titem{position:relative}
.tedited{font-size:12px; font-style:italic; color:var(--ink2); white-space:nowrap}
.tacts{position:absolute; top:0; right:0; display:flex; align-items:center; gap:4px; padding-left:6px;
  background:var(--tint); opacity:0; pointer-events:none}
.titem:hover .tacts,.titem:focus-within .tacts{opacity:1; pointer-events:auto}
.tacts.tq{position:static; flex-wrap:wrap; margin-top:3px; padding:0; font-size:12px; line-height:16px; opacity:1;
  pointer-events:auto}
.tacts button{padding:0 6px; border:1px solid var(--frame); border-radius:3px; font-size:12px;
  line-height:16px}
.tacts button.tdelok:not(:disabled){background:var(--red); border-color:var(--red); color:#fff}
.tconfq{color:var(--ink); font-weight:600}
.tedw textarea.tedit{display:block; width:100%; resize:none; overflow-y:auto; max-height:160px; margin-top:2px;
  padding:4px 6px; border:1px solid var(--blue); border-radius:3px; font-size:13px; line-height:18px;
  color:var(--ink); background:var(--paper)}
.tedhint{margin-top:2px; font-size:12px; line-height:16px; color:var(--ink2)}
/* the Review queue's message lines (.qnote): the note's graphite italic, two lines at most, the voice word upright
   before it */
.commititem .qnote{
  display:-webkit-box; -webkit-box-orient:vertical; -webkit-line-clamp:2; overflow:hidden;
  font-size:13px; line-height:18px; font-style:italic; color:var(--ink2);
  white-space:pre-wrap; overflow-wrap:break-word;
}
.nrole{font-style:normal; font-size:12px; line-height:16px; color:var(--ink2); margin-right:6px}
/* both texts at one tab stop, a wrapped line included: a hanging indent of the voice word's 68 px plus its 6-px
   margin */
.commititem .qnote{padding-left:74px; text-indent:-74px}
.commititem .qnote .nrole{display:inline-block; min-width:68px; text-indent:0}
/* focus: the base rules' field ring; the blue border is restated because the frames above beat
   textarea:focus-visible (later at its specificity, or more specific) */
textarea.src:focus,textarea.tl:focus,.cell.notes textarea.tcomp:focus{border-color:var(--blue)}
/* a block marked Final is text-locked (the server refuses the rest): its Japanese and English read as set copy,
   no frame and graphite text. Typesetting still works on it, and so does its thread - hence the two class
   names instead of a bare `textarea`. No background here: the tint is the field rule's alone */
.row.locked textarea.src,.row.locked textarea.tl{border-color:transparent; color:var(--ink2); cursor:default}
/* a desynced page's hold (applyDesyncHold sets .held): its composer, panel and speaker boxes take no typing until the
   reload, so they read as held - a dashed frame, graphite, no halo. A class, not :read-only: Typeset's panel box
   is read-only on its own account */
.row.held textarea.tcomp,.row.held input.pnum,.row.held input.spk{
  border-style:dashed; background:transparent; color:var(--ink2); cursor:not-allowed;
}
.row.held textarea.tcomp:focus,.row.held input.pnum:focus,.row.held input.spk:focus{
  box-shadow:none; outline:1px dashed var(--frame); border-color:var(--frame);
}
/* the only background of the three fields: the row's --tint (paper or the selection wash), so a field never
   stands out white inside the selected row. Keep it after every other textarea rule */
textarea.src,textarea.tl,textarea.tcomp{background:var(--tint)}
.chips{display:flex; flex-wrap:wrap; gap:4px}
.chips:empty{display:none}
/* the pill: `button.chip.gterm` is the `Glossary: <English>` chip under a source block, a button that opens its
   entry in the Glossary (gloOpenTerm); its hover is a frame, so the state's wash stays */
.chip{
  font-size:12px; line-height:18px; padding:0 8px; border-radius:999px; border:1px solid transparent;
}
.chip.gterm{background:var(--wash); color:var(--blue)}
button.chip.gterm:hover:not(:disabled){background:var(--wash); color:var(--blue); border-color:currentColor}
/* its state against the English (renderChips): amber while the English lacks the term, red while it has an avoided
   spelling; the word after the term says the same, so the colour is never the only sign */
.chip.gterm.gwarn{background:var(--amber-bg); color:var(--amber)}
.chip.gterm.gerr{background:var(--red-bg); color:var(--red)}
button.chip.gterm.gwarn:hover:not(:disabled){background:var(--amber-bg); color:var(--amber)}
button.chip.gterm.gerr:hover:not(:disabled){background:var(--red-bg); color:var(--red)}
/* in the source cell a long term can wrap the chip (a narrow cell): 9 px keeps a one-line 20-px chip the pill and
   makes a wrapped one a rounded label, never a two-line blob */
.cell.mid .chip.gterm{border-radius:9px}
.emptyrows{padding:18px 12px; color:var(--ink2); font-style:italic}

/* ---- side panel: a paper sheet laid on the desk beside the rows (a sheet shadow, no rule), headed by its own
   tabs: a 30-px tab line on the desk (the open tab a paper chip that runs into the paper below) and a 31-px
   context line on paper (#panelCtx). 30 + 31 = the pane head's 61 ---- */
#panel{
  flex:0 0 360px; min-width:0; background:var(--paper); position:relative; z-index:5;
  display:flex; flex-direction:column; overflow:hidden;
  box-shadow:var(--sheet);
}
.ptabs{
  display:flex; align-items:flex-end; gap:2px; flex:0 0 30px; height:30px; padding:0 4px 0 8px;
  background:var(--desk); color:var(--ink2);
}
/* the tabs (docked in #filters or here): the open one is a paper chip */
.ptablist{display:flex; align-items:flex-end; gap:2px; min-width:0}
.ptablist button{height:26px; padding:0 10px; border-radius:6px 6px 0 0; white-space:nowrap; color:var(--ink2)}
.ptabs .ptablist button:hover:not(:disabled){background:var(--desk-hover); color:var(--ink)}
/* the open panel's line holds four tabs in Translate beside ×, at the sides and size the rows head docks them at;
   should a badge push it over its 360 px, the line scrolls in itself (no bar) instead of running under × */
.ptabs .ptablist{overflow:auto hidden; scrollbar-width:none}
/* the open tab: paper, lifted, joined to the context line under it (2 px of it overlap the line) */
.ptablist button.on,.ptablist button.on:hover:not(:disabled){
  background:var(--paper); color:var(--ink); box-shadow:0 -1px 2px rgba(16,48,64,.16); position:relative;
}
.ptabs .ptablist button.on::after{content:""; position:absolute; left:0; right:0; bottom:-2px; height:2px; background:var(--paper)}
.ptablist button:focus-visible{outline-offset:-2px}
/* the title, shown only while the mode hides the open tab's opener (markPanelTab) */
#panelTitle:not(.vh){align-self:center; flex:0 1 auto; min-width:0; padding:0 8px 0 2px; font-weight:600;
  color:var(--ink); white-space:nowrap; overflow:hidden; text-overflow:ellipsis}
#btnPanelClose{margin-left:auto; align-self:center; width:26px; height:26px; padding:0; font-size:18px; line-height:1}
#btnPanelClose:hover:not(:disabled){background:var(--desk-hover); color:var(--ink)}
/* one line, 12 px, centred in its 31 px by the line box (a block, so the ellipsis applies to its text) */
#panelCtx{
  flex:0 0 31px; height:31px; padding:0 12px; font-size:12px; line-height:31px;
  color:var(--ink2); font-variant-numeric:tabular-nums; white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
}
/* a tab body with parts (Review, Reference): its part switch, then the shown part, its scroller */
.ptab{flex:1 1 auto; display:flex; flex-direction:column; min-height:0}
.ptab[hidden]{display:none}
/* the part switch, a joined control drawn as .scopeseg: the shown part a paper chip on the well */
.partseg{align-self:flex-start; height:26px; margin:2px 12px 4px; background:var(--well); border-color:var(--well)}
.partseg > button{padding:0 12px; color:var(--ink2); font-size:13px}
.partseg > button + button{border-left-color:transparent}
.partseg > button:hover:not(:disabled):not(.on){background:var(--desk-hover); color:var(--ink)}
.partseg > button.on{background:var(--paper); color:var(--ink); box-shadow:var(--lift)}
.partseg > button:focus-visible{outline:2px solid var(--ink); outline-offset:1px}
.pbody{flex:1 1 auto; overflow:auto; padding:4px 12px 12px; display:flex; flex-direction:column; gap:8px; min-height:0}
.pbody[hidden]{display:none}
.pbody > *{flex-shrink:0}   /* the tab scrolls; a short panel never squeezes a part under its content */
.opts{display:flex; flex-wrap:wrap; gap:4px 12px; color:var(--ink2); font-size:12.5px}
.opts label{display:flex; align-items:center; gap:5px}
/* a line of buttons; in the dialogs and the Typeset tab the primary (ink) keeps to its right end, in Search
   Find leads the line it starts */
.srowbtns{display:flex; flex-wrap:wrap; align-items:center; gap:6px}
.srowbtns button{min-height:28px; padding:0 12px}
.err{color:var(--red); font-size:12.5px; white-space:pre-wrap}
/* the panel lists (search, review, the Review queue, glossary, cast): lines of paper that take the wash on hover; the
   chosen one is marked like a selected row, an inset orange bar on the selection wash */
.list{display:flex; flex-direction:column; gap:1px; min-height:0; margin:0 -8px}   /* the words on the fields' edge */
.item{
  text-align:left; border:0; border-radius:3px; padding:5px 8px;
  background:none; cursor:pointer; display:block; width:100%; color:var(--ink);
  transition:color .12s, background-color .12s;
}
.item:hover:not(:disabled){background:var(--wash); color:var(--ink)}
.item.sel,.item.sel:hover:not(:disabled){
  background:var(--sel-wash); box-shadow:inset 3px 0 0 var(--sel); border-radius:0 3px 3px 0;
}
.item .loc{color:var(--ink2); font-size:12px; line-height:16px; font-variant-numeric:tabular-nums}
.item .snip{display:block; white-space:pre-wrap; word-break:break-word}
/* a match: the amber wash and an underline in the text's ink, so it reads on the selection wash and without colour */
.item mark{background:var(--amber-bg); color:inherit; padding:0 1px; border-radius:2px;
  text-decoration:underline 2px; text-underline-offset:2px}
/* a search result on one line: its block number, then the text from just before the match, ended with an ellipsis.
   The current one is marked without colour as well (a ▸ and a bold number) and shows up to two lines */
.sres{display:flex; align-items:baseline; gap:6px}
.sres .loc{flex:0 0 auto}
.sres .scur{flex:0 0 auto; width:8px; color:var(--ink); visibility:hidden}
.sres.sel .scur{visibility:visible}
.item.sres.sel .loc{color:var(--ink); font-weight:700}
.item.sres .snip{flex:1 1 0; min-width:0; white-space:nowrap; overflow:hidden; text-overflow:ellipsis}
.item.sres.sel .snip{white-space:normal; display:-webkit-box; -webkit-box-orient:vertical; -webkit-line-clamp:2}
/* the results' page groups (<details>), headed "p.025 · 6" with the disclosure triangle drawn as Check's */
.sgroup > summary{display:flex; align-items:baseline; gap:6px; padding:4px 8px; cursor:pointer; font-weight:600;
  color:var(--ink); font-variant-numeric:tabular-nums}
.sgroup > summary::before{content:"\25B8"; width:10px; color:var(--ink2); font-weight:400}
.sgroup[open] > summary::before{content:"\25BE"}
/* Find and Replace with: visible labels over their fields; ‹ 2 of 25 › beside Find */
.sfield{color:var(--ink2); font-size:12px; margin-bottom:-6px}
.sstep{display:flex; align-items:center; gap:2px; flex:0 0 auto}
.sstep button{min-width:24px; min-height:28px; padding:0 4px; font-size:16px; line-height:1}
#searchPos{min-width:48px; text-align:center; font-size:12px; color:var(--ink2); font-variant-numeric:tabular-nums}
/* Replace all's count, asked on the selection wash before anything changes */
.sask{display:flex; flex-direction:column; gap:6px; padding:8px 10px; border-radius:3px; background:var(--sel-wash);
  box-shadow:inset 3px 0 0 var(--sel); color:var(--ink)}
/* the filter row with a trailing button (glossary "Add term") */
.grow{display:flex; align-items:center; gap:6px}
.grow input{flex:1 1 auto; min-width:0}
.grow button{flex:0 0 auto; white-space:nowrap; min-height:28px}
/* a glossary row is a div: the whole row inserts the term (.gins), the pencil edits it (.gedit);
   both reset the global button chrome so the row still reads as one list line */
.gitem{display:flex; align-items:flex-start; gap:6px; width:100%; text-align:left}
.gins,.gedit{background:none; border:none; padding:0; border-radius:0; text-align:left}
.gins:hover:not(:disabled),.gedit:hover:not(:disabled){background:none; border-color:transparent}
.gins{flex:1 1 auto; min-width:0; color:var(--ink)}
.gedit{flex:0 0 auto; color:var(--ink2); opacity:.7; line-height:22px; padding:0 2px}
.gitem:hover .gedit,.gedit:focus{opacity:1}
.gitem .ja{font-size:15px; line-height:22px}
.gitem .en{color:var(--ink); font-weight:600}
.gitem .note{display:block; color:var(--ink2); font-size:12px; line-height:16px; white-space:pre-wrap}
/* the Check tab (19_check.js): one <details> per check, its head the severity pill, the name and the count; a finding
   is a list line that is not itself a button: its block buttons (.cjump, with Search's .loc and .snip) take the wash,
   and Ignore sits at its right like the glossary's pencil. An ignored finding (Show ignored) is faded */
/* the findings count with Check again, framed and unfilled, at its right; Show ignored at the line's end */
.ccount #checkCount{color:var(--ink); font-size:13px; font-variant-numeric:tabular-nums}
.ccount #checkCount:empty{display:none}
.ccount #btnCheckRun{border-color:var(--frame)}
.ccount .opts{margin-left:auto}
.cgroup{padding:0 8px}
.cgroup > summary{display:flex; align-items:baseline; gap:6px; padding:5px 0; cursor:pointer; font-weight:600; color:var(--ink)}
/* a flex summary drops the browser's disclosure triangle: draw it, pointing down while the check is open */
.cgroup > summary::before{content:"\25B8"; width:10px; color:var(--ink2); font-weight:400}
.cgroup[open] > summary::before{content:"\25BE"}
.cgroup .csev{font-size:12px; line-height:16px; padding:0 6px; border-radius:999px}
.cgroup.cerr .csev{background:var(--red-bg); color:var(--red)}
.cgroup.cwarn .csev{background:var(--amber-bg); color:var(--amber)}
.cgroup .ccnt{margin-left:auto; color:var(--ink2); font-weight:400; font-variant-numeric:tabular-nums}
.cgroup .creason{margin:0 0 4px}
.cgroup > .item{margin:0 -8px; width:calc(100% + 16px)}
.citem,.citem:hover:not(:disabled){cursor:default; background:none; padding:3px 4px}
.citem > .cwhat,.citem > .cwhy{padding:0 4px}
.citem.off{opacity:.85}   /* faded, its graphite still 4.6:1 on paper and the selection wash */
.citem .cline{display:flex; align-items:flex-start; gap:6px}
.citem .cjumps{flex:1 1 auto; min-width:0; display:flex; flex-direction:column; gap:1px}
.citem .cwhat,.citem .cwhy{display:block; font-size:12px; line-height:16px; color:var(--ink2); white-space:pre-wrap; word-break:break-word}
.cjump{display:flex; flex-wrap:wrap; align-items:baseline; column-gap:8px; width:100%; text-align:left; background:none;
  border:none; padding:2px 4px; border-radius:3px; color:var(--ink)}
.cjump:hover:not(:disabled){background:var(--wash); border-color:transparent}
.cjump.sel,.cjump.sel:hover:not(:disabled){background:var(--sel-wash); box-shadow:inset 3px 0 0 var(--sel); border-radius:0 3px 3px 0}
/* the subject on its own line; the place, then the text on one line with an ellipsis (checkSnip) */
.cjump .cwhat{flex:0 0 100%; color:var(--ink); font-weight:600}
.cjump .loc{flex:0 0 auto}
.cjump .snip{flex:1 1 0; min-width:0; white-space:nowrap; overflow:hidden; text-overflow:ellipsis}
.cign{flex:0 0 auto; min-height:24px; padding:0 8px; font-size:12px}
/* the style guide box (25_style.js) above the findings: a summary like a check's, a label and select per setting,
   and the rules as list lines with the findings' severity pill and Ignore-sized delete */
#styleBox{padding-bottom:8px; border-bottom:1px solid var(--frame)}
#styleBox > summary{padding:4px 0; cursor:pointer; font-weight:600; color:var(--ink)}
#styleBox:not([open]){padding-bottom:0}
#styleBox > * + *{margin-top:6px}
#styleBox .scap{color:var(--ink2); font-size:12px}
#styleIgnored > summary{cursor:pointer; color:var(--ink2); font-size:12.5px}
#styleIgnoredList{margin:4px 0 0; padding-left:18px; white-space:pre-wrap; word-break:break-word}
.stylesets{display:grid; grid-template-columns:auto minmax(0, 1fr); gap:4px 8px; align-items:center}
.stylesets .srow{display:contents; font-size:12.5px; color:var(--ink2)}
.stylesets select{min-width:0; height:26px}
.srule{display:flex; align-items:flex-start; gap:6px; cursor:default}
.srule:hover:not(:disabled){background:none}
.srule .swhat{flex:1 1 auto; min-width:0; word-break:break-word}
.srule code{font-size:12px}
.srule .cwhy{display:block; font-size:12px; line-height:16px; color:var(--ink2)}
.srule .csev{font-size:12px; line-height:16px; padding:0 6px; border-radius:999px}
.srule .serr{background:var(--red-bg); color:var(--red)}
.srule .swarn{background:var(--amber-bg); color:var(--amber)}
.srule .sskip{display:block; font-size:12px; line-height:16px}
/* the run's notes above the findings: the regex rules still running, or one skipped as too slow */
.cnote{padding:4px 0; font-size:12.5px}
/* the Memory tab (19_tm.js): a card is a list line that is not itself a button (its Insert and Copy are), so it takes
   no hover wash; the keyboard on it (↑ / ↓) washes it. The score chip is the glossary chip's blue on --wash; the
   difference marks are one colour, amber (what needs a look), a strike for what the block lacks and a caret for
   what it adds; the hits of a search are Search's marks (.item mark) */
.memcard,.memcard:hover:not(:disabled){cursor:default; background:none; display:flex; flex-direction:column; gap:2px}
.memcard:focus-within{background:var(--wash)}
.memcard:focus-visible{outline-offset:-2px}
.memhead{display:flex; align-items:baseline; gap:6px; min-width:0}
.memhead .loc{min-width:0; overflow-wrap:anywhere}
.memres > .loc{overflow-wrap:anywhere}
.chip.memscore{flex:0 0 auto; background:var(--wash); color:var(--blue); font-weight:600; font-variant-numeric:tabular-nums}
.memja{font-size:15px; line-height:22px; white-space:pre-wrap; word-break:break-word}
.memen{color:var(--ink); font-weight:600; white-space:pre-wrap; word-break:break-word}
.memja del{color:var(--amber); text-decoration:line-through; text-decoration-thickness:1.5px}
.memja ins{color:var(--amber); text-decoration:none; font-weight:700}
.memacts{display:flex; flex-wrap:wrap; align-items:center; gap:6px; margin-top:2px}
.memacts button{min-height:24px; padding:0 10px; font-size:12px}
/* Replace English and Insert at caret are one joined group, Replace (the card's main act) first and bold; the ⋯
   menu (Copy, and Hide for everyone, which takes the pair out of the memory for everyone) sits apart at the end */
.memacts .memgrp{background:var(--paper)}
.memacts button.memuse{font-weight:600}
.memacts button.memmore{margin-left:auto; padding:0 8px; font-size:16px; line-height:1; color:var(--ink2)}
#memMorePop [role=menuitem]{display:block; width:100%; height:28px; padding:0 12px; color:var(--ink); text-align:left;
  white-space:nowrap}
#memMorePop [role=menuitem]:hover,#memMorePop [role=menuitem]:focus-visible{background:var(--wash)}
#memMorePop [role=menuitem]:focus-visible{outline-offset:-2px}
/* the matches' heading and Add to memory on one line, under the search and its results */
.memtools{display:flex; align-items:center; gap:6px; margin:10px 0 4px}
.memtools button{min-height:24px; padding:0 10px; font-size:12px; margin-left:auto}
/* "Search results" and "Matches for #3": the two lists' headings, so a result is not read as a match */
.memhd{margin:0; font-size:13px; line-height:18px; font-weight:600; color:var(--ink)}
#memResHead{margin-top:4px}
.memopen{font-size:12px; line-height:24px; color:var(--blue)}
.memsearch{display:flex; flex-direction:column; gap:4px}
.memsearch .scap{color:var(--ink2); font-size:12px}
#memInfo button.memundo{min-height:24px; padding:0 8px; font-size:12px; font-weight:600}
#memList:focus-visible{outline-offset:-2px}
/* the row's Memory chip (memRowChip): a button in the glossary chips' line, their pill and blue, framed so it reads
   as the one that can be pressed */
.chips .chip.mem{
  padding:0 8px; border-radius:999px; border-color:var(--frame); background:var(--paper); color:var(--blue);
  font-size:12px; line-height:18px; font-variant-numeric:tabular-nums;
}
.chips .chip.mem:hover:not(:disabled){background:var(--wash); color:var(--blue)}
#rulesPre{
  margin:0; white-space:pre-wrap; word-break:break-word; font-size:12.5px; line-height:18px;
  font-family:Consolas,ui-monospace,monospace; color:var(--ink);
}
/* the rules drawn as a short document in the UI face (renderRules) */
#rulesPre.md{white-space:normal; font:13px/19px var(--font); overflow-wrap:anywhere}
#rulesPre.md h3{margin:4px 0 6px; font-size:16px; line-height:22px; font-weight:600}
#rulesPre.md h4{margin:14px 0 4px; font-size:13px; line-height:18px; font-weight:600}
#rulesPre.md p{margin:0 0 8px}
#rulesPre.md ul, #rulesPre.md ol{margin:0 0 8px; padding-left:18px}
#rulesPre.md li + li{margin-top:2px}
#rulesPre.md pre{
  margin:0 0 8px; padding:6px 8px; white-space:pre; overflow-x:auto; overflow-wrap:normal;
  font:12px/16px Consolas,ui-monospace,monospace; background:var(--desk-2); border-radius:3px;
}
#rulesPre.md pre code{padding:0; background:none}
#rulesPre.md code{
  font:12px/16px Consolas,ui-monospace,monospace; background:var(--desk-2); padding:0 3px; border-radius:3px;
}

/* ---- conflicts: cards of paper on a strip of desk, an amber bar (changed, not final) down each one; the
   cards the Final lock produced carry a grey bar ---- */
.conflicts{
  flex:0 0 auto; border-top:1px solid var(--rule); padding:10px 12px; max-height:46%; overflow:auto;
  background:var(--desk);
}
.conflicts h3{margin:0 0 8px; font-size:12.5px; line-height:16px; font-weight:600; color:var(--ink)}
.conflict{
  padding:8px 10px 8px 13px; margin-bottom:8px; background:var(--paper); box-shadow:inset 3px 0 0 var(--amber);
}
.conflict .loc{font-size:12px; line-height:16px; color:var(--ink2); font-variant-numeric:tabular-nums}
.conflict .txt{white-space:pre-wrap; word-break:break-word; font-size:12.5px; line-height:18px; margin:4px 0}
.conflict .txt b{font-weight:600; color:var(--ink2); font-size:12px; line-height:16px; display:block}
.conflict .cbtns{display:flex; gap:4px; margin:4px 0 0 -8px}
/* a card the Final lock produced: nothing to merge, so it offers the clipboard and a discard */
.conflict.locked{box-shadow:inset 3px 0 0 var(--final-ring)}

/* ---- the Typeset tab, top to bottom: the head (the tag, the proof, its caption), Apply to with its amber line,
   the preset line (the select and Save as preset… always; Apply, Rename… and Delete preset in red pencil with a
   preset), the fields under three headings, Text, Outline and Layout, in two columns with their captions above (the
   alignment as a joined control, a muted note under the multipliers), then Reset to defaults (ghost) ---- */
.tsrow{display:flex; flex-wrap:wrap; align-items:center; gap:4px 10px; color:var(--ink2); font-size:12.5px}
.tsrow label{display:flex; align-items:center; gap:5px}
.tsrow input{margin:0}
.tspreset{gap:4px 4px}
.tspreset select{flex:1 1 140px; width:auto}
.tspreset button{height:24px; padding:0 6px}
/* the name field of Save as preset… and Rename… (#presetDlg), under the button that opened it (placePresetDlg) */
#presetDlg.anchored{position:fixed; inset:auto; margin:0}
/* a group's heading: the panel's quiet caption weight, a hairline above it */
#tabTypeset .tsgh{
  margin:10px 0 0; padding-top:8px; border-top:1px solid var(--rule); font-size:12px; line-height:16px;
  font-weight:600; color:var(--ink);
}
#tabTypeset .tsnote{font-size:12px; line-height:16px; color:var(--ink2); margin:2px 0 0}
/* a swatch and its hex value, one line */
.tscol{display:flex; align-items:center; gap:6px; min-width:0}
.tsgrid .tscol input.hex{width:auto; flex:1 1 0; font-variant-numeric:tabular-nums}
#btnTsPresetDelete,#btnTsPresetDelete:hover:not(:disabled){color:var(--red)}
.tsgrid{display:grid; grid-template-columns:1fr 1fr; gap:8px 12px; margin:4px 0 2px}
.tsgrid label{
  display:flex; flex-direction:column; gap:3px; color:var(--ink2); font-size:12px; line-height:16px; min-width:0;
}
.tsgrid input,.tsgrid select,.tspreset select{
  height:26px; border:1px solid var(--frame); border-radius:3px; padding:0 6px; background:var(--paper);
  color:var(--ink); font-size:13px; line-height:18px; min-width:0;
}
.tsgrid input,.tsgrid select{width:100%}
.tsgrid input[type=color]{padding:2px; cursor:pointer; width:56px}   /* a swatch, not a slab */
/* a field that does not apply reads as off: the placeholder grey on the head tone, its caption lighter still */
.tsgrid input:disabled,.tsgrid select:disabled{
  background:var(--desk-2); color:var(--ink3); border-color:var(--well); cursor:default;
}
.tsgrid input[type=color]:disabled{opacity:.4; cursor:default}
.tsgrid label:has(> input:disabled,> select:disabled),.tsrow label:has(> input:disabled){color:#8A949C}
/* ^ lighter than --ink3: disabled text is exempt from AA, and the inert state stays obvious */
/* the alignment: a joined control whose parts sit in a desk trough, the chosen one a paper chip */
#tsAlign{background:var(--desk); height:26px}
#tsAlign button{width:30px; padding:0; color:var(--ink2)}
#tsAlign button.on{color:var(--ink); background:var(--paper)}
/* the render row: Reset to defaults │ Render page, one line of 6-px padded buttons, Render page at the right end
   (the panel is 360 px, so the line always fits; browser_test pins it at 1280) */
#tabTypeset .srowbtns{margin-top:4px; flex-wrap:nowrap; gap:4px}
#tabTypeset .srowbtns button{padding:0 6px}
#btnTsReset{margin-left:-6px}   /* its word on the column's left edge */
#tsMsg,#tsPresetMsg{white-space:pre-wrap}
/* the head - the block's own balloon tag (its verdict ring, the selection fill) beside its proof, the text box cut
   from the rendered page at 100 %; then "Apply to [block | page | project]" on top of the settings */
#tabTypeset .tshead{display:flex; align-items:flex-start; gap:10px; min-height:26px}
#tsProof{display:block; max-width:calc(100% - 44px); border:1px solid var(--frame); background:var(--paper)}
#tsHead{font-size:12px; line-height:16px; margin-top:-4px}
.tsnum{
  position:relative; flex:0 0 auto; min-width:26px; height:24px; padding:0 7px; margin-top:2px;
  font:700 15px/24px var(--font); font-variant-numeric:tabular-nums; text-align:center;
  background:var(--sel); color:var(--ink); border-radius:12px 12px 12px 0;
}
.tsnum::after{
  content:""; position:absolute; left:0; top:calc(100% - 1px); border-top:6px solid var(--sel);
  border-right:5px solid transparent;
}
.tsnum[data-st=final]{box-shadow:0 0 0 1.5px var(--paper), 0 0 0 3px var(--final)}
.tsnum[data-st=edited]{box-shadow:0 0 0 1.5px var(--paper), 0 0 0 3px var(--edited)}
.tsnum[data-st=commit]{box-shadow:0 0 0 1.5px var(--paper), 0 0 0 3px var(--commit)}
.tsnum:empty{display:none}          /* a text part: no page, no tag (the chrome invariants' CSS exceptions) */
.tsscope{gap:8px; margin:2px 0}
.tsscope .scap{color:var(--ink2); font-size:12px}
.scopeseg{height:26px; background:var(--well); border-color:var(--well)}
.scopeseg > label{
  position:relative; display:flex; align-items:center; padding:0 12px; color:var(--ink2); cursor:pointer; font-size:13px;
}
.scopeseg > label + label{border-left-color:transparent}
.scopeseg > label:has(input:checked){background:var(--paper); color:var(--ink); box-shadow:var(--lift)}
.scopeseg > label:has(input:focus-visible){outline:2px solid var(--ink); outline-offset:1px}
.scopeseg input{position:absolute; opacity:0; width:1px; height:1px; pointer-events:none}
/* the scope reaches past the block: amber, "changed, not final" (syncScopeWarn) */
.hint.wide{padding:3px 8px; border-radius:3px; background:var(--amber-bg); color:var(--amber)}

/* ---- the Review tab's Queue (the commit blue: blocks waiting for the editor) and OCR parts ---- */
#commitHead,#reviewHead{font-size:12px; line-height:16px; color:var(--ink2); font-variant-numeric:tabular-nums}
/* "Review queue" names the section (its counts are #panelCtx's) */
#commitHead{font-size:13px; line-height:18px; font-weight:600; color:var(--ink)}
/* the OCR queue's head reads as a heading (13/18 600 ink), its sentence in caption graphite under it */
#reviewHead .sech{display:block; font-size:13px; line-height:18px; font-weight:600; color:var(--ink)}
/* the editor's verbs under the selected inbox item (placeCommitActs): the row's own stamp (.rbtns, Translated on),
   then after a gap Send back and Reply as ghost buttons */
.cacts{
  display:flex; align-items:center; gap:4px; padding:2px 8px 10px 11px; margin-top:-1px;
  background:var(--sel-wash); box-shadow:inset 3px 0 0 var(--sel); border-radius:0 0 3px 0;
}
.cacts button{height:26px; padding:0 10px; color:var(--ink); white-space:nowrap}
.cacts .rbtns{margin-right:12px}
.cacts .rbtns button{height:24px; padding:0 8px; line-height:22px}
.item.commititem.sel{border-radius:0 3px 0 0}
/* the editor's inbox: a waiting item's place in the commit ink, an answered one's (the editor spoke last) in
   graphite; its English in the UI face at the English field's size, on two lines at most (renderCommitPanel joins
   the hard-wrapped lines), then its note lines (.qnote, above) */
/* the queue is its part's one scroller (renderCommitPanel keeps its scroll), under its fixed head */
#partQueue{overflow:hidden}
#commitList{flex:0 1 auto; min-height:0; overflow:auto}
#commitList .item:focus-visible{outline-offset:-2px}   /* the list scrolls: its ring goes inside (.ptablist's rule) */
.item.commititem{padding:8px 10px 10px 11px}
.item.commititem .loc{color:var(--commit)}
.item.commititem.answered .loc{color:var(--ink2)}
.commititem .snip{font-family:var(--font); font-size:15px; line-height:22px; margin:2px 0;
  display:-webkit-box; -webkit-box-orient:vertical; -webkit-line-clamp:2; overflow:hidden}
/* two lines at most: a long Japanese line is no wall (the whole line is the item's title) */
.revitem .snip{font-family:var(--font-ja); font-size:16px; line-height:24px;
  display:-webkit-box; -webkit-box-orient:vertical; -webkit-line-clamp:2; overflow:hidden}
#commitList > .hint,#reviewList > .hint{padding:0 8px}   /* the items' inset, under the list's -8px margin */
.revitem .loc{margin-right:6px}
.revitem .badge{vertical-align:1px}
/* an OCR list item: the block's crop (ocrThumb: a fixed frame, square-cut like every crop), its text, and a › that
   says the line opens */
.item.revitem{display:flex; align-items:flex-start; gap:10px}
.revthumb{order:-1; flex:0 0 56px; height:72px; position:relative; overflow:hidden; background:var(--paper);
  outline:1px solid var(--frame); outline-offset:-1px}
.revthumb img{position:absolute; max-width:none; visibility:hidden}
.revthumb img.on{visibility:visible}
.revtxt{flex:1 1 auto; min-width:0}
.item.revitem::after{content:"\203A" / ""; align-self:center; color:var(--ink2); font-size:18px}

/* ---- crop lightbox: the crop floats over a dark glass, square-cut like every crop ---- */
#lightbox{
  position:fixed; inset:0; z-index:60; display:flex; align-items:center; justify-content:center;
  background:rgba(16,24,32,.82); cursor:zoom-out; padding:20px;
}
#lightbox img,#lightbox canvas{
  max-width:90vw; max-height:90vh; background:var(--paper); border-radius:0; box-shadow:var(--float);
}
#lightbox .lbmsg{color:var(--desk); font-size:13px}
/* the crop with its × over its top-right corner and its caption under it */
#lightbox .lbfig{position:relative; display:flex; flex-direction:column; align-items:center; gap:8px}
#lightbox .lbclose{position:absolute; top:-14px; right:-14px; width:28px; height:28px; padding:0; border-radius:999px;
  border:1px solid var(--frame); background:var(--paper); color:var(--ink); font-size:18px; line-height:26px;
  box-shadow:var(--lift); cursor:pointer}
#lightbox .lbcap{color:var(--desk); font-size:13px; font-variant-numeric:tabular-nums}

/* ---- dialogs (delete, cast name, preset name, glossary row, export, panels, shortcuts): a floating sheet of
   paper over a dimmed desk, the heading 16/22, the buttons at the right end in their pinned order, the one
   commit in ink (Delete in red pencil). Only #keysDlg[open] below sets a display ---- */
#delDlg,#keysDlg,#gloDlg,#exportDlg,#panelsDlg,#castDlg,#presetDlg,#catchDlg,#bugDlg,#passDlg,#importDlg{
  border:0; border-radius:8px; padding:16px 18px; max-width:380px;
  background:var(--paper); color:var(--ink); box-shadow:var(--float);
}
/* the modal sheets hang 16 px under the bar (never over it), the desk still readable above; #keysDlg is placed
   below (a non-modal sheet) */
#delDlg,#gloDlg,#exportDlg,#panelsDlg,#castDlg,#presetDlg,#bugDlg,#passDlg,#importDlg{
  margin:calc(var(--barh) + 16px) auto auto; max-height:calc(100vh - var(--barh) - 32px); overflow:auto;
}
#delDlg{width:360px}
#delDlg::backdrop,#keysDlg::backdrop,#gloDlg::backdrop,#exportDlg::backdrop,#panelsDlg::backdrop,#castDlg::backdrop,#presetDlg::backdrop,#catchDlg::backdrop,#bugDlg::backdrop,#passDlg::backdrop,#importDlg::backdrop{background:rgba(16,24,32,.36)}
#castDlg h2,#presetDlg h2,#gloDlg h2,#exportDlg h2,#panelsDlg h2,#keysDlg h2,#catchDlg h2,#bugDlg h2,#passDlg h2,#importDlg h2,#delDlg #delMsg{
  margin:0; font-size:16px; line-height:22px; font-weight:600; color:var(--ink);
}
#delDlg #delMsg{margin-bottom:10px}
#delDlg .opt{display:flex; align-items:center; gap:6px; margin:0 0 16px; color:var(--ink2); font-size:12.5px}
/* what the dialog deletes: the crop (at most 120 px high, square-cut like every crop) and one line each of the
   Japanese and the English, cut with an ellipsis; the undo line sits under the recover tick */
#delDlg #delCrop{display:block; max-width:100%; max-height:120px; margin:0 0 8px; border:1px solid var(--rule)}
#delDlg .delline{margin:0 0 4px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; color:var(--ink2)}
#delDlg #delJa{font-family:var(--font-ja)}
#delDlg #delEn{margin-bottom:10px}
#delDlg .opt:has(+ .delundo){margin-bottom:4px}
#delDlg .delundo{margin:0 0 16px; color:var(--ink2); font-size:12.5px}
#delDlg .srowbtns,#keysDlg .srowbtns,#gloDlg .srowbtns,#castDlg .srowbtns,#presetDlg .srowbtns,#bugDlg .srowbtns,#passDlg .srowbtns{justify-content:flex-end}
#delDlg button.primary{background:var(--red); border-color:var(--red)}
#delDlg button.primary:hover:not(:disabled){background:var(--red-hover)}

/* ---- the cast bank: the tab's rows and the "name this character" dialog ---- */
#castDlg,#presetDlg{max-width:min(320px,94vw)}
#castDlg form,#presetDlg form{display:flex; flex-direction:column; gap:10px; align-items:stretch}
#castThumb{align-self:center; background:var(--paper); border:1px solid var(--frame); border-radius:0}
/* a cast row is a list line that is not clickable itself, so it takes no hover */
.citem{cursor:default; display:flex; flex-direction:column; gap:6px; padding:6px 8px 8px}
.item.citem:hover{background:none}
.citem .cname{font-weight:600; color:var(--ink)}
.citem .ccrops{display:flex; flex-wrap:wrap; gap:6px}
.citem .cwrap{position:relative; display:inline-block}
.citem .ccrop{display:block; border:1px solid var(--frame); border-radius:0; background:var(--paper)}
/* the crop's × is a small paper chip picked up off the crop's corner; red pencil when it is about to remove */
.citem .cx{
  position:absolute; top:-5px; right:-5px; width:16px; height:16px; line-height:1; padding:0;
  border-radius:8px; font-size:12px; background:var(--paper); color:var(--ink2); box-shadow:var(--lift);
}
.citem .cx:hover:not(:disabled){background:var(--paper); color:var(--red)}
.citem .cbtns{display:flex; gap:4px; margin-left:-8px}
.citem .cbtns button,.citem .cbtns input{font-size:12.5px; height:24px; padding:0 8px}
.citem .cbtns input{flex:1 1 auto; min-width:0; margin-left:8px}
/* the Cast part's one action, framed so an empty bank shows a button, not a line of text */
#btnFindChars{align-self:flex-start; min-height:28px; border-color:var(--frame); background:var(--paper); color:var(--ink)}
#btnFindChars:hover:not(:disabled){background:var(--wash)}

/* ---- the Team tab's chat (16_chat.js): a ledger, not bubbles - a day line, then per run of one sender its 12/16 caption
   (name, time, page chip) over the text; the list is the tab's one scroller and the composer stays at its foot.
   Counts and the tab's badge are blue pencil, a mention is a wash chip (yours underlined), a page link blue ---- */
/* the Team tab's unread badge follows its word */
.ptablist button .ubadge{
  display:inline-block; margin-left:4px; vertical-align:1px; min-width:16px; height:16px; padding:0 3px; border-radius:8px;
  background:var(--blue); color:#fff; font-size:12px; line-height:16px; text-align:center;
  font-variant-numeric:tabular-nums; pointer-events:none;
}
#tabTeam{overflow:hidden; gap:6px; padding-bottom:8px}
#chatHead{display:flex; align-items:center; gap:6px; min-height:26px}
#chatMore{font-size:12.5px; height:24px}
/* the bell glyph, struck through while off, and the state's word beside it */
#chatChime{margin-left:auto; height:26px; padding:0 8px; display:inline-flex; align-items:center; gap:5px;
  font-size:12.5px; color:var(--ink2)}
.bglyph{width:14px; height:14px; fill:none; stroke:var(--ink2); stroke-width:1.3; stroke-linecap:round}
#chatChime[aria-pressed=true] .bslash{display:none}
#chatList{flex:1 1 auto; min-height:0; overflow:auto; margin:0 -12px; padding:0 12px 4px}
.cday{margin:10px 0 2px; font-size:12px; line-height:16px; color:var(--ink2); text-align:center}
.cmsg{padding:1px 0}
.cmsg:not(.cont){margin-top:6px}
.chead{display:flex; align-items:baseline; gap:6px; font-size:12px; line-height:16px; color:var(--ink2)}
.cwho{font-weight:600; color:var(--ink); min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap}
.cwhen{font-variant-numeric:tabular-nums}
.cpage{margin-left:auto; padding:0 5px; border-radius:3px; background:var(--desk-2); color:var(--ink2);
  font-size:12px; line-height:16px; font-variant-numeric:tabular-nums}
button.cpage:hover:not(:disabled){background:var(--wash); color:var(--ink)}
.ctext{white-space:pre-wrap; overflow-wrap:anywhere; color:var(--ink)}
.ment{padding:0 2px; border-radius:3px; background:var(--wash); color:var(--blue)}
.ment.me{font-weight:600; box-shadow:inset 0 -2px 0 var(--blue)}
.plink{padding:0; border:0; border-radius:0; color:var(--blue); text-decoration:underline; font-variant-numeric:tabular-nums}
.plink:hover:not(:disabled){background:none; color:var(--ink)}
.cempty{padding:8px 0; color:var(--ink2); font-style:italic}
#chatNew{align-self:center; height:24px; padding:0 10px; border-radius:999px; background:var(--wash); color:var(--blue);
  font-size:12px; font-variant-numeric:tabular-nums}
#chatForm{display:flex; align-items:flex-end; gap:6px}
#chatInput{flex:1 1 auto; min-width:0; min-height:40px; max-height:120px; resize:vertical; padding:4px 7px;
  border:1px solid var(--frame); border-radius:3px; background:var(--paper)}
#btnChatSend{flex:0 0 auto; min-height:28px; padding:0 12px}
#chatMsg:empty{display:none}
/* the voice room (18_voice.js) heads the Team tab: who is in it and Join, or a line per person (blue pencil once
   connected, red pencil when unreachable) with Mute and Leave; the top bar's mic toggle shows while you are in it,
   struck through while muted. Existing tokens only */
#voiceBar{display:flex; flex-direction:column; gap:4px; padding-bottom:6px; border-bottom:1px solid var(--rule)}
#voiceHead{display:flex; align-items:center; gap:6px; min-height:26px}
#voiceWho{flex:1 1 auto; min-width:0; color:var(--ink2); font-size:12.5px; overflow:hidden; text-overflow:ellipsis;
  white-space:nowrap}
#voiceHead button{flex:0 0 auto; height:24px; padding:0 10px; font-size:12.5px}
#voicePeers{margin:0; padding:0; list-style:none; display:flex; flex-direction:column; gap:2px; font-size:12.5px;
  line-height:17px}
.vpeer{display:flex; flex-wrap:wrap; align-items:baseline; gap:2px 6px}
.vpeer .vname{font-weight:600; color:var(--ink)}
.vpeer .vstate{color:var(--ink2); overflow-wrap:anywhere}
.vpeer.ok .vstate{color:var(--blue)}
.vpeer.bad .vstate{color:var(--red)}
.vpeer .vhear{height:24px; padding:0 8px; font-size:12px}
#voiceMsg:empty{display:none}
#voiceMsg.err{color:var(--red)}
.mglyph{width:14px; height:14px; fill:none; stroke:currentColor; stroke-width:1.3; stroke-linecap:round; vertical-align:-2px}
#btnVoice{width:28px; padding:0; display:inline-flex; align-items:center; justify-content:center}
#btnVoice[aria-pressed=false] .mslash{display:none}
.pop .prow .pvoice{display:inline-flex; color:var(--blue)}

/* ---- Report a bug: the words, then the attachment as the JSON that is sent, in a scrolling box of its own ---- */
#bugDlg{width:min(520px,94vw); max-width:min(520px,94vw)}
#bugDlg form{display:flex; flex-direction:column; gap:10px}
#bugText{width:100%; resize:vertical; min-height:96px}
#bugMore > summary{cursor:pointer; color:var(--ink2)}
#bugMore .ghint{margin:6px 0 0}
#bugPre{max-height:220px; overflow:auto; margin:6px 0 0; padding:6px 8px; border:1px solid var(--frame); border-radius:4px;
  background:var(--desk-2); font-size:12px; line-height:16px; white-space:pre-wrap; overflow-wrap:anywhere}

/* ---- Change password: three fields, the rules under them, the commit at the right end ---- */
#passDlg{max-width:min(400px,94vw)}
#passDlg form{display:flex; flex-direction:column; gap:6px}
#passDlg .ghint{margin:0}
/* the fields as the Projects page draws them: a 3:1 edge, the 2-px ink ring for the keyboard, red while invalid */
#passDlg .pw input{border-color:var(--field-edge)}
#passDlg .pw input:focus-visible{outline:2px solid var(--ink); outline-offset:1px; border-color:var(--field-edge)}
#passDlg .pw input[aria-invalid=true]{border-color:var(--red)}
/* the consequence of the change, its own line by the buttons */
#passDlg #passEnds{margin-top:4px}
/* each field's error line under it keeps one line's height, empty or not, taken from the gap between the fields, so
   an error showing or going moves neither the fields nor the buttons */
#passDlg .gfield .err{margin:0; min-height:16px; font-size:12px; line-height:16px}
/* Show and Hide take one width, so the field's edge stays put */
#passDlg .pwshow{width:56px; padding:0}

/* ---- glossary row editor ---- */
#gloDlg{max-width:min(420px,94vw)}
#gloDlg form{display:flex; flex-direction:column; gap:10px}
.gfield{display:flex; flex-direction:column; gap:3px}
.gfield span{color:var(--ink2); font-size:12px; line-height:16px}
/* "Send on every page" (decision 0032): the tick, its why under it, and the list's mark of a ticked row */
.gcheck{display:flex; align-items:center; gap:6px}
.ghint{margin:-6px 0 0; color:var(--ink2); font-size:12px; line-height:16px}
/* the tick's one line, then the rest behind "Why?" */
.gwhy{margin:-6px 0 0}
.gwhy summary{width:max-content; color:var(--ink2); font-size:12px; line-height:16px; cursor:pointer}
.gwhy .ghint{margin:4px 0 0}
.gitem .galways{margin-left:6px; color:var(--ink2); font-size:12px; font-weight:400; white-space:nowrap}
#gloJa{font-size:15px; line-height:22px}
#gloErr{margin:0}
#gloErr button{margin-left:6px; font-size:12px; padding:0 7px}
/* Delete (a row being edited) stands apart from the commit, at the line's left end, in red pencil */
#btnGloDelete,#btnGloDelete:hover:not(:disabled){margin:0 auto 0 -12px; color:var(--red)}

/* ---- export dialog (the .x* classes are shared with the panels dialog) ---- */
#exportDlg,#panelsDlg{max-width:min(480px,94vw)}
/* the export sheet keeps one width, so a range warning showing or hiding (ticking "result") never moves the
   checkbox out from under the pointer; a long warning wraps onto its own line (.xpresets below) */
#exportDlg{width:min(420px,94vw)}
#exportDlg form,#panelsDlg form{display:flex; flex-direction:column; gap:14px}
/* on a short window the sheet scrolls and its buttons stay pinned to its foot, on paper that runs out over the
   side and bottom padding (the negative margins keep the unscrolled layout as it was; a sticky inset counts from
   inside the sheet's 16-px padding, so -16px pins the paper's own foot to the sheet's edge) */
#exportDlg .srowbtns,#panelsDlg .srowbtns,#importDlg .srowbtns{
  justify-content:flex-end; position:sticky; bottom:-16px; z-index:1; background:var(--paper);
  margin:0 -18px -16px; padding:0 18px 16px;
}
/* a group is its caption and its choices, set off by space, not by a frame */
.xgroup{display:flex; flex-direction:column; gap:4px}
.xlabel{color:var(--ink); font-size:12.5px; line-height:16px; font-weight:600}
/* a choice's row (the Export kinds; #panelsDlg and #catchDlg share the class) is at least 24 px tall, so its input's
   24-px target circle (WCAG 2.5.8, axe target-size) clears its neighbours' with the 4-px gap between rows */
.xkind{display:flex; align-items:center; gap:6px; min-height:24px; font-size:13px}
.xkind input,.xopts input{margin:0}
.xkind input[disabled]{cursor:not-allowed}
.xkind select{height:24px; padding:0 2px; font-size:12.5px}
/* the zip's options start under the words of the choice they belong to (a 13-px radio + the 6-px gap) */
.xopts{display:flex; flex-wrap:wrap; gap:4px 12px; padding-left:19px; font-size:12.5px; color:var(--ink2)}
.xopts.off,.xkind.off,.xrange.off,.xpresets.off{opacity:.45}
#expTmxWhy{font-style:italic}
.xpair{display:flex; gap:18px}
.xcolsum{display:flex; align-items:center; gap:6px; min-height:24px; cursor:pointer}
.xcolsum::before{content:"\25B8"; width:10px}
details[open] > .xcolsum::before{content:"\25BE"}
.xcols{display:flex; flex-wrap:wrap; gap:4px 12px; padding-bottom:4px}
.xhint{flex:1 1 100%; color:var(--ink2); font-size:12px}   /* the XLIFF choice's note on the zip */
/* Import… sits at the foot's left, apart from Close and Export */
#btnImport{margin-right:auto}

/* ---- import dialog (21_import.js) ---- */
#importDlg{width:min(560px,94vw); max-width:min(560px,94vw)}
#importDlg form{display:flex; flex-direction:column; gap:12px}
#importDlg .hint{margin:0; color:var(--ink2); font-size:12.5px; line-height:17px}
.impsum{margin:0; font-size:13.5px; font-weight:600; color:var(--ink)}
.impdetails{margin:4px 0 0; padding-left:18px; font-size:12.5px; color:var(--ink2)}
.imptable{width:100%; border-collapse:collapse; font-size:12px; margin-top:4px}
.imptable th,.imptable td{text-align:left; padding:3px 6px; border-bottom:1px solid var(--rule); vertical-align:top}
.impnotes{font-size:12.5px; color:var(--ink2)}
/* the XLIFF preview's cards: the editor's conflict card on a strip of desk, scrolled inside the dialog */
.impcards{margin-top:8px}
#impCardList{max-height:40vh; overflow:auto; background:var(--desk); padding:8px; margin-top:4px}
.impbatches{list-style:none; margin:0; padding:0; font-size:12.5px}
.impbatches li{display:flex; align-items:center; gap:8px; min-height:26px}
.impbname{flex:1 1 auto; overflow:hidden; text-overflow:ellipsis; white-space:nowrap}
#impDone{margin:0; font-size:13px; display:flex; align-items:center; gap:10px; flex-wrap:wrap}   /* the reason the memory cannot be exported, under its greyed choice */
.xopts label{display:flex; align-items:center; gap:5px}
.xrange{display:flex; gap:12px}
.xend{flex:1 1 0; display:flex; flex-direction:column; gap:4px; align-items:stretch; min-width:0}
.xthumb{height:92px; object-fit:contain; background:var(--well); border-radius:0}
.xend input[type=range]{accent-color:var(--ink2); margin:2px 0}
.xname{
  color:var(--ink2); font-size:12px; line-height:16px; text-align:center;
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
.xend input[type=number]{width:100%; font-variant-numeric:tabular-nums}
/* This page, All and the count keep one line each (nowrap); a warning that cannot stand beside them wraps onto its
   own line under them, and the panels dialog's long sort note always takes its own */
.xpresets{display:flex; flex-wrap:wrap; align-items:center; gap:4px; font-size:12.5px}
.xpresets button{height:24px; padding:0 8px; flex:0 0 auto; white-space:nowrap}
.xpresets button:first-child{margin-left:-8px}   /* "This page" on the thumbnails' left edge */
.xpresets .spacer{flex:1}   /* the range warning keeps to the line's right end */
.xcount{color:var(--ink2); font-variant-numeric:tabular-nums; margin-left:6px; flex:0 0 auto; white-space:nowrap}
.xwarn{color:var(--amber); font-size:12px; line-height:16px; text-align:right; margin-left:auto}   /* wrapped too */
#pnlWarn{flex:1 1 100%; text-align:left}
#expProgWrap{display:flex; align-items:center; gap:8px; font-size:12px; color:var(--ink2)}
#expProg,#pnlProg{flex:1 1 auto; accent-color:var(--blue)}
#expErr{margin:0}
#expDone{margin:0; font-size:13px}
#expDone a{color:var(--blue); font-weight:600}

/* ---- panels dialog: the same range picker plus one result row per finished page ---- */
#pnlProgWrap{display:flex; align-items:center; gap:8px; font-size:12px; color:var(--ink2)}
#pnlErr{margin:0}
#pnlRows{max-height:180px; overflow:auto}
#pnlRows:empty{display:none}   /* no result rows yet: no second 14-px gap above the buttons */
#pnlRows .jrow{
  display:flex; gap:8px; padding:1px 4px; border-radius:3px; font-size:12px; font-variant-numeric:tabular-nums;
}
#pnlRows .jrow.go{cursor:pointer}
#pnlRows .jrow.go:hover{background:var(--wash)}
#pnlRows .jrow.bad{color:var(--red)}
/* a row that jumps is a button (pnlAddRow), drawn as the plain line: the class rules above keep its padding, size,
   hover and red */
#pnlRows button.jrow{width:100%; border:0; background:none; color:inherit; text-align:left; cursor:pointer}
#pnlRows button.jrow:focus-visible{outline-offset:-2px}   /* the rows scroll: the ring goes inside (#commitList's rule) */

/* ---- the catch-up report (17_catchup.js): the title with Close, the ranges, one group per person, the footnote;
   the counts in blue pencil, a page chip a small button ---- */
/* a non-modal sheet in #keysDlg's place (its rule below), so the rows and the page stay in reach beside it */
#catchDlg{
  position:fixed; inset:auto; top:calc(var(--barh) + 8px); right:12px; left:auto; margin:0; z-index:20;
  width:min(460px,94vw); max-width:min(460px,94vw); max-height:calc(100vh - var(--barh) - 20px); overflow:auto;
}
#catchDlg form{display:flex; flex-direction:column; gap:12px}
#catchDlg .catchtop{display:flex; align-items:center; justify-content:space-between; gap:12px}
#catchDlg .catchtop button{min-height:28px; padding:0 12px}
#catchDlg .catchrange{display:flex; flex-wrap:wrap; gap:4px 14px}
#catchBody{display:flex; flex-direction:column; gap:12px; font-size:13px; line-height:18px}
#catchBody .cnone{margin:0; color:var(--ink2)}
#catchBody .cperson{display:flex; flex-direction:column; gap:4px}
#catchBody h3{margin:0; display:flex; align-items:baseline; gap:8px; font-size:13px; line-height:18px; font-weight:600}
#catchBody .cat{color:var(--ink2); font-size:12px; font-weight:400; font-variant-numeric:tabular-nums}
#catchBody .cline{margin:0; color:var(--blue); font-variant-numeric:tabular-nums}
#catchBody .cnote,#catchBody .cother{margin:0; display:flex; flex-wrap:wrap; align-items:baseline; gap:4px 6px}
#catchBody .cwho{color:var(--ink2)}
#catchBody .ctext{overflow-wrap:anywhere}
#catchBody .cpages,#catchBody .cref{display:inline-flex; flex-wrap:wrap; gap:4px}
#catchBody .cpage{height:24px; padding:0 6px; font-size:12px; font-variant-numeric:tabular-nums}
#catchBody .cpage.gone{display:inline-flex; align-items:center; color:var(--ink2)}
#catchFoot{margin:0; color:var(--ink2); font-size:12px; line-height:16px}

/* ---- shortcuts list ---- */
/* only the [open] rule may set display: `dialog:not([open]){display:none}` is a UA rule,
   and any author `display` on the base selector would beat it and leak the closed dialog */
/* a non-modal sheet (openKeysDlg's show()) at the right, 8 px under the bar, so it never covers the topbar and the
   keys keep working beside it (onKey closes it on Esc). z 20: above every desk layer (6 at most), below the
   topbar's stacking context (30), so a pop the bar opens (#pillLog, #presencePop) paints over the sheet. Its width
   holds a key's action phrase on one line */
#keysDlg{
  position:fixed; inset:auto; top:calc(var(--barh) + 8px); right:12px; left:auto; margin:0; z-index:20;
  width:min(420px,94vw); max-width:none; max-height:calc(100vh - var(--barh) - 20px);
}
#keysDlg[open]{display:flex}
#keysDlg form{display:flex; flex-direction:column; gap:12px; min-height:0; max-height:100%}
/* the scroller takes the focus when the dialog opens: a quiet halo, not the ink ring of a control */
#keysDlg .keyswrap{overflow:auto; min-height:0; -webkit-overflow-scrolling:touch; margin:0 -6px; border-radius:3px}
#keysDlg .keyswrap:focus-visible{outline:2px solid var(--halo); outline-offset:0}
#keysTable{border-collapse:collapse; width:100%; font-size:12.5px; line-height:18px}
#keysTable th{
  text-align:left; padding:14px 6px 4px; font-size:12.5px; line-height:16px; font-weight:600; color:var(--ink);
}
#keysTable tbody:first-child tr.sec th{padding-top:0}
#keysFilter{flex:0 0 auto}
#keysNone{padding:0 6px}
#keysTable td{padding:3px 6px; vertical-align:top}
/* a key's note: a muted second line under its action (undo's reach under Ctrl+Z) */
#keysTable .knote{display:block; color:var(--ink2); font-size:12px; line-height:16px}
/* the controls with no key: the name, then where it is, one line each */
#keysWhereHead{margin:0; padding:14px 6px 4px; font-size:12.5px; line-height:16px; font-weight:600; color:var(--ink)}
#keysWhere{margin:0; padding:0 6px; font-size:12.5px; line-height:18px}
#keysWhere .kw{display:grid; grid-template-columns:minmax(0,9em) minmax(0,1fr); gap:0 12px; padding:3px 0}
#keysWhere dt{font-weight:600}
#keysWhere dd{margin:0; color:var(--ink2)}
/* the gesture column never breaks inside a chord: its keycaps are inline boxes (an inline-block is a break
   opportunity on each side, which split "Alt+|→"), so "Alt+Shift+N" holds together as one word and a long cell
   breaks only at its spaces, the " / " between chords; the description column takes the rest and wraps */
#keysTable td.k{color:var(--ink2); width:34%}
/* keycaps: the well with a --frame edge, a touch deeper at the bottom */
#keysTable kbd{
  font-family:inherit; font-size:12px; line-height:16px; display:inline; padding:0 5px;
  background:var(--well); border:1px solid var(--frame); border-radius:3px; box-shadow:inset 0 -1px 0 var(--frame);
  color:var(--ink); white-space:nowrap;
}

/* ---- a tutorial's coach bubble and backdrop (26_tutorial.js, projects.js): over the chrome and its sheets, under
   the pops at the root (40) and the floating cards (45). The bar's z-index makes it the stacking context of its own
   pops, so a pop open there lifts the whole bar over the backdrop and the bubble: a menu the user opens is never
   dimmed or covered. The backdrop takes no pointer; its hole and ring are SVG geometry, the bubble's place and arrow
   the scripts' CSSOM writes (data-side is the edge that points at the target, --arrow where on it). Its done line is
   ink, not a status colour ---- */
#topbar:has(.pop:not([hidden])){z-index:39}
.tutdim{position:fixed; left:0; top:0; width:100vw; height:100vh; z-index:35; pointer-events:none}
.tutdim path{fill:rgba(14,26,36,.38)}
.tutdim.quiet path{fill:rgba(14,26,36,.12)}
.tutring{fill:none; stroke:var(--sel); stroke-width:2}
.tutorial{
  position:fixed; left:0; top:0; z-index:38; box-sizing:border-box; width:300px; max-width:calc(100vw - 24px);
  padding:12px 14px; background:var(--paper); border-radius:8px; box-shadow:var(--float); color:var(--ink);
  font-size:13px; line-height:19px;
}
.tutorial::before{content:""; position:absolute; width:12px; height:12px; background:var(--paper); transform:rotate(45deg)}
.tutorial[data-side=left]::before{left:-6px; top:calc(var(--arrow, 50%) - 6px)}
.tutorial[data-side=right]::before{right:-6px; top:calc(var(--arrow, 50%) - 6px)}
.tutorial[data-side=top]::before{top:-6px; left:calc(var(--arrow, 50%) - 6px)}
.tutorial[data-side=bottom]::before{bottom:-6px; left:calc(var(--arrow, 50%) - 6px)}
.tutorial[data-side=none]::before{display:none}
.tutorial p{margin:0}
.tutorial .tutcount{color:var(--ink2); font-size:12px; line-height:16px}
.tutorial .tutsaid{margin:4px 0 2px; padding-left:18px; position:relative; color:var(--ink2)}
.tutorial .tutsaid::before{content:"\2713"; position:absolute; left:2px; color:var(--ink)}
.tutorial .tuttitle{margin:2px 0 4px; font-weight:600; font-size:15px; line-height:20px}
.tutorial .tutbtns{display:flex; align-items:center; gap:8px; margin-top:10px}
.tutorial .tutbtns button{min-height:24px}
.tutorial #tutNext{border-color:var(--frame)}
.tutorial .tutskip{margin-left:auto}

/* keep this the last rule of the file */
@media (prefers-reduced-motion:reduce){*,*::before,*::after{transition:none!important; animation:none!important}
  .titem.flash{background:var(--amber-bg)}}
