/* ClaireTMS editor UI — the design/clairetms-editor.html token system
   (doc 03) carried into the real app. Light theme first; dark later. */
* { box-sizing: border-box; }
:root {
  /* SOFTENED GROUND AND INK (founder choice, 6 Aug 2026, off a study that
     put the editor beside itself at both sets). The editor used to render
     body text at 16.5:1 on pure white — more than twice the WCAG AAA
     floor — and that glare, not the geometry, is what a translator feels
     after six hours in the grid. Now 14.5:1 on an off-white ground:
     still above AAA, visibly calmer.
     The neutral ramp (ground → hover → divider → pressed) carries a
     faint GREEN bias rather than the old blue one, so the greys agree
     with the accent instead of pulling against it. Keep the four in
     step: they are one ramp, and hover states read as a different
     colour family the moment one of them drifts.
     --on-surface-var lands at AA (6.1:1) rather than AAA. That is
     deliberate — segment numbers and draft targets are meant to recede —
     but it is the one value to revisit if anyone reports the muted text
     as hard to read.
     This stays inside doc 01's "Material 3": M3 specifies tonal roles,
     not pure white. */
  --surface: #fbfbf9; --surface-low: #f4f6f2; --surface-container: #eceee9;
  --surface-high: #e4e7e0; --on-surface: #23272a; --on-surface-var: #5a615c;
  --outline: #c4c7c5; --outline-var: #dfe3dd;
  --primary: #146c2e; --on-primary: #fff;
  --primary-container: #c8ecd2; --on-primary-container: #04290f;
  --good: #146c2e; --good-container: #c8ecd2;
  --bad: #b3261e; --bad-container: #f9dedc;
  --warn: #8a5300; --warn-container: #fef0d0;
  /* The warning MARK is amber, not the text brown: --warn carries
     enough contrast to be read as a sentence, --warn-mark is what a
     glyph and its card border wear so the lane and the QA card show the
     SAME triangle (founder annotation, 30 Jul 2026). */
  --warn-mark: #f9ab00;
  --info: #1a73e8; --info-container: #cddffb;   /* QA notes: never an error */
  --shadow-lg: 0 2px 6px rgba(0,0,0,.12), 0 8px 24px rgba(0,0,0,.10);
  /* ---- the four widths the editor header's alignment is arithmetic on
     (10 Aug 2026) ----
     .ed-head-right is sized by a calc() that adds all four, so that the
     progress READING ends exactly on the panel's left edge. Written as
     literals, that calc was a copy of four numbers whose originals live
     hundreds of lines apart — and two of them, --pct-w and --bar-w, are
     on .pct/.bar, which the FILES TABLE and the PROJECT ROWS share. A
     one-line change there for a table (five digits of percent, say)
     would have moved the editor's alignment off the panel edge with
     nothing to connect cause to effect.
     Now there is one number each and the calc reads them, so the
     coupling is declared instead of remembered: retune .pct for the
     table and the editor header follows it to the pixel, which is the
     behaviour the comment on that calc always claimed. */
  /* The rail's width is a term of --panel-w, so it is a token too
     (10 Aug 2026, the Google-chrome pass): 44px of capsule + 6px of air
     each side, because the capsules FLOAT now and a floating shape that
     touches the window edge reads as clipped, not floating. */
  --rail-w: 56px;
  --panel-w: calc(330px + var(--rail-w));   /* .panel — content + rail */
  /* The editor chrome's side air (10 Aug 2026, third round of the
     Google pass): the command-bar pill, the grid's right inset, the
     statusbar pill and the header's progress reading all keep exactly
     this much distance from the panel card / window edge — the founder
     drew one dashed line through all of them, so they share one
     number. */
  --gutter: 16px;
  --bar-w: 120px;     /* .bar */
  --pct-gap: 8px;     /* .pct's own left margin */
  --pct-w: 34px;      /* .pct's min-width — tabular-nums, so it is exact */
}
/* ---- rounded glyph treatment (founder choice, 6 Aug 2026) ----
   Every glyph in the product is a 24x24 filled SVG in Material Icons
   FILLED, whose corners and terminals are cut square. The founder picked
   the Rounded look off the proof sheet at 0.4, so each filled shape now
   carries a hairline round-joined, round-capped stroke of its own colour:
   the corners round, the weight barely moves, and no path is redrawn —
   one rule instead of swapping 71 hand-checked paths, which is also why
   it cannot introduce a clipped or malformed glyph.
   0.4 is deliberately the low end. The treatment thickens as it rounds,
   and past about 1.2 shapes with a deliberate thin gap close up —
   concordance's magnifier handle is the binding case now. copySource
   used to be named here twice over: as the two-sheets glyph, and then
   as switch_access_2's SEGMENTED frame, which was nothing but thin gaps.
   Since 11 Aug 2026 it is Material Symbols `input`, whose only gap is
   the break the arrow passes through in the left wall — six units of
   the 24 grid, so it no longer constrains this number at all.
   :not([stroke]) leaves the four OUTLINE glyphs alone — reviewOk,
   reviewBad, signoffBad and circle already declare their own stroke and
   own their geometry; overriding it would repaint them at 0.4 and they
   would vanish. */
svg :is(path, polygon, rect, circle):not([stroke]) {
  stroke: currentColor; stroke-width: .4;
  stroke-linejoin: round; stroke-linecap: round;
}

html, body { height: 100%; margin: 0; }
body { font: 14px/1.5 "Google Sans Text", Roboto, system-ui, -apple-system, sans-serif;
  color: var(--on-surface); background: var(--surface);
  display: flex; flex-direction: column; }
button, input { font: inherit; color: inherit; }
button { background: none; border: none; cursor: pointer; padding: 0; }
.hidden { display: none !important; }
.grow { flex: 1; }
/* Stale-server banner (3 Aug 2026): first child of <body>, so when it is
   not .hidden it sits above the whole app and pushes it down; when hidden
   it takes no space. Amber, not red — a restart is due, nothing is broken. */
.stale-banner { flex: none; display: flex; align-items: center; gap: 12px;
  padding: 8px 16px; background: var(--warn-container); color: var(--warn);
  border-bottom: 1px solid var(--warn-mark); font-size: 13px; }
.stale-banner code { font-size: 12px; padding: 1px 5px; border-radius: 4px;
  background: rgba(0,0,0,.06); }
/* The message is ONE flex item so it wraps as prose; without the span,
   each raw text run between <code> and the button became its own item
   and stacked into ragged columns on a narrow screen. */
.stale-banner .msg { flex: 1; min-width: 0; }
.stale-banner .rbtn { flex: none; }
kbd { font: 11px/1.4 inherit; padding: 1px 6px; border-radius: 4px;
  border: 1px solid var(--outline-var); background: var(--surface-low); }

/* A view fills the window: there is no app bar above them any more. */
.view { flex: 1; min-height: 0; display: flex; flex-direction: column; }
/* The Syntag wordmark (founder rebrand, 28 Jul 2026 — design/syntag):
   "syn" in ink, "tag" in the amber. The amber DARKENED 30 Jul 2026
   (founder decision, after seeing the two side by side): #D99A2B read
   2.44:1 on white, so the "tag" half arrived a beat after "syn" and the
   word landed in two pieces. #BE8118 is 3.31:1 — the two halves now
   carry the same weight, and it clears the 3:1 large-text line that a
   logo is exempt from but does not have to fail. Flat two-tone type, no
   image asset. (The rail icon that used to share clairetrans green
   lost it with the persona, 31 Jul 2026.) Currently unplaced: the app
   bar was removed 1 Aug 2026 and the wordmark is looking for a home. */
.wordmark { font-weight: 650; font-size: 20px; letter-spacing: -.4px;
  color: #163B2C; white-space: nowrap; user-select: none; }
.wordmark em { font-style: normal; color: #BE8118; }

/* The APP MARK finds the home the wordmark is still looking for
   (founder direction, 7 Aug 2026): static/logo.svg, first thing on
   every page — leftmost in each .topbar, so on the editor it sits
   ahead of the "Projects" crumb; over the greeting on the dashboard;
   over the card on the login screen. app.js placeLogo() puts it there
   at boot rather than eleven copies of this markup in index.html, so a
   view added later is branded by existing to have a .topbar.
   28px is sized off the bar it lives in: one notch under the 36px
   .icon-btn beside it, because the mark identifies the app and must
   not out-shout the control that does something. `flex: none` because
   a .topbar is a flex row and an <img> in one is squeezable. */
.logo { width: 28px; height: 28px; flex: none; display: block; user-select: none; }
/* In a bar the mark is a link home, so it gets a hit target the size of
   the icon buttons beside it rather than the 28px image — the button is
   what you aim at, not the artwork.
   The halo is a ROUNDED SQUARE, not .icon-btn's circle: a 28px square has
   a 39.6px diagonal, so its corners would poke outside a 36px circle.
   10px is on the app's existing radius scale (8/10/16/20).
   40px, NOT the 36px of the .icon-btn beside it, and the reason is
   measured rather than chosen: the mark is an OPAQUE plate, so unlike a
   glyph it hides the halo it sits on. At 36px only a 4px ring of hover
   showed and the state was invisible at a glance. 40px — already the
   app's .icon-btn.big, used for the action glyphs in these same bars —
   makes it 6px, and it reads. A transparent glyph never needs this,
   which is why no other control in the bar does.
   NO TRANSITION, deliberately: every hover in this app snaps, and one
   eased control in the top bar reads as lag rather than polish. Same
   reason the mark takes no shadow — in this stylesheet box-shadow means
   "floating above the page" (cards, dialogs, menus, the overlaid panel),
   and a mark printed into the bar is not floating. */
.logo-home { flex: none; width: 40px; height: 40px; border-radius: 10px;
  display: grid; place-items: center; cursor: pointer; }
.logo-home:hover { background: var(--surface-high); }
.logo-home:focus-visible { outline: 2px solid var(--primary); outline-offset: -1px; }
/* On the dashboard the head is align-items:flex-start, so the mark
   would hang off the cap line of a 20px h1. Nudge it onto that line. */
.page-head > .logo { margin-top: 2px; }
/* Login has no bar to sit in: the mark is the page's only branding, so
   it goes large and centred over the card. */
.auth-card > .logo { width: 44px; height: 44px; margin-bottom: 2px; }

/* ---- auth ---- */
#view-auth { align-items: center; justify-content: center; background: var(--surface-container); }
.auth-card { background: var(--surface); border-radius: 20px; box-shadow: var(--shadow-lg);
  padding: 32px; width: 340px; display: flex; flex-direction: column; gap: 14px; align-items: center; }
.auth-card h1 { font-size: 18px; font-weight: 500; margin: 0; }
.auth-card form { display: flex; flex-direction: column; gap: 10px; width: 100%; }
.auth-card input { padding: 10px 14px; border: 1px solid var(--outline); border-radius: 10px; }
.auth-card input:focus { outline: 2px solid var(--primary); border-color: transparent; }
.linkish { color: var(--primary); font-size: 13px; }
.auth-error { color: var(--bad); font-size: 13px; }

.btn-filled { display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  height: 36px; padding: 0 18px; border-radius: 18px;
  background: var(--primary); color: var(--on-primary); font-weight: 500; cursor: pointer; }
.btn-filled:hover { filter: brightness(1.08); }

/* ---- shared chrome ---- */
.topbar { display: flex; align-items: center; gap: 12px; padding: 10px 16px;
  border-bottom: 1px solid var(--outline-var); flex: 0 0 auto; }
/* The editor's header carries two FILE-level facts — how far it has come
   and whether it is done (founder direction, 6 Aug 2026, refined twice
   from annotated screenshots; refined again 7 Aug from a third).
   The alignment rule is that THE READING ENDS where the panel begins:
   "63%" finishes on the panel's left edge, the bar that produced it runs
   back over the grid it measures, and the verdict ("In progress")
   finishes flush with the window.
   It used to be the reading's LEFT edge on that line, which put the whole
   number 34px out over the panel ribbon — the reading touched the line
   but sat on the wrong side of it, so the bar and the number read as
   overhanging the panel rather than stopping at it. Ending on the line is
   what "aligned" meant. This costs nothing in stability because .pct is
   min-width:34px + tabular-nums, so its right edge does not move between
   "0%" and "100%" — aligning that edge would have been a jittering
   mistake on a number that resized.
   So the group is anchored right and is as wide as the panel plus
   everything that has to sit left of the panel's edge — the bar, the gap
   it keeps from the number, and the number itself.
   Right padding moves from the bar onto this group so the width
   measures from the true window edge; the panel has none either. */
/* No border under the editor's topbar (founder direction, 10 Aug 2026,
   from an annotated screenshot + a Google Slides recording): the ruled
   line made the header and the command bar read as two rows of a table.
   The separation is now carried by the command bar itself — a floating
   pill (below) — the way Slides hangs its toolbar under a borderless
   header. Editor only: the other views have no pill to take over the
   job, so their topbars keep the rule. */
#view-editor .topbar { padding-right: 0; border-bottom: 0; }
#view-editor .crumbs { flex: 1; min-width: 0; }
.ed-head-right { flex: none; box-sizing: border-box; padding-right: var(--gutter);
  /* --panel-w = .panel, --gutter = the air the table and the pill keep
     from the panel card, --bar-w = .bar, --pct-gap = .pct's own left
     margin, --pct-w = .pct's own min-width — the term that moves the
     reading fully left of its line instead of straddling it.
     THE READING ENDS WHERE THE TABLE ENDS now (founder annotation,
     10 Aug 2026, third round — the dashed line through the table's
     edge and this number), not on the panel edge it aligned to from
     7–10 Aug: the table pulled back --gutter from the panel, and the
     reading follows the table it measures. That is one extra --gutter
     term in this width.
     These were literals until 10 Aug 2026, which meant this line was a
     COPY of numbers owned elsewhere: .pct and .bar in particular are
     shared with the files table and the project rows, so a width
     retuned for a table silently broke an alignment in the editor.
     Each is declared once in :root and read here, so the dependency is
     in the code rather than in whoever remembers it. */
  width: calc(var(--panel-w) + var(--gutter) + var(--bar-w) + var(--pct-gap) + var(--pct-w));
  display: flex; align-items: center; justify-content: space-between; gap: 12px; }
/* The bar and its reading stay together at the group's left, so the pair
   straddles the panel edge exactly as annotated. */
.ed-head-right #ed-progress { flex: none; }
/* Collapse the panel and there is no --panel-w edge left to align to — the
   group would leave the bar hovering over the grid, a third of the
   window from the status control it belongs with. Collapsed, the pair
   simply hugs the right instead. */
#view-editor.panel-collapsed .ed-head-right { width: auto; }
/* Below the split the panel floats over the grid, so there is no edge
   left to align to: the group goes back to hugging its contents. */
@media (max-width: 900px) {
  #view-editor .topbar { padding-right: 16px; }
  .ed-head-right { width: auto; padding-right: 0; margin-left: auto; }
}
.topbar h1 { font-size: 16px; font-weight: 500; margin: 0; }
.crumbs .project { font-size: 12px; color: var(--on-surface-var); }
.crumbs .job { font-size: 15px; font-weight: 500; display: inline-flex; align-items: center;
  gap: 6px; border-radius: 8px; padding: 2px 6px; margin-left: -6px; color: var(--on-surface);
  /* Bounded by whatever the row can spare — without this the inline-flex
     is shrink-to-fit against MAX-content and simply runs across the bar.
     The +6px is the margin-left above, added back: a negative margin
     makes the margin box that much narrower than the border box, so a
     plain `max-width: 100%` charges the name for the pull-flush and
     ellipsised it six pixels early at every width where the row
     otherwise fit — measured at 1440, where there were 139px to spare
     and the file name was still losing its last character. The two 6s
     are one number; change either and change both. */
  max-width: calc(100% + 6px); min-width: 0; }
/* The name's own box. text-overflow does not apply to a flex container,
   and .job must stay one (the .multi triangle below is a flex item of
   it, so it survives the clip); so the ellipsis lives here. */
.crumbs .job .jobname { min-width: 0; overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; }
.crumbs .job.multi:hover { background: var(--surface-high); }
/* The crumb bar became a ROW when the pair joined it (6 Aug 2026):
   #job-switch is a block, so a plain span after it wrapped to its own
   line instead of sitting beside the name. Baseline alignment keeps the
   two type sizes sharing one line. */
/* The row's two actions are BARE glyphs (founder direction, 6 Aug
   2026), like the command bar's: no border, no fill, the glyph is the
   button. Confirm keeps the primary colour so the row's main verb is
   still the one the eye finds; when the row is confirmed the control
   becomes the pencil that demotes it, in the ordinary ink. */
.seg-actions .rbtn.bare { border-color: transparent; background: transparent;
  color: var(--on-surface-var); }
.seg-actions .rbtn.bare:hover:not([disabled]) { background: var(--surface-high); }
/* A folded-away filter bar must not conceal that the grid is narrowed:
   with a filter or a search live, the toggle keeps the accent even
   though it reads as "off" (6 Aug 2026). The colour is the same one the
   filter chips use when they are doing something. */
.cmdbar .rbtn.filter-toggle.narrowing { color: var(--primary); }
/* One glyph, two colours: grey means "this row is still a draft", green
   means confirmed. (6 Aug 2026 — the pencil/checkmark swap it replaced
   read as two different controls.) */
.seg-actions .rbtn.confirm-toggle { color: var(--on-surface-var); }
.seg-actions .rbtn.confirm-toggle.is-done { color: var(--primary); }
/* The rail's spacer: settings sits at the BOTTOM (6 Aug 2026). */
.prail-grow { flex: 1 1 auto; min-height: 8px; }
/* Info / Settings panel views: a label-value list, the shape a facts
   pane wants — nothing here is editable except the two switches. */
.info-sec { font-size: 11px; font-weight: 600; letter-spacing: 1px; text-transform: uppercase;
  color: var(--on-surface-var); margin: 14px 0 6px; }
.info-sec:first-child { margin-top: 2px; }
.info-row { display: flex; gap: 12px; align-items: baseline; padding: 3px 0;
  border-top: 1px solid var(--outline-var); font-size: 13px; }
.info-row .k { color: var(--on-surface-var); flex: 0 0 42%; }
.info-row .v { flex: 1; min-width: 0; overflow-wrap: anywhere; }
.info-row .v.num { text-align: right; font-variant-numeric: tabular-nums; }
.info-row.total { font-weight: 600; border-top-width: 2px; }
/* askDialog's prompt field (⌘G, 6 Aug 2026). */
.dialog-input { width: 100%; height: 34px; padding: 0 10px; margin-top: 4px;
  border: 1px solid var(--outline-var); border-radius: 8px; font-size: 14px;
  background: var(--surface); color: var(--on-surface); }
.dialog-input:focus { outline: 2px solid var(--primary); border-color: transparent; }
.crumbs { display: flex; align-items: baseline; gap: 8px; min-width: 0; }
/* Breadcrumbs (6 Aug 2026): ancestors recede, the page you are on does
   not — the trail is for orientation and one-click escape, never for
   competing with the thing it labels. */
/* ---- THE ROW HOLDS ITS BOX (10 Aug 2026) ----
   Until now the only thing keeping the crumb row off the editor
   header's progress group was that names were short. A 76-character
   file name at 1024px did not merely paint over the bar (the fault as
   reported): with no `nowrap` it WRAPPED to six lines and drove the
   topbar from 56px to 230px, pushing the whole editor down the page.
   The rule that was supposed to prevent this lived only inside
   @media (max-width: 900) — and measured there, it would not have been
   enough on its own either: `overflow: hidden` never engages on a box
   that is free to size to its own content, so the name still ran 555px
   straight across the bar. Both halves are needed, at every width.
   THE RULE: exactly the two items whose length is UNBOUNDED shrink —
   the nearest ancestor (the project name) and the file name. Everything
   else in the row is furniture of known, small size: the word
   "Projects", the separators, the language pair. Those are flex: 0 0
   auto, so the row can only overflow if it cannot afford ~230px of
   fixed parts, and below 900px .ed-head-right releases its width and
   hands the row three hundred more.
   Proportional shrink — the default, and the first thing tried — is
   what makes a breadcrumb read badly: with everything shrinkable, a
   100px deficit takes a couple of pixels off EVERY item, so "Projects"
   loses its last letter and the pair reads "en-US → da-…" for no gain.
   Measured at 1024px, that is exactly what it did. Pinning the
   furniture spends the whole deficit where there is something to spend
   it on.
   Between the two that do shrink, the ancestor goes first (.near, 40 to
   the file name's 4): the project name is the most expendable thing in
   the row — it is on the file name's hover card and one click away —
   while the file NAME is what this bar exists to identify.
   The row is NOT clipped with `overflow: hidden` on .crumbs, tempting
   as that is: #job-switch's dropdown is absolutely positioned inside
   the row, and clipping the row clips the open file-switcher menu. */
.crumbs .crumb { font-size: 15px; color: var(--on-surface-var); text-decoration: none;
  border-radius: 6px; padding: 1px 4px; margin: 0 -4px; white-space: nowrap;
  flex: 0 0 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.crumbs .crumb.near { flex-shrink: 40; }
/* #job-switch, the block the file name sits in: it must be allowed to
   shrink at all (a flex item's automatic minimum is its CONTENT), but
   less eagerly than the ancestor above it. */
.crumbs .status-filter { flex: 0 4 auto; min-width: 0; }
.crumbs a.crumb:hover { background: var(--surface-high); color: var(--on-surface); }
.crumbs .crumb-sep { font-size: 15px; color: var(--outline-var); flex: none; }
/* The last crumb is the h1 of this page in every level's markup —
   #files-title and #ed-job already carry .job's weight. */
/* Quieter than the file name it follows: smaller, secondary colour —
   present for the glance, never competing with the one thing the bar
   identifies. */
/* The pair lives in the FOOTER since 16 Aug 2026 (founder direction:
   the file status took its old seat beside the name). Its old crumbs
   lesson still holds where it now sits and for its successor: never
   shrinkable — a truncated language tag ("en-US → da-…") is the one
   thing that can be actively misread, and the derived status label
   inherits the same rule, because "Translation Rej…" would invert the
   meaning it exists to carry. */
.statusbar .pair { font-size: 12px; color: var(--on-surface-var); letter-spacing: .3px;
  white-space: nowrap; flex: 0 0 auto; }
.crumbs .file-status-label { flex: 0 0 auto; }
.crumbs .job.multi::after { content: ''; width: 0; height: 0; margin-left: 2px;
  border: 5px solid transparent; border-top-color: currentColor; transform: translateY(3px); }
#job-switch .status-menu { min-width: 260px; max-width: 70vw; }
#job-menu [role="option"] { gap: 12px; }
#job-menu .fname { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
#job-menu .count { flex: none; }
/* Derived status (30 Jul 2026): where a project or a file stands — no
   work yet, under way, all done, handed back. Coloured TEXT, never a
   capsule (founder direction: first the files table, then the Projects
   rows). The colour is the signal a scan uses; the pill was the
   container it arrived in, and a column of lozenges reads as
   decoration. Once both lists dropped it the pill had no user left, so
   this is one rule rather than an override. */
.pstatus { flex: none; font-size: 13px; font-weight: 500; white-space: nowrap; }
/* Status labels are WORDS, not traffic lights (founder direction,
   16 Aug 2026) — the amber/green went, in both the projects grid and
   the files table, because the Progress bar beside them already carries
   the at-a-glance signal. The classes stay: the hover card and the
   census still key on them. */
.ps-notstarted, .ps-progress, .ps-complete { color: var(--on-surface-var); }
.ps-returned { color: #1B4E86; }
/* Archived is the quietest of the five: it is not a stage of the work,
   it is the absence of one (7 Aug 2026). */
.ps-archived { color: var(--on-surface-var); font-weight: 400; }

/* The STEP repeats down its column — every file in a package usually
   shares one — so it reads as muted text beside the file name. */
.files-table td .step-chip {
  background: none; padding: 0; height: auto; border-radius: 0;
  font-size: 13px; font-weight: 400; color: var(--on-surface-var); }

/* ---- project FILES view (29 Jul 2026): the middle level of the tree */
.files-wrap { padding: 18px 20px; overflow: auto; }
.files-table { width: 100%; border-collapse: collapse; font-size: 14px; }
.files-table th { text-align: left; font-size: 11px; letter-spacing: .8px;
  text-transform: uppercase; color: var(--on-surface-var); font-weight: 600;
  padding: 0 12px 8px; border-bottom: 1px solid var(--outline-var); }
.files-table th.num, .files-table td.num { text-align: right; font-variant-numeric: tabular-nums; }
/* The Modified column wears the projects grid's muted/nowrap dressing;
   those rules are scoped to .projects-grid, so the files table carries
   its own copy (adversarial review, 16 Aug 2026: the classes matched
   nothing here and the cell silently rendered full-color, wrapping). */
.files-table td.muted { color: var(--on-surface-var); }
.files-table .nowrap { white-space: nowrap; }
.files-table td { padding: 12px; border-bottom: 1px solid var(--outline-var); }
.files-table tbody tr { cursor: pointer; }
.files-table tbody tr:hover { background: var(--surface-high); }
.files-table .fname { font-weight: 500; color: var(--on-surface); }
/* Progress bar, shared by the files table and the project rows. */
.bar { position: relative; height: 6px; width: var(--bar-w); border-radius: 999px; flex: none;
  background: var(--surface-container); overflow: hidden; }
.bar i { position: absolute; inset: 0 auto 0 0; background: var(--primary); }
.pct { font-size: 12px; color: var(--on-surface-var); margin-left: var(--pct-gap);
  font-variant-numeric: tabular-nums; min-width: var(--pct-w); text-align: right; }
.progress-cell { display: flex; align-items: center; flex: none; }
.icon-btn { width: 36px; height: 36px; border-radius: 50%; display: grid; place-items: center;
  color: var(--on-surface-var); font-size: 16px; }
.icon-btn:hover { background: var(--surface-high); }
/* A view's ACTION glyph is drawn larger than the chrome around it
   (founder direction, 30 Jul 2026) — muted like the rest of the bar, so
   size alone carries the emphasis the filled chip used to. */
.icon-btn.big { width: 40px; height: 40px; }
/* The import control is a <label> for the hidden file input, so it needs
   the pointer a button gets for free. */
label.icon-btn { cursor: pointer; }
.icon-btn.big .sicon { width: 26px; height: 26px; }
.icon-btn:disabled { opacity: .45; }
.chip { display: inline-flex; align-items: center; gap: 6px; height: 32px; padding: 0 14px;
  border-radius: 8px; border: 1px solid var(--outline-var); font-size: 13px; font-weight: 500;
  color: var(--on-surface-var); background: var(--surface); }
.chip:hover { background: var(--surface-low); }
.chip[aria-pressed="true"] { background: var(--primary-container); color: var(--on-primary-container); border-color: transparent; }
.chip[disabled] { opacity: .45; pointer-events: none; }
.chip .count { font-variant-numeric: tabular-nums; opacity: .75; font-weight: 400; }

/* ---- projects ---- */
/* A GRID, NOT CARDS (founder direction, 7 Aug 2026, from
   design/projects-grid.html variant C). Every row used to be a bordered,
   14px-radius card carrying a name over a run-on muted meta line. The
   columns are the structure now: no border and no radius around a row —
   a box around each one makes the eye read cards and stop reading
   columns — with one rule under the header and one between rows.
   Four columns, the founder having turned down Number, Files, Stage,
   Status and Last modified for now. */
/* The page is now a column: grid scrolls, footer pinned under it. */
#view-projects { display: flex; flex-direction: column; min-height: 0; }
.projects { padding: 0; overflow-y: auto; flex: 1; min-height: 0; }
/* Selection bar — the TM page's, in this page's gutter. It appears only
   while a selection exists, and its verbs act on that selection, so
   nothing that could change the selection sits beside them. */
.proj-selbar { display: flex; align-items: center; gap: 8px;
  padding: 10px 18px; border-bottom: 1px solid var(--outline-var);
  background: var(--surface-low); flex: 0 0 auto; }
/* The FOOTER (7 Aug 2026): three scopes and the count of what is
   listed. Pinned, because it is also the only way back from the
   Archived view — a footer that scrolls away with an empty list would
   strand you exactly when you need it. */
.proj-footer { display: flex; align-items: center; gap: 12px; flex: 0 0 auto;
  padding: 8px 18px; border-top: 1px solid var(--outline-var);
  background: var(--surface); font-size: 13px; }
.proj-footer .scope { display: inline-flex; gap: 2px; }
.proj-footer .scope-btn { border: 0; background: transparent; cursor: pointer;
  padding: 5px 12px; border-radius: 999px; font: inherit;
  color: var(--on-surface-var); }
.proj-footer .scope-btn:hover { background: var(--surface-high); }
.proj-footer .scope-btn[aria-selected="true"] {
  background: var(--primary-container); color: var(--on-primary-container); font-weight: 500; }
.proj-footer .muted { color: var(--on-surface-var); }
/* The checkbox column: fixed and narrow, and NOT wearing the row's
   pointer — the cell opens nothing, it ticks. */
.projects-grid .c-sel { width: 44px; cursor: default; }
.projects-grid .c-sel input { cursor: pointer; margin: 0; }
.projects-grid tbody tr.picked { background: var(--primary-container); }
.projects-grid tbody tr.picked:hover { background: var(--primary-container); }
.projects-grid { width: 100%; border-collapse: collapse; }
/* Sticky against .projects, which is the scroller. Opaque background, or
   the rows read through it on the way past. */
.projects-grid th { text-align: left; font-weight: 400; font-size: 13px;
  color: var(--on-surface-var); padding: 12px; white-space: nowrap;
  border-bottom: 1px solid var(--outline-var);
  position: sticky; top: 0; background: var(--surface); z-index: 1; }
.projects-grid td { padding: 14px 12px; vertical-align: middle; }
/* The page's own 18px gutter, kept on the outer columns only — the rule
   under the header runs edge to edge, which is what makes it read as a
   table heading rather than a boxed strip. */
.projects-grid th:first-child, .projects-grid td:first-child { padding-left: 18px; }
.projects-grid th:last-child, .projects-grid td:last-child { padding-right: 18px; }

/* ---- sortable/filterable headers + the grid popover (16 Aug 2026) ----
   Shared by the projects grid and the files table: one look, one set of
   rules, the same argument as the shared machinery in app.js. */
th.sortable { cursor: pointer; user-select: none; white-space: nowrap; }
th.sortable:hover { color: var(--on-surface); }
/* display:inline-block is load-bearing (the .tm-pen-dialog rule knows
   the same trap): icon() svgs carry .sicon, which is display:block —
   left alone, the sort arrow drops onto its own line UNDER the label
   and the header grows to two rows, which white-space:nowrap on the th
   cannot prevent (founder report, 16 Aug 2026). */
.th-arrow svg { width: 14px; height: 14px; vertical-align: -2px; margin-left: 2px;
  display: inline-block; }
.th-arrow svg path { fill: currentColor; }
/* The funnel is quiet until it matters: barely-there beside an idle
   column, full-strength once its filter is narrowing the table. */
.th-funnel { border: 0; background: transparent; padding: 0 2px; margin-left: 4px;
  cursor: pointer; color: var(--on-surface-var); opacity: .35; vertical-align: middle; }
.th-funnel:hover { opacity: .8; }
.th-funnel.on { opacity: 1; color: var(--primary); }
.th-funnel svg { width: 13px; height: 13px; display: block; }
.th-funnel svg path { fill: currentColor; }
#grid-pop { position: fixed; z-index: 70; min-width: 180px; max-width: 260px;
  background: var(--surface); border-radius: 12px; box-shadow: var(--shadow-lg);
  padding: 10px 12px; }
.grid-pop-title { font-size: 12px; font-weight: 600; color: var(--on-surface-var);
  margin-bottom: 6px; letter-spacing: .3px; }
.grid-pop-row { display: flex; align-items: center; gap: 8px; padding: 5px 2px;
  font-size: 13px; cursor: pointer; }
.grid-pop-row input[disabled] + span { color: var(--on-surface-var); }
.grid-pop-q { margin-bottom: 8px; }
.grid-pop-clear { width: 100%; margin-top: 6px; }
/* NO rule between rows either (founder direction, 7 Aug 2026, moving
   from variant C to A after seeing both in the running app): the rule
   under the header is the only one on the page. What separates one row
   from the next is the row height and the hover — which is enough at
   four columns, and stops a list of five projects reading as a ledger.
   The `tr + tr` border this replaced is gone rather than commented out;
   variant C survives in design/projects-grid.html if it is wanted back. */
.projects-grid tbody tr { cursor: pointer; }
.projects-grid tbody tr:hover { background: var(--surface-low); }
.projects-grid .name { font-weight: 500; }
.projects-grid .muted, .projects-grid .langs { color: var(--on-surface-var); }
/* A count is a number: right-aligned and tabular, so 3 and 28 and 128
   line up on their units digit down the page. */
.projects-grid .right { text-align: right; }
.projects-grid .num { font-variant-numeric: tabular-nums; }
.projects-grid .nowrap { white-space: nowrap; }
/* The progress column is the one cell that must not stretch: the bar is
   a fixed length so the percentages line up down the page. */
.projects-grid td .progress-cell { width: max-content; }
.job-row { display: flex; gap: 10px; align-items: center; font-size: 13px; color: var(--on-surface-var); }
.job-row .fname { color: var(--on-surface); }
.step-chip { display: inline-flex; align-items: center; height: 20px; padding: 0 8px;
  border-radius: 10px; font-size: 11px; font-weight: 500;
  background: var(--primary-container); color: var(--on-primary-container); }
/* One tonal color per workflow step (founder direction, 27 Jul 2026):
   Translation orange, Review green, Sign-off blue — projects page and
   editor share the classes. */
.step-chip.step-translation { background: #fdebd9; color: #b05a1a; }
.step-chip.step-review { background: var(--primary-container); color: var(--on-primary-container); }
.step-chip.step-signoff { background: #d3e3fd; color: #0b57d0; }
/* Tracked changes (Trados parity, docs/04): deletions struck through,
   insertions violet + underlined — the Studio review look. */
.rev-del { color: #c62828; text-decoration: line-through; }
.rev-ins { color: #7b1fa2; text-decoration: underline; }
/* QA findings panel: dismissed cards fade back but stay auditable. */
.qa-dismissed { opacity: .55; }
/* QA findings are a FLAT LIST (founder direction, 30 Jul 2026): no
   per-segment grouping at all. Each card carries its own segment jump
   (tooltip names it) and the active segment's cards keep the green
   outline — it is what the grid-follow scroll aims at. */
.card.qa[data-qa-jump] { cursor: pointer; }
.card.qa[data-qa-jump]:hover { background: var(--surface-low); }
.card.qa.qa-active { outline: 2px solid var(--primary); outline-offset: -1px;
  background: #eaf5ec; }
.card.qa.qa-dismissed { opacity: .7; }
/* Long check names shrink with an ellipsis instead of pushing the
   Dismiss button out of the card (founder catch, 28 Jul 2026). */
.card-head .kind { min-width: 0; overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; }
.card-head .rbtn { flex: none; }
.seg-ref { font-size: 11.5px; color: var(--on-surface-var); white-space: nowrap;
  font-variant-numeric: tabular-nums; }
.qa-summary { padding: 2px 2px 8px; }
.qa-glyph { cursor: pointer; }
/* Communication-lane icons rest translucent and wake on hover (founder
   direction + example clip, 27 Jul 2026): findings stay findable
   without shouting over the text — full color is the HOVER state. */
.qa-glyph, .comment-glyph { opacity: .4; transition: opacity .12s; }
.qa-glyph:hover, .comment-glyph:hover { opacity: 1; }
/* Accept/Reject tracked changes: kind chips + the all-row. */
.sev-chip.chg-ins { background: #ede7f6; color: #5e35b1; }
.sev-chip.chg-del { background: #fdecea; color: #b3261e; }
.chg-all { display: flex; gap: 8px; padding-bottom: 4px; }
.rev-del, .rev-ins { cursor: pointer; }
/* Commented text highlights, Studio-style severity colors. */
.cmt-hl { border-radius: 2px; cursor: pointer; }
.cmt-hl.sev-info { background: #fdf0b2; }
.cmt-hl.sev-warning { background: #ffddb0; }
.cmt-hl.sev-error { background: #ffc4b5; }
.empty-note { color: var(--on-surface-var); padding: 40px; text-align: center; }

/* ---- editor ---- */
/* The command bar (6 Aug 2026): one icon row above the grid, groups
   divided by a hairline — Smartcat's shape. Wraps rather than scrolls,
   because a verb you cannot see is a verb you do not have.
   A FLOATING PILL since 10 Aug 2026 (founder direction, from a Google
   Slides recording): light fill, fully rounded ends, margins on every
   side, and NO border lines — the pill's own shape is the separation
   that the two ruled lines (the topbar's and this bar's border-bottom)
   used to provide. Slides' toolbar is exactly this: the bar is an
   object sitting on the page, not a row of a table the page is made of.
   border-radius 999px survives the wrap: on a two-line bar it clamps to
   half the height, which is still the pill shape, just taller. */
.cmdbar { display: flex; align-items: center; flex-wrap: wrap; gap: 2px;
  margin: 2px var(--gutter) 6px; padding: 5px 14px; flex: 0 0 auto;
  background: var(--surface-low); border-radius: 999px; }
.cmd-group { display: inline-flex; align-items: center; gap: 2px; }
.cmd-sep { width: 1px; align-self: stretch; margin: 3px 4px;
  background: var(--outline-var); flex: none; }
/* SPLIT button (6 Aug 2026): the everyday verb and a caret to its heavier
   relatives. `position: relative` is what the menu hangs off, so the popup
   travels with the button when the bar wraps. The caret is deliberately
   narrower than the face — it is a modifier, not a peer. */
.rbtn-split { position: relative; display: inline-flex; align-items: center; }
.cmdbar .rbtn-split .rbtn.caret { padding: 0 2px; margin-left: -4px; }
.cmdbar .rbtn-split .rbtn.caret svg { width: 14px; height: 14px; }
/* One glyph, two colours — the row's toggle rule, on the bar: grey means
   the active segment is still a draft, green means it is confirmed. */
.cmdbar .rbtn.confirm-cmd { color: var(--on-surface-var); }
.cmdbar .rbtn.confirm-cmd.is-done { color: var(--primary); }
/* Right-aligned instead of left, for a split button close enough to the
   bar's right edge that the popup would otherwise paint over the panel.
   The class is applied by measurement at open time, not by position in
   the bar — a wrapped bar puts the last group at x=16, where `right: 0`
   would push the menu off the LEFT edge instead (7 Aug 2026). */
.cmd-menu.flip { left: auto; right: 0; }
.cmd-menu { position: absolute; top: 100%; left: 0; z-index: 30; min-width: 170px;
  margin-top: 4px; padding: 4px; display: flex; flex-direction: column;
  background: var(--surface); border: 1px solid var(--outline-var);
  border-radius: 10px; box-shadow: 0 6px 20px rgba(0,0,0,.14); }
.cmd-menu-item { text-align: left; padding: 7px 10px; border-radius: 7px;
  font-size: 12.5px; color: var(--on-surface); background: transparent; border: 0; }
.cmd-menu-item:hover { background: var(--surface-low); }
/* Tighter than a panel button: 30 verbs have to share one row before
   the wrap, and an icon row reads as a row only while it IS one.
   No border and no fill (founder direction, 6 Aug 2026): 30 outlined
   boxes in a row read as a grid of cells, not a toolbar — the glyph is
   the button. Hover and the pressed state carry the affordance, so
   nothing is lost except the boxes. */
.cmdbar .rbtn { height: 26px; padding: 0 8px; font-size: 12px;
  border-color: transparent; background: transparent; }
/* :not() so a PRESSED toggle keeps its fill under the cursor — the
   hover rule out-specifies .rbtn[aria-pressed] otherwise, and Aa would
   go grey exactly while you were about to click it. */
.cmdbar .rbtn:not([aria-pressed="true"]):hover { background: var(--surface-high); }
.cmdbar .rbtn.icon { width: 26px; padding: 0; }
.cmdbar .rbtn.icon svg { width: 17px; height: 17px; }
.toolbar { display: flex; align-items: center; gap: 8px; padding: 4px 16px 10px; flex: 0 0 auto;
  flex-wrap: wrap; }   /* the toolbar shares its row with the panel now — chips wrap, never overflow */
  /* Its border-bottom (inherited from .grid-head when the column
     headings went, 6 Aug 2026) came off in the 10 Aug de-ruling: with
     the pill above it borderless, the one remaining line under the
     chips read as a leftover. What separates the chips from the first
     segment now is WHITESPACE alone — this bar's 10px bottom padding
     plus the row's 11px top padding — because the same pass removed
     the grid's row hairlines too. (The first draft of this comment
     leaned on those hairlines, three rules before deleting them; the
     adversarial sweep caught the contradiction.) */
/* The Status dropdown (founder direction, 27 Jul 2026): the chip row
   nested into one exposed-dropdown control; the menu floats over the
   grid, so z-index beats the virtualizer's rows. */
.status-filter { position: relative; }
.status-filter .chip { white-space: nowrap; }
.status-filter .chip svg { width: 18px; height: 18px; margin: 0 -6px 0 -2px; }
.status-filter .chip[aria-expanded="true"] { background: var(--primary-container);
  color: var(--on-primary-container); border-color: transparent; }
/* A non-All filter keeps the chip filled — the standing "you are
   looking at a subset" signal (founder direction, 27 Jul 2026; green
   rather than blue to stay in the dropdown's own selection color).
   Shared by the Status and Match chips; QA fills via aria-pressed. */
.status-filter .chip.filtered { background: var(--primary-container);
  color: var(--on-primary-container); border-color: transparent; }
#status-current { font-weight: 500; font-variant-numeric: tabular-nums; }
.status-menu { position: absolute; top: calc(100% + 4px); left: 0; z-index: 6;
  min-width: 200px; background: var(--surface); border: 1px solid var(--outline-var);
  border-radius: 10px; box-shadow: 0 4px 16px rgba(0, 0, 0, .12); padding: 4px; }
.status-menu [role="option"] { display: flex; align-items: center; justify-content: space-between;
  gap: 24px; width: 100%; padding: 8px 12px; border: none; border-radius: 7px;
  background: none; text-align: left; font-size: 13px; color: var(--on-surface); }
.status-menu [role="option"]:hover { background: var(--surface-low); }
.status-menu [role="option"][aria-selected="true"] { background: var(--primary-container);
  color: var(--on-primary-container); }
.status-menu .count { font-variant-numeric: tabular-nums; opacity: .75; }
/* Multi-select rows (the TM-match menu): drawn checkbox, label takes
   the middle, state lives in the box — not a row fill — so several
   ticks read as ticks, not as one long selection. */
.status-menu .lbl { flex: 1; }
.status-menu .cbx { width: 15px; height: 15px; flex: none; margin-right: -9px;
  border: 1.5px solid var(--outline); border-radius: 4px;
  display: grid; place-items: center; }
.status-menu [aria-checked="true"] .cbx { background: var(--primary); border-color: var(--primary); }
.status-menu [aria-checked="true"] .cbx::after { content: ''; width: 4px; height: 8px;
  margin-top: -2px; border: solid #fff; border-width: 0 2px 2px 0; transform: rotate(45deg); }
.search-field { display: flex; align-items: center; height: 34px; padding: 0 12px;
  border: 1px solid var(--outline-var); border-radius: 17px; background: var(--surface-low); width: 220px; }
.search-field:focus-within { border-color: var(--primary); background: var(--surface); }
.search-field input { flex: 1; min-width: 0; border: none; background: none; outline: none; font-size: 13px; }
.search-count { font-size: 12px; color: var(--on-surface-var); font-variant-numeric: tabular-nums; }
/* In the filter bar the field sits chip-height beside the chips and
   yields gracefully on narrow screens (the toolbar wraps). */
.toolbar-search { height: 32px; flex: 0 1 240px; min-width: 140px; }

.workspace { flex: 1; min-height: 0; display: flex; }
.grid-area { flex: 1; min-width: 0; display: flex; flex-direction: column; }
/* overflow-anchor off: Chrome's scroll anchoring "helpfully" adjusts
   scrollTop when absolutely-positioned rows above the viewport change
   height — which is the virtualizer's normal life — and it overrode
   the fixed eye-line pin deterministically (28 Jul 2026 jitter hunt). */
#vp { flex: 1; overflow-y: auto; overflow-anchor: none; }
#spacer { position: relative; }
/* THE GRID DRAWS NO LINES (founder direction, 10 Aug 2026, annotated
   screenshot — "remove the table borders", arrows on the column divider
   AND the row hairline). Two rules died here:
   * the row border-bottom (var(--surface-container), since the first
     grid), and
   * .seg::after — the full-height source/target divider the 26 Jul
     annotations asked for, with its 50% − 12px derivation. The founder
     drew it in and the founder drew it out; the derivation comment
     lives on in git if the line ever comes back.
   What separates things now: rows by their own combined padding (45px
   since the fifth round's ledger — 11 top + 34 bottom; it was 22 when
   this comment was written) and the hover fill, the two columns by the
   40px status lane between them, the active pair by its green fill.
   That is the Google reading — the page is not a table, the content
   makes its own edges. */
/* right: var(--gutter), not 0 (founder annotation, 10 Aug 2026, third
   round): the table ran flush into the panel card — "the table border
   touches the panel, and that is not great" — while the command-bar
   pill above it kept its air. The rows now end at the same line the
   pill ends on, and the row fills (hover, active) stop there with
   them; #vp's scrollbar lives in the gutter this leaves. The LEFT
   edge stays 0: the founder flagged the panel side only, and a row
   fill running to the window's left edge is the reading-app idiom. */
/* THE ROW IS THE SAME SIZE ACTIVE AND AT REST (founder direction,
   10 Aug 2026, fifth round — Smartcat as the reference: "no movement
   of the text inside the segment or below a segment when a segment
   becomes active"). The 34px bottom padding is not air for its own
   sake: it RESERVES, in every row, exactly the band the active row's
   in-box strip draws into — so activation adds nothing and later
   segments never shift.
   THE BAND IS RESERVED ON THE ROW, NOT PER COLUMN, and that is the
   whole trick. The first draft traded the row's padding (34 → 6) and
   gave the 28 back as `padding-bottom` on the SOURCE cell; that is
   only correct while the source or the target is the tallest thing in
   the row. A one-line segment carrying a QA finding AND a comment has
   a 44px glyph lane driving its height, and it SHRANK 22px on
   activation — the invariant inverted, on a row shape any reviewer
   meets (found by the adversarial sweep, reproduced at both widths;
   the same arithmetic bit the seq number when a split id wraps below
   900px). Per-column compensation cannot be right, because any column
   may be the driver.
   So: the row keeps 11/34 in BOTH states, and the box's extra band is
   pulled back out of the flow with a negative BOTTOM margin — it draws
   into the padding that was always there. Then, whatever drives the
   height:
     rest    = 11 + max(num, src, st, tgt, alerts) + 34
     active  = 11 + max(num, src, st, tgt, alerts) + 34   (identical)
   because the box contributes −7 + 7 + tgtH + 28 − 28 = tgtH, exactly
   what the bare .tgt contributed at rest.
   Touch the box's border, padding or the strip's height and BOTH the
   negative bottom margin and this 34 must follow — the equality is the
   feature. */
.seg { position: absolute; left: 0; right: var(--gutter); display: grid;
  grid-template-columns: 56px 1fr 40px 1fr 40px; gap: 0 14px; padding: 11px 16px 34px;
  font-size: 14.5px; line-height: 1.55; cursor: pointer; }
.seg:hover { background: var(--surface-low); }
.seg .num { font-size: 12px; color: var(--on-surface-var); font-variant-numeric: tabular-nums; padding-top: 4px; }
.seg .tgt { white-space: pre-wrap; }
/* An unbreakable run BREAKS rather than widening its column (10 Aug
   2026). Segment text carries URLs, and at rest a long one floored the
   1fr track at its own width and squeezed the other column; the active
   box then needed the same floor or the columns re-laid on activation
   — the round-4 sweep caught exactly that and paid for it by dropping
   the box's min-width:0, which in turn let the round-5 chip strip
   widen the box. Breaking the token retires the whole argument: with
   `anywhere` the min-content of both cells is one character, so
   neither state has a floor to disagree about, and no content can
   escape its column in either. */
.seg .src, .seg .tgt { overflow-wrap: anywhere; }
.seg.draft .tgt { color: var(--on-surface-var); }
.seg .st, .seg .alerts { display: flex; flex-direction: column; gap: 3px; align-items: center; padding-top: 1px; }
/* ONE status icon per segment (founder direction, 25 Jul 2026):
   Material-style shape = confirmation level, color = provenance clue —
   green trusted (human / 100% / CM), amber fuzzy, blue machine, grey
   PM, translucent auto-propagated. All detail lives in the tooltip. */
.sicon { width: 20px; height: 20px; display: block; }
/* Desaturated icons (founder direction, 26 Jul 2026) — one dial over
   all status/lane colors; brand tokens elsewhere untouched. The error
   red additionally starts from a muted brick: even filtered, --bad
   read too loud in the lane. */
.sicon { filter: saturate(.6); }
.ic-green { color: var(--good); }
/* 100% match (exact, untouched) = the founder's swatch (27 Jul 2026),
   rendered verbatim like the other founder swatches — the "sometimes
   billed" band between PM/CM dark green and full-rate orange. */
.ic-exact { color: #7ca05f; filter: none; }
/* Human-worked = full rate (founder direction, 27 Jul 2026): typed
   from scratch or edited off a match. Deeper than the fuzzy amber so
   the two warm bands stay tellable apart. */
.ic-orange { color: #e8710a; }
.ic-amber { color: var(--warn-mark); }
.ic-blue { color: var(--info); }
.ic-grey { color: var(--on-surface-var); opacity: .75; }
.ic-pale { color: var(--good); opacity: .4; }
/* Error red back to the original --bad (founder direction, 27 Jul
   2026, superseding the coral swatch): full strength again — the lane's
   calm now comes from resting translucency (below), not from muting
   the color. filter:none keeps the dial off it, as "the beginning"
   predates the dial. */
.ic-red { color: var(--bad); filter: none; }
.ic-warn { color: var(--warn); }
.ic-lock { color: var(--on-surface-var); opacity: .75; }
/* Locked rows read as untouchable, like Studio's greyed text. */
.seg.locked-row .src, .seg.locked-row .tgt { color: var(--outline); }
/* Active pair: a very light green FILL, no border ring (founder
   direction, 25 Jul 2026 annotated screenshot). */
.seg.active { background: #eaf5ec; border-radius: 12px; cursor: default; z-index: 1; }
/* The locked active row has no box, so its Unlock strip does the
   pulling itself: 4 + 24 = the same 28, taken back out of the flow so
   it too draws into the row's reserved band. (Its first draft traded
   the row padding instead and grew the row 36px — measured, not
   reasoned.) */
.seg.active.locked-row .seg-actions { margin-top: 4px; margin-bottom: -28px; }
.seg.active.locked-row .seg-actions .rbtn { height: 24px; }
/* ---- the editor box (founder direction, 10 Aug 2026, annotated
   screenshot + recording) ----
   The green border lives on .tgt-box, a wrapper around the
   contenteditable, so the char count and the row's two verbs sit
   INSIDE the border under the text: they used to be a detached strip
   below the box, and every activation grew the row by that strip —
   "the segment pair box has to expand downwards, which causes some
   distraction."
   THE NEGATIVE SIDE MARGINS ARE WRAP PARITY, not styling. The box's
   chrome (2px border + 12px padding a side) used to be carved OUT of
   the text column, so the editable text was 28px narrower than the
   same text at rest and re-wrapped on activation (the founder's video:
   "year's" in segment 1 jumps to the next line). −14px a side = border
   + padding exactly, so the text keeps its resting x and width and the
   line breaks cannot move. The 14px the box now borrows IS the grid's
   14px column gap — the box edge lands on the neighbouring column's
   edge, not on its content. Change the box's border or padding and the
   margin must follow: margin = border + padding, per side, always. */
.seg.active .tgt-box { grid-column: 4; border: 2px solid var(--primary);
  border-radius: 10px; background: var(--surface); margin: -7px -14px -28px;
  display: flex; flex-direction: column;
  /* min-width: 0 is REQUIRED, and safe only because of the
     overflow-wrap rule on .src/.tgt above. Required: the tag strip is
     a nowrap chip run, and without this the box's min-content grew to
     the whole run — measured, the box took 658px of an 878px row and
     wrapped the source into eleven lines. Safe: with tokens breakable,
     the resting cell has no min-content floor either, so the two
     states size their columns identically. Round 4 removed this rule
     for the floor's sake and round 5 needs it back; the overflow-wrap
     line is what lets both be right. */
  min-width: 0; }
/* The −7 top margin is VERTICAL wrap... rather, position parity — the
   third face of the same rule: margin = border + padding (2 + 5), so
   the first line of the editable sits exactly where the resting text
   sits and NOTHING moves on activation. The founder proposed trading
   paddings until the movement was small; the negative margin makes it
   zero, which is what Smartcat does. Padding-top came down 8 → 5 in
   the same stroke ("decrease the padding a bit in the edit view") so
   the pull stays inside the row's 11px top band. */
.seg.active .tgt-box .tgt[contenteditable] { outline: none; border: none;
  padding: 5px 12px 2px; min-height: 1.5em; background: transparent;
  /* flex:1 — THE EDITABLE TAKES THE SLACK, so the strip sits on the
     box's bottom edge (founder direction, 10 Aug 2026: "align this at
     the bottom"). The box is a grid item and stretches to the row, so
     on a row whose SOURCE is the taller column the strip used to float
     mid-box, right under a one-line target, with dead white below it.
     Growing the EDITABLE rather than pushing the strip with
     `margin-top: auto` is the deliberate half: the slack stays part of
     the text field, so clicking anywhere in the white still places the
     caret — the rule the in-box strip's mousedown guard exists to
     protect. */
  flex: 1 1 auto; }
/* NO source/num/st compensations here any more. The morning's design
   dropped the active TARGET 10px (box chrome) and marched the source,
   the seq number and the glyphs down after it; the afternoon's
   zero-movement direction (fifth round) pins the target's text at the
   RESTING line instead, via the box's −7px top margin — so every other
   cell is already exactly where it was, in both states, locked rows
   included. The whole compensation family (and its :not(.locked-row)
   scoping, an adversarial-sweep fix that lived for about two hours)
   deletes rather than inverts. */
/* Active-row footer (founder direction, 27 Jul 2026): char count LEFT,
   the two everyday actions — copy source, confirm — RIGHT, in the row
   itself so neither needs a panel trip. */
/* grid-column 4 + the top margin serve the strip's one remaining
   OUTSIDE-the-box use: the locked row's Unlock verb (no editor, no
   box). Inside .tgt-box both are inert/overridden below. */
.seg-actions { grid-column: 4; display: flex; gap: 8px; align-items: center; margin-top: 8px; }
/* Inside the box: tight to the text above and the border around —
   "minimal space between the UIs and the translated text and minimal
   space to the border." The 12px left padding keeps the count on the
   text's own left edge. 22px buttons and 2px bottom pad since the
   fifth round: the strip band is a term of the row-height ledger on
   .seg, and every pixel here costs a pixel of resting padding on all
   thousand rows. */
.tgt-box .seg-actions { margin-top: 0; padding: 0 5px 2px 12px; }
.tgt-box .seg-actions .rbtn { height: 22px; }
/* The tag chips ride the strip's CENTER (founder direction, 10 Aug
   2026, fifth round — reversing the same morning's "it may have to
   stay" hedge): count left, chips centered, verbs right, one line, so
   a tagged row costs the same height as a plain one. flex:1 makes the
   empty span the strip's spacer on untagged rows. The overrides kill
   the OUTSIDE-the-box layout the base .tag-strip rule still describes
   (grid-column, top margin, the 20px floor). */
/* SAFE center, not center (founder report, 10 Aug 2026, with a
   recording: "I am unable to scroll all the way to the left", and the
   strip did not begin with the word Missing). Both complaints were one
   bug. Plain `center` splits the overflow BOTH ways, and the start-side
   half is unreachable — scrollLeft cannot go negative — so on a segment
   owing several tags the "Missing:" label and the first chips sat off
   the left edge with no way to reach them. `safe center` falls back to
   flex-start the moment the content overflows: the row still centres
   while the chips fit, and the label is the first thing in the strip
   the moment they do not. */
.tgt-box .seg-actions .tag-strip { flex: 1 1 auto; justify-content: safe center;
  margin: 0; min-width: 0; min-height: 0; grid-column: auto;
  /* NOWRAP is load-bearing, not cosmetic (adversarial sweep, 10 Aug
     2026): the base .tag-strip wraps, and inside the box a wrapping
     chip run grew the strip past the 28px the row reserves — so the
     one row shape that still pushed everything below it down was the
     FRESH ACTIVATION of an untranslated tagged segment, which owes
     every source tag at once. That is precisely the click this strip
     exists to serve. Chips scroll instead; the scrollbar is hidden
     because a native bar inside a 22px band is louder than the chips.
     What this costs, deliberately: with a long chip run the tail is
     off-screen until you scroll. ⌘. inserts the next owed tag without
     the strip, and the QA finding names them all, so nothing is only
     reachable here. */
  flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none; }
.tgt-box .seg-actions .tag-strip::-webkit-scrollbar { display: none; }
.charcount { font-size: 11.5px; color: var(--on-surface-var); font-variant-numeric: tabular-nums; }
.conflict { grid-column: 4; margin-top: 8px; background: var(--bad-container); color: var(--bad);
  border-radius: 10px; padding: 8px 12px; font-size: 13px;
  /* The same −14px the box borrows (margin = the box's border+padding,
     both breakpoints): the banner answers the box directly above it,
     and spanning only the bare column left it inset 14px from the green
     edges it argues with (adversarial sweep, 10 Aug 2026). */
  margin-left: -14px; margin-right: -14px; }
.conflict button { color: var(--bad); font-weight: 600; margin-right: 12px; text-decoration: underline; }
/* ---- inline tag chips (doc 03: atomic tokens — position, never edit).
   Placeholders get a DISTINCT color from formatting pairs: formatting
   is about your text, placeholders ARE content you carry verbatim. */
.tag-token { display: inline-block; font: 11.5px/1.3 ui-monospace, SFMono-Regular, Menlo, monospace;
  background: var(--surface-high); border: 1px solid var(--outline-var); border-radius: 6px;
  padding: 0 5px; margin: 0 1px; color: var(--on-surface-var); white-space: nowrap;
  user-select: none; cursor: default; }
.tag-token.ph { background: var(--warn-container); color: var(--warn); border-color: transparent; }
/* Studio's un-formatting remnant <g/>: an EMPTY pair, not content —
   dashed so it reads as a leftover anchor, not a placeholder. */
.tag-token.ghost { border-style: dashed; background: transparent; }
/* Chips carry their revision verdict (founder feedback, 28 Jul 2026):
   text-decoration does not propagate into inline-block, so a deleted
   chip looked exactly like a live one. Deleted chips go red + struck
   like the deleted text; inserted/relocated chips go the revision
   violet like the inserted text. The verdict outranks the ph tint. */
.rev-del .tag-token { color: #c62828; background: #fdecea; border-color: #c62828;
  text-decoration: line-through; }
.rev-ins .tag-token { color: #7b1fa2; background: #f3e5f5; border-color: #7b1fa2;
  text-decoration: underline; }
/* The active row's owed-tags strip: the QA gate as UI, not a report. */
.tag-strip { grid-column: 4; display: flex; flex-wrap: wrap; align-items: center; gap: 6px;
  margin-top: 8px; min-height: 20px; }
.strip-label { font-size: 11.5px; color: var(--on-surface-var); }
button.tag-token.insertable { cursor: pointer; border-style: dashed; border-color: var(--outline); }
button.tag-token.insertable:hover { background: var(--primary-container); color: var(--on-primary-container); }
/* .tag-qa was the strip's verdict text — "Tags complete ✓" and the
   non-blocking note. Both were struck on founder direction (10 Aug
   2026, fifth round: the first says nothing a clean strip doesn't, the
   second repeats the QA finding), so only the BLOCKING message still
   renders here, and it wears .warn. The .ok rule went with its text. */
.tag-qa { font-size: 11.5px; white-space: nowrap; }
.tag-qa.warn { color: var(--warn); }
/* Native formatting display (28 Jul 2026): styles come inline from
   fmtStyle (validated in lib/tags.fmtOf); links get the reading color. */
.fmt-link { color: #1558b0; }
/* Structure context (schema/012): heading/title rows wear Word's
   built-in style look — the same reason Studio shows them big and
   blue. Word-default approximations; the tooltip carries the truth. */
.seg.ctx-heading1 .src, .seg.ctx-heading1 .tgt { font-size: 1.32em; color: #2F5496; }
.seg.ctx-heading2 .src, .seg.ctx-heading2 .tgt { font-size: 1.18em; color: #2F5496; }
.seg.ctx-heading3 .src, .seg.ctx-heading3 .tgt { font-size: 1.08em; color: #1F3763; }
.seg.ctx-heading4 .src, .seg.ctx-heading4 .tgt { font-style: italic; color: #2F5496; }
.seg.ctx-title .src, .seg.ctx-title .tgt { font-size: 1.5em; color: #2F5496; }

/* Split segment ids ("4 a", "7 b a") wrap in the narrow number cell —
   Studio stacks them the same way (29 Jul 2026). */
.seg .num { white-space: normal; line-height: 1.25; word-break: keep-all; }

/* Structure rows (29 Jul 2026, Studio parity): locked, unnumbered
   scenery between segments — trans-units with no seg mrk. */
/* Scenery rows never activate (activate() skips them), so the 34px
   band the ledger reserves is dead height on them — and it SHOWS,
   because these are the one row class with a fill of its own: text
   riding 11px below the top edge and 34px above the bottom read as a
   mistake. Symmetric again; the ledger is untouched because these rows
   have no second state to be equal to. */
.seg.structure { background: var(--surface-container); cursor: default;
  padding-bottom: 11px; }
.seg.structure .src, .seg.structure .tgt { opacity: .8; }

/* A borderless pill like the command bar (founder annotation, 10 Aug
   2026, third round: "the footer needs to be in a borderless soft card
   similar like the toolbar so as to avoid the table border") — the
   border-top it carried was the last ruled line in the editor. Same
   fill, same full rounding, same --gutter margins as the pill above;
   the kbd chips inside step to WHITE because their own fill is
   --surface-low and would vanish into the pill — the exact double-duty
   the QA-card hover fix documents. */
.statusbar { display: flex; align-items: center; gap: 18px; padding: 6px 14px;
  margin: 6px var(--gutter) 8px; background: var(--surface-low); border-radius: 999px;
  font-size: 12px; color: var(--on-surface-var);
  font-variant-numeric: tabular-nums; flex: 0 0 auto; }
/* ONE LINE, structurally (founder report, 17 Aug 2026: the footer had
   wrapped to two — the counter breaking inside itself, the legend
   spilling its tail). The counter and the pair never wrap or shrink;
   the LEGEND is the one flexible item and CLIPS from its tail end when
   the row runs out — it is teaching furniture, and a clipped last
   chord costs less than a second line under every segment. Below the
   split it is display:none already, so the clip only governs the
   widths in between. */
.statusbar #sb-pos { white-space: nowrap; flex: 0 0 auto; }
.statusbar .kbd-legend { white-space: nowrap; overflow: hidden;
  min-width: 0; flex: 0 1 auto; }
.statusbar kbd { background: var(--surface); }

/* Segment-details hover card (founder request, 28 Jul 2026): the
   Trados-style structured tooltip on the status cell — the native
   title's fixed ~1s delay and flat text were rejected. */
#hover-tip { position: fixed; z-index: 40; background: var(--surface);
  border: 1px solid var(--outline-var); border-radius: 10px;
  box-shadow: 0 12px 32px -14px rgba(22, 59, 44, .4);
  padding: 10px 12px; font-size: 12px; max-width: 300px;
  pointer-events: none; line-height: 1.55; }
#hover-tip h6 { font-size: 10px; letter-spacing: .8px; text-transform: uppercase;
  color: var(--on-surface-var); margin: 0 0 4px; font-weight: 650; }
#hover-tip .tip-sec + .tip-sec { margin-top: 8px; padding-top: 8px;
  border-top: 1px solid var(--outline-var); }
#hover-tip .row { display: flex; gap: 10px; }
#hover-tip .k { color: var(--on-surface-var); flex: none; min-width: 72px; }
#hover-tip .v { color: var(--on-surface); }
/* The TERMBASE card (founder direction, 10 Aug 2026). Scoped to .tb-card
   rather than changing #hover-tip h6, because that heading is shared
   with the segment-details and project cards, where the uppercase label
   is a CATEGORY ("FILE DETAILS") and belongs shouted. Here the heading
   is the term itself, so it keeps the case the source wrote — the
   letter-spacing goes with the transform, since tracking tuned for caps
   reads as a typo on mixed case. */
#hover-tip .tb-card h6 { text-transform: none; letter-spacing: 0;
  font-size: 12.5px; font-weight: 600; color: var(--on-surface); margin-bottom: 5px; }
#hover-tip .tb-r { color: var(--on-surface); }
/* The ban has no label any more, so the styling carries it — struck
   through in --bad, the same mark the Termbase panel uses, so one
   vocabulary covers both surfaces. */
#hover-tip .tb-r.no { color: var(--bad); text-decoration: line-through; }
/* Italic is what says "commentary, not a rendering" now that the "Note"
   label is gone. */
#hover-tip .tb-n { font-style: italic; color: var(--on-surface-var); margin-bottom: 3px; }
#hover-tip .tb-r + .tb-r { margin-top: 3px; }

/* ---- narrow layout (founder request, 28 Jul 2026) ----
   Below 900px the fixed-width panel would crush the grid into
   overlap soup (seen in the founder's recordings at ~400px). The
   panel becomes an OVERLAY: the rail stays pinned on the right (the
   grid reserves its --rail-w), the expanded panel floats over the grid
   instead of squeezing it. The keyboard legend is a desktop
   affordance and hides; grid columns tighten. */
.workspace { position: relative; }
@media (max-width: 900px) {
  .grid-area { margin-right: var(--rail-w); }
  /* The overlay's ground and shadow moved from the container to the
     CARD when the panel became one (10 Aug 2026): the container also
     holds the transparent rail, and painting it solid put a white strip
     behind the floating capsules. The card carries its own gray, so
     over the grid it only needs the shadow. */
  /* The container's width restates the card's clamp, plus the rail —
     NOT the base --panel-w and NOT width:auto. The base 386px left a
     dead transparent gap between the clamped card and the rail at
     widths below ~400px (the sliver the clamp exists for sat behind
     the container's empty left edge, unreachable); width:auto was
     worse — a flex container's intrinsic width takes the flex:1 card
     at its CONTENT size, so the container went full-bleed and pushed
     the rail 16px off the window edge. min() says exactly what the
     card will do, so the container always ends where the rail does.
     .workspace .panel, not .panel: this media block sits EARLIER in
     the file than the base .panel rule, so at equal specificity the
     base width would win — a trap this rule is the first to hit,
     because until 10 Aug 2026 it never set a width and leaned on the
     base's. Measured, not assumed: as plain .panel the declaration was
     silently dead and the container clamped to 100vw instead. */
  .workspace .panel { position: absolute; right: 0; top: 0; bottom: 0; z-index: 8;
    width: calc(min(330px, 100vw - var(--rail-w) - 16px) + var(--rail-w));
    max-width: 100vw; }
  /* The clamp = the rail plus 16px of visible grid — the escape
     affordance at phone widths. It was a literal 60 (the OLD 44px rail
     + 16) and survived the 44→56 retune untouched, leaving a 4px
     sliver below ~390px; the token keeps it honest (adversarial
     sweep, 10 Aug 2026). */
  .panel .panel-main { width: 330px; max-width: calc(100vw - var(--rail-w) - 16px);
    box-shadow: 0 0 24px rgba(0, 0, 0, .14); }
  .kbd-legend { display: none; }
  .statusbar { gap: 10px; }
  .seg { grid-template-columns: 34px 1fr 26px 1fr 26px; gap: 0 8px; }
  /* The 34px bottom is the row-height ledger's reserved strip band —
     see .seg's base comment. It survives the narrow re-derivation
     untouched because the strip's HEIGHT doesn't change with the gap;
     only the side chrome does. (The first draft wrote `padding: 11px
     8px` here and silently zeroed the ledger below 900px.) */
  .seg { padding: 11px 8px 34px; }
  /* Wrap parity re-derived for the tighter gap: the box may only borrow
     the 8px gap, so its side chrome must total 8 — 2px border + 6px
     padding. Same rule as the base: margin = border + padding, always.
     Every rule that copies the box's chrome re-derives WITH it — the
     strip's text-left-edge padding and the conflict banner's negative
     margins (adversarial sweep, 10 Aug 2026: the first draft re-derived
     the box and forgot the strip, so the charcount sat 6px right of
     the text it claims to align with). */
  .seg.active .tgt-box { margin: -7px -8px -28px; }
  .seg.active .tgt-box .tgt[contenteditable] { padding: 5px 6px 2px; }
  .tgt-box .seg-actions { padding-left: 6px; }
  .conflict { margin-left: -8px; margin-right: -8px; }
  .toolbar { padding: 8px; gap: 6px; }
  /* The two rules that used to live here — the .job ellipsis and
     .crumbs{min-width:0} — moved out to the base rules on 10 Aug 2026.
     Narrow was never the only width where the row overflowed; it was
     only the width where somebody had looked. Nothing replaces them
     here: the containment is unconditional now. */
  /* The expanded panel FLOATS over the grid here, and both header rows
     span the grid's full width — so whatever sits at their trailing end
     was underneath it and could not be clicked. That was survivable
     while the bar ended in Change case; moving Confirm to the end of the
     bar (7 Aug 2026) put the founder's own button under the overlay at
     every width below ~913px, and the two navigation arrows below
     ~852px. Measured, not reasoned: at 880px the Status group spanned
     491..539 against a panel edge at 506.
     Reserve the floating width so the rows WRAP instead of hiding.
     330 = .panel-main above; .grid-area's margin already reserves the
     --rail-w rail. Only while the panel is EXPANDED — collapsed, there
     is nothing but the rail and the space would be dead. */
  #view-editor:not(.panel-collapsed) .cmdbar,
  #view-editor:not(.panel-collapsed) .toolbar { padding-right: 338px; }
}

/* ---- right panel ---- */
/* Rail at the window's edge (10 Aug 2026; it was on the panel's LEFT —
   between grid and content — from 26 Jul to 10 Aug): one icon per view,
   the chevron collapses the whole panel to just the rail. --panel-w =
   330px of content + --rail-w, so content width is unchanged from the
   original design; the token is declared in :root because the editor
   header's alignment measures against the panel's left edge. */
/* No border-left since the same 10 Aug pass that unruled the grid: the
   panel's edge is now the CARD's edge (.panel-main below), a change of
   fill rather than a drawn line. The header's progress reading still
   ends exactly there — the card keeps no left margin so the card edge
   IS the --panel-w edge the calc measures to. */
.panel { width: var(--panel-w); flex: none;
  display: flex; flex-direction: row; min-height: 0; }
.panel.collapsed { width: auto; }
.panel.collapsed .panel-main { display: none; }
/* THE RAIL IS OUTERMOST now (founder direction, 10 Aug 2026, from a
   Google Slides recording): panel content opens to the LEFT of the
   icons, the way Slides' side panels open left of its floating
   buttons — the icons keep one home at the window's edge whether or
   not a panel is out. Moved IN THE MARKUP (.panel-main before .prail
   in index.html), not with `order`: the first draft used `order: 1`,
   and the adversarial sweep called it — flex order moves pixels, not
   the tab sequence, so keyboard focus ran grid → rail at the far edge
   → BACK LEFT into the panel content, a zigzag on every pass (WCAG
   2.4.3). DOM order and visual order agree again.
   And it is TRANSPARENT — no strip, no border-right. The buttons sit
   in two floating capsules (.prail-cap below) on whatever ground is
   behind them, which is the "no table borders, round design" of the
   annotation. The strip's own scrollbar went with it; short windows
   scroll the capsule content via the rail's overflow. */
.prail { flex: 0 0 var(--rail-w); display: flex; flex-direction: column;
  align-items: center; gap: 8px; padding: 6px 0; overflow-y: auto; }
.prail-btn, #panel-tabs .tab { width: 34px; height: 34px; border-radius: 50%;
  display: grid; place-items: center; color: var(--on-surface-var);
  padding: 0; flex: none; margin: 0; border: none; }
.prail-btn:hover, #panel-tabs .tab:hover { background: var(--surface-high); }
.prail svg { width: 20px; height: 20px; }
.panel.collapsed .prail-btn svg { transform: rotate(180deg); }
/* The chevron floats alone above the capsules, a circle of the same
   fill — Slides gives its lone controls the same treatment. */
.prail-btn { background: var(--surface-low); }
#panel-tabs { display: flex; flex-direction: column; gap: 8px; border: none;
  /* flex: 1 so .prail-grow inside has height to spend: settings' capsule
     rides the rail's bottom (founder direction, 6 Aug 2026). */
  flex: 1 1 auto; min-height: 0; }
/* A capsule: the pill the icons live in. Fill and radius do all the
   work — no border, same as the command bar's pill. */
.prail-cap { display: flex; flex-direction: column; align-items: center; gap: 2px;
  padding: 5px; background: var(--surface-low); border-radius: 999px; flex: none; }
#panel-tabs .tab[aria-selected="true"] { background: var(--primary-container);
  color: var(--on-primary-container); border: none; }
.prail-sep { width: 22px; height: 1px; background: var(--outline-var); margin: 5px 0; flex: none; }
/* The assistant's rail icon USED to be the one allowed a color of its
   own: clairetrans green (#0d6b64), because the assistant was a persona
   with the agency's brand. The persona was retired 31 Jul 2026 and that
   rationale went with it, so the override is gone and the icon now wears
   --on-surface-var resting and the --primary-container pill selected,
   exactly like its neighbours.
   The obvious replacement — the Syntag wordmark amber #BE8118 — was
   measured and rejected. It clears 3:1 resting on --surface-low (3.17:1)
   but FAILS on hover over --surface-high (2.84:1) and on its own
   selected wash (2.76:1), where the old green was 6.08:1. Darkening it
   far enough to pass everywhere with margin walks it straight into
   --warn (#8a5300), the QA warning brown — so amber cannot be both
   accessible here and semantically free.
   That is the general finding, and the reason this is now uncolored:
   the rail's color vocabulary is fully spent on QA meaning — amber
   warns, blue notes, red errs, green is selection — so there is no free
   color for an assistant that is no longer a brand. Giving it one again
   means minting a palette token deliberately, which is a founder
   decision, not a CSS edit. `.tab-assistant` stays on the button as the
   hook for that day (renamed from .tab-claire, 5 Aug 2026 — the
   deferred claire→assistant sweep). */
/* THE PANEL IS A SOFT GRAY CARD (founder direction, 10 Aug 2026,
   annotated screenshot: "enclose the panel on a gray box with rounded
   corners, just like in Google Slides"). Fill and radius, no border —
   the same grammar as the command-bar pill and the rail capsules, at
   panel scale. Margins top and bottom float it between the topbar and
   the statusbar; the LEFT margin stays 0 on purpose, because the card's
   left edge is the alignment edge the header's progress reading ends
   on, and pushing the card right would quietly retire that rule. Air on
   the right is the rail's own 6px. overflow:hidden clips the scrolling
   content to the rounded corners; the footer's upward menu stays inside
   the card, so nothing is lost to the clip. */
.panel-main { flex: 1; min-width: 0; display: flex; flex-direction: column; min-height: 0;
  background: var(--surface-low); border-radius: 16px;
  /* 2px top = the command-bar pill's own margin-top, so the card's top
     edge and the pill's top edge share one line (founder annotation,
     10 Aug 2026, third round: "the soft box border should align with
     the toolbar"); 8px bottom = the statusbar pill's bottom margin,
     same rhyme at the other corner. */
  margin: 2px 0 8px;
  overflow: hidden; }
/* On the gray ground the panel's cards go WHITE and lose their outline —
   Slides' interior grammar (light tiles on the panel's gray). Scoped to
   the editor panel: .card elsewhere (the QA page) still sits on the
   page's white and keeps its line. (An .rsection twin of this rule was
   written and deleted the same day: nothing renders .rsection since the
   ribbon left the panel on 6 Aug — the adversarial sweep caught the
   dead rule before it shipped.)
   Kept at 0,2,0 specificity ON PURPOSE: the QA severity borders
   (.card.qa.sev-*, 0,3,0) must keep beating this — a severity border is
   meaning, not table furniture. */
.panel-main .card { background: var(--surface); border-color: transparent; }
/* INTERACTIVE tiles keep their edge (adversarial sweep, 10 Aug 2026):
   a TM candidate is click-to-insert and a data-qa-jump card is
   role=button, and after the transparent-border rule above their whole
   boundary was the white-on-gray fill — 1.05:1, nothing (WCAG 1.4.11
   wants 3:1 where fill is the only indicator). Static cards stay
   fill-only; a clickable one keeps the hairline. Same 0,2,0 specificity
   as the transparent rule, later in the file, so it wins there and
   still loses to the sev-* borders. */
.panel-main .cand, .panel-main [data-qa-jump] { border-color: var(--outline-var); }
/* THE HOVER TINT STEPS PAST THE GROUND (adversarial sweep, 10 Aug
   2026, the one high finding): every hover fill in the panel used
   --surface-low, which was a tint on the old white panel and is now
   THE GROUND ITSELF — so hovering a QA card repainted it the exact
   color behind it (a dismissed card dissolved bodily under the
   cursor, precisely when its click affordance should read), and the
   QA footer's and Changes view's buttons hovered to a face identical
   to the card they sit on. One step down the ramp is the ground; two
   is a tint again. :not([aria-expanded="true"]) keeps the open view
   dropdown's --primary-container fill from being repainted mid-hover. */
.panel-main .card.qa[data-qa-jump]:hover { background: var(--surface-container); }
.panel-main .rbtn:hover:not([aria-expanded="true"]):not(.primary) { background: var(--surface-container); }
/* Command views (Home/Review/Advanced) keep the Trados grouping —
   buttons above, group caption below — rendered as sections in the one
   scrollable panel body like every other view. */
.rsection { background: var(--surface); border: 1px solid var(--outline-var);
  border-radius: 10px; padding: 8px 10px 3px; margin-bottom: 10px; }
.rbtns { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
.rbtn { height: 28px; padding: 0 11px; border-radius: 8px; font-size: 12.5px;
  font-weight: 500; border: 1px solid var(--outline-var); color: var(--on-surface);
  background: var(--surface); display: inline-flex; align-items: center; gap: 6px; }
.rbtn:hover { background: var(--surface-low); }
.rbtn.primary { background: var(--primary); border-color: transparent; color: var(--on-primary); }
.rbtn.primary:hover { filter: brightness(1.08); }
.rbtn kbd { font-size: 10px; padding: 0 4px; opacity: .8; background: transparent;
  border-color: currentColor; color: inherit; }
/* Icon-only command buttons (founder annotation, 26 Jul 2026): the
   label lives in the hover tooltip; the section caption names the group. */
.rbtn.icon { width: 32px; padding: 0; justify-content: center; }
.rbtn.icon svg { width: 18px; height: 18px; }
.rname { font-size: 10px; font-weight: 600; letter-spacing: .8px; text-transform: uppercase;
  color: var(--on-surface-var); text-align: center; margin-top: 5px; }
.rgoto { display: inline-flex; align-items: center; gap: 6px; }
.rgoto input { width: 58px; height: 28px; padding: 0 8px; border: 1px solid var(--outline-var);
  border-radius: 8px; font-size: 12.5px; background: var(--surface); }
.rgoto input:focus { outline: 2px solid var(--primary); border-color: transparent; }
.tabs { display: flex; border-bottom: 1px solid var(--outline-var); }
.tab { flex: 1; padding: 11px 0 9px; text-align: center; font-size: 13.5px; font-weight: 500;
  color: var(--on-surface-var); border-bottom: 2px solid transparent; margin-bottom: -1px; }
.tab[aria-selected="true"] { color: var(--primary); border-bottom-color: var(--primary); }
.panel-scroll { flex: 1; overflow-y: auto; padding: 14px; }
/* Born mirroring the grid's statusbar (founder direction, 28 Jul
   2026: same top border, pinned below the scrolling list). The
   statusbar became a borderless pill on 10 Aug and this deliberately
   did NOT follow: the class is shared with the TM and TB pages, whose
   footers still sit on white pages, and inside the editor's gray card
   the line is doing a job — content scrolls under this bar, and the
   border is what says so. */
.panel-footer { display: flex; align-items: center; gap: 8px; padding: 8px 14px;
  border-top: 1px solid var(--outline-var); flex: 0 0 auto; font-size: 12px;
  color: var(--on-surface-var); }
/* The view dropdown opens UPWARD — the footer sits at the bottom, so
   a below-the-button menu would clip out of the viewport. */
.panel-footer .status-menu { top: auto; bottom: calc(100% + 4px); }
.panel-footer .rbtn svg { width: 16px; height: 16px; margin: 0 -4px 0 -2px; }
.panel-footer .rbtn[aria-expanded="true"] { background: var(--primary-container); }
.card { border: 1px solid var(--outline-var); border-radius: 12px; padding: 12px 14px; margin-bottom: 10px; }
.card-head { display: flex; align-items: center; gap: 8px; margin-bottom: 6px; }
.score { font-size: 12px; font-weight: 600; padding: 1px 8px; border-radius: 6px;
  background: var(--primary-container); color: var(--on-primary-container); font-variant-numeric: tabular-nums; }
.score.exact { background: var(--good-container); color: var(--good); }
.card-head .kind { font-size: 12px; color: var(--on-surface-var); }
.card .pair .s { color: var(--on-surface-var); font-size: 13.5px; margin-bottom: 3px; }
.card .pair .t { font-size: 13.5px; }
/* The card border follows SEVERITY (30 Jul 2026): a red frame around an
   informational note reads as an error no matter what the icon says.
   A dismissed card carries no sev- class and keeps the neutral border. */
.card.qa.sev-error { border-color: var(--bad); }
.card.qa.sev-warning { border-color: var(--warn-mark); }
.card.qa .kind { color: var(--bad); font-weight: 500; }
.card .note { font-size: 12px; color: var(--on-surface-var); margin-top: 6px; }
/* ---- comments (panel-based, never a popup) ---- */
.comment-glyph { cursor: pointer; }
.comment-form { display: flex; flex-direction: column; gap: 8px; margin-bottom: 14px; }
.cf-row { display: flex; gap: 10px; }
.cf-row .cf-label { flex: 1; }
.cf-sect { margin: 14px 0 6px; font-weight: 600; }
.src .cmt-hl { cursor: pointer; }
.cf-label { font-size: 12px; color: var(--on-surface-var); display: flex; flex-direction: column; gap: 4px; }
.comment-form select, .comment-form textarea { font: inherit; font-size: 13px;
  border: 1px solid var(--outline-var); border-radius: 8px; padding: 7px 10px;
  background: var(--surface); color: var(--on-surface); }
.comment-form textarea { resize: vertical; }
.comment-form select:focus, .comment-form textarea:focus { outline: 2px solid var(--primary); border-color: transparent; }
.comment-form .btn-filled { height: 32px; align-self: flex-start; font-size: 13px; }
.sev-chip { font-size: 10.5px; font-weight: 600; padding: 1px 8px; border-radius: 6px; }
.sev-chip.sev-info { background: var(--surface-high); color: var(--on-surface-var); }
.sev-chip.sev-warning { background: var(--warn-container); color: var(--warn); }
.sev-chip.sev-error { background: var(--bad-container); color: var(--bad); }
.comment-card.sev-error { border-color: var(--bad); }
.comment-card.sev-warning { border-color: var(--warn-mark); }
/* Info completes the set (5 Aug 2026, flagged 2 Aug): same token the QA
   card's info border wears, so "info" reads identically in both places. */
.comment-card.sev-info { border-color: var(--info-container); }
.comment-del { color: var(--on-surface-var); font-size: 12px; padding: 2px 6px; }
.comment-del:hover { color: var(--bad); }

.jr { display: flex; gap: 10px; padding: 7px 2px; font-size: 13px; border-bottom: 1px solid var(--outline-var); }
.jr .at { color: var(--on-surface-var); font-size: 11.5px; white-space: nowrap; }
.panel-empty { color: var(--on-surface-var); font-size: 13px; padding: 8px 2px; }
/* ---- Concordance and Termbase panels (10 Aug 2026) ----
   Both borrow the shapes already in the panel — .card/.pair for a hit,
   .info-sec for a heading, .dialog-input for the box — so a concordance
   hit reads exactly like the TM suggestion it is. Only the pieces with
   no existing equivalent are declared here.
   The hit mark is the same --warn-container the TM page, the search
   results and the property rows already use: one colour means "this is
   the bit you searched for" on every surface that highlights. */
/* A termbase hit in the SOURCE (founder direction, 10 Aug 2026). Rounded
   and tinted, NOT a chip: a tag token is an object you move, this is a
   run of ordinary source text that happens to be ruled on — so the text
   keeps its own colour and only the ground changes, and the radius is
   the small one the QA marks use rather than the chip's box.
   Clickable, because clicking inserts the rendering at the caret; the
   pointer says so before the hover card explains it.
   LITERAL SWATCHES, NOT --primary-container (founder direction, later
   the same day: "a bit darker so that there is more contrast with the
   green background in the active segment"). The mark now only ever
   appears on the ACTIVE row, whose ground is #eaf5ec — and measured
   against it, --primary-container was 1.15:1, which is why it read as
   barely there. These are tuned to that one background rather than to
   the page, so a token whose job is "container tint on white" is the
   wrong thing to reach for; the family it belongs to is #eaf5ec, which
   is itself a literal for the same reason.
   Measured on #eaf5ec: rest 1.55:1, hover 1.85:1 — a visible step and
   then a further one. The row's ink stays 8.7:1 on the rest tint, so
   nothing is traded for the contrast. */
.tb-hit { background: #96d2a9; border-radius: 5px;
  padding: 0 2px; margin: 0 -1px; cursor: pointer; }
.tb-hit:hover { background: #7ac492; }
/* Every rendering banned: there is nothing to insert, so it must not
   invite the click. Amber rather than green — this is a warning about
   the source, not an offer. Darkened in the same pass and for a worse
   reason: --warn-container measured 1.01:1 against the active row, so
   the one mark that says "do not use any of these" was invisible
   exactly where it is shown. #f5cf72 is 1.34:1 — lower than the green
   above, but amber against green differs in HUE as well as luminance,
   which a contrast ratio does not count. */
.tb-hit.all-forbidden { background: #f5cf72; cursor: default; }
.tb-hit.all-forbidden:hover { background: #f5cf72; }
/* The rendering chooser (founder catch, 10 Aug 2026): a term with two
   blessed renderings cannot be inserted by a click that picks one.
   position:fixed and body-mounted like #hover-tip, because the mark it
   hangs off lives in a virtualized row that scrolls and rebuilds. */
#term-menu { position: fixed; z-index: 45; display: flex; flex-direction: column;
  min-width: 160px; max-width: 320px; padding: 4px;
  background: var(--surface); border: 1px solid var(--outline-var);
  border-radius: 10px; box-shadow: var(--shadow-lg); }
#term-menu .cmd-menu-item { display: flex; align-items: baseline; gap: 10px;
  justify-content: space-between; }
#term-menu .tm-note { font-size: 11px; color: var(--on-surface-var);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 140px; }
/* The arrow-key highlight. A CLASS, not :focus — ⌘; drives this list
   while the caret stays in the target, so no menu item is ever focused
   and :focus would highlight nothing. Same fill the command menu's
   hover uses, so pointer and keyboard land on one appearance. */
#term-menu .cmd-menu-item.on { background: var(--surface-low); }
.conc-head { margin-bottom: 10px; }
.conc-count { font-size: 11.5px; color: var(--on-surface-var); margin: 0 2px 8px; }
#panel-body mark { background: var(--warn-container); color: inherit; border-radius: 3px; }
/* A term is a PAIR, not a card: the panel can hold dozens and boxing
   each one turns the list into a grid of tiles. The rule between them is
   the same hairline the info rows use. */
.tb-term { padding: 7px 2px; border-top: 1px solid var(--outline-var); font-size: 13.5px; }
.tb-term:first-of-type { border-top: 0; }
.tb-term .s { color: var(--on-surface-var); }
.tb-term .t { color: var(--on-surface); }
.tb-term .note { font-size: 12px; color: var(--on-surface-var); margin-top: 3px; }
/* Forbidden entries wear the error colour on the TARGET only — the
   source term is not the problem, the rendering is (lib/terms.js draws
   the same distinction when it decides what to flag). */
.tb-term.forbidden .t { color: var(--bad); text-decoration: line-through; }
.tb-no { font-size: 11px; color: var(--bad); text-decoration: none;
  background: var(--bad-container); border-radius: 6px; padding: 1px 6px; margin-left: 4px; }
/* The termbase search row (founder direction, 10 Aug 2026). The box owns
   the top of the panel and the clear button sits beside it, not inside:
   an affordance floating over the text it clears is a native decoration
   the browsers disagree about, and this one has to be found by someone
   who has just been told the list is the wrong list.
   align-items: flex-end because .dialog-input carries a 4px top margin
   of its own — centring would hang the button that far above the box. */
.tb-head { display: flex; align-items: flex-end; gap: 4px; margin-bottom: 10px; }
.tb-head .dialog-input { flex: 1; min-width: 0; }
.tb-head .rbtn.icon { flex: none; height: 34px; width: 34px; color: var(--on-surface-var); }
.tb-head .rbtn.icon:hover { color: var(--on-surface); }
/* [hidden] is a UA rule of display:none, and .rbtn.icon's own display
   outranks it — so the empty-box state drew a clear button with nothing
   to clear, and `el.hidden === true` said otherwise. Caught by a
   screenshot, not by the property read (doc 03: verify the geometry). */
.tb-head .rbtn.icon[hidden] { display: none; }
/* Chrome draws its own ✕ in a type=search field. Ours is the one that
   keeps ED.tbQ in step, so the native twin would be a second control
   doing nearly the same thing a few pixels away. */
#tbp-q::-webkit-search-cancel-button { display: none; }

/* Claire panel: the doc 08 §5 token meter (always visible, never a
   surprise overage), the document brief, and the live narration log. */
.meter { margin: 6px 0 2px; }
.meter-bar { height: 6px; border-radius: 3px; background: var(--surface-high); overflow: hidden; }
.meter-fill { height: 100%; border-radius: 3px; background: #0d6b64; transition: width .3s; }
.meter-fill.over { background: var(--bad); }
.meter + .note, .meter .note { margin-top: 3px; }
.assistant-chips { display: flex; flex-wrap: wrap; gap: 5px; margin: 2px 0 8px; }
.assistant-terms { margin-top: 8px; border-top: 1px solid var(--outline-var); padding-top: 6px; }
.assistant-terms .term { font-size: 12.5px; padding: 2px 0; }
.assistant-terms .ts { color: var(--on-surface-var); }
.assistant-terms .tt { font-weight: 600; }
.assistant-log { max-height: 300px; overflow-y: auto; }
.rbtn[disabled] { opacity: .45; pointer-events: none; }
/* Toggle buttons in the ribbon (the Display section) fill when on,
   the same convention as the filter chips. */
.rbtn[aria-pressed="true"] { background: var(--primary-container);
  color: var(--on-primary-container); border-color: transparent; }

/* The toast tracks the ink token rather than a literal near-black, so it
   moves with the palette (6 Aug 2026 softening) instead of becoming the
   one surface still tuned to the old one. */
.toast { position: fixed; left: 50%; bottom: 26px; transform: translateX(-50%);
  background: var(--on-surface); color: var(--surface); border-radius: 10px; padding: 10px 18px;
  font-size: 13.5px; max-width: 70vw; z-index: 9; }
.toast.error { background: var(--bad); }
/* (The sticky completion toast's rules sat here for part of 16 Aug
   2026; the founder chose the centered dialog on seeing it, and the
   dialog brought its own chrome.) */

/* File status in the editor header: a LABEL since 16 Aug 2026
   (schema/044) — the status is derived and nobody sets it. */
.file-status-label { font-size: 13px; color: var(--on-surface-var); white-space: nowrap; }

/* Modal (30 Jul 2026): used only where a choice must be made. Toasts
   still carry everything informational. */
.dialog-back { position: fixed; inset: 0; z-index: 60; display: grid; place-items: center;
  background: rgba(22, 59, 44, .38); }
.dialog { background: var(--surface); border-radius: 16px; box-shadow: var(--shadow-lg);
  padding: 22px 24px; width: min(460px, calc(100vw - 40px)); }
.dialog h2 { font-size: 17px; font-weight: 600; margin: 0 0 10px; }
.dialog p { margin: 0 0 8px; font-size: 14px; color: var(--on-surface-var); }
.dialog p.warn-line { color: var(--warn); font-weight: 500; }
/* The bulk dialogs' "Skipped:" breakdown. Negative top margin closes the
   8px the heading <p> above it already spends, so the reasons read as
   that heading's list rather than as a second paragraph. */
.dialog ul.dialog-list { margin: -4px 0 8px; padding-left: 20px;
  font-size: 14px; color: var(--on-surface-var); }
.dialog ul.dialog-list li { margin: 0 0 2px; }
.dialog-acts { display: flex; justify-content: flex-end; gap: 8px; margin-top: 18px;
  flex-wrap: wrap; }
/* An action marked `full` takes the whole row (founder direction,
   18 Aug 2026: the delete dialog's primary spells out its consequence
   and needs the width); `grow` actions split their row between them
   so the line fills edge to edge. */
.dialog-acts .dialog-act-full { flex-basis: 100%; }
/* Grown buttons center their label: .rbtn is inline-flex and a wide
   flex-start button reads as a misaligned bar, not a button. */
.dialog-acts .dialog-act-grow { flex: 1; justify-content: center; }

/* ---- AI engine settings page (BYOK, 18 Aug 2026) ---- */
.ai-row { display: flex; align-items: center; gap: 10px; padding: 12px 14px;
  border: 1px solid var(--outline-var); border-radius: 12px; margin-bottom: 8px;
  background: var(--surface); }
.ai-row.open { border-bottom-left-radius: 0; border-bottom-right-radius: 0;
  margin-bottom: 0; }
.ai-pick { display: flex; align-items: center; gap: 10px; cursor: pointer; }
.ai-pick .nm { font-weight: 500; }
.ai-chip { font-size: 11.5px; padding: 2px 8px; border-radius: 999px;
  border: 1px solid var(--outline-var); color: var(--on-surface-var); }
.ai-chip.ok { color: var(--primary); border-color: var(--primary); }
.ai-chip.bad { color: var(--error, #b3261e); border-color: currentColor; }
.ai-card { border: 1px solid var(--outline-var); border-top: none;
  border-radius: 0 0 12px 12px; padding: 14px 16px 12px; margin-bottom: 8px;
  background: var(--surface-low); }
.ai-card .mem-row { margin-bottom: 8px; }
.ai-actions { display: flex; align-items: center; gap: 8px; margin: 10px 0 6px; }
.ai-warn { color: var(--error, #b3261e); }
/* ---- the special-character picker (18 Aug 2026) ----
   Ten fixed columns everywhere (grid, customs, recents) so the three
   sections read as one instrument and the recents row never reflows as
   it fills — the empty slots are disabled placeholder cells. */
.char-pick { margin-top: 4px; }
.char-grid { display: grid; grid-template-columns: repeat(10, 1fr); gap: 4px; margin: 6px 0 2px; }
.char-add { margin-top: 2px; }
.char-custom-wrap { margin-top: 2px; }
/* The recents row sits above the close-choice; its own bottom margin
   keeps the checkbox line from crowding the last cells. */
.char-recent { margin-bottom: 2px; }
.char-close-after { accent-color: var(--primary); }
.char-cell { height: 32px; min-width: 0; display: flex; align-items: center; justify-content: center;
  border: 1px solid var(--outline-var); border-radius: 7px; background: var(--surface);
  color: var(--on-surface); font-size: 16px; line-height: 1; padding: 0; cursor: pointer; }
.char-cell:hover { background: var(--surface-low); }
.char-cell.sel { outline: 2px solid var(--primary); outline-offset: -1px; }
.char-cell.empty { background: var(--surface-low); border-style: dashed; cursor: default; }
.char-sec { font-size: 11px; font-weight: 600; letter-spacing: 1px; text-transform: uppercase;
  color: var(--on-surface-var); margin: 12px 0 2px; }
.char-add-toggle { width: 100%; justify-content: center; margin-top: 10px;
  border: 1px solid var(--outline-var); }
.char-add-row { display: flex; gap: 8px; align-items: flex-end; }
.char-add-row .dialog-input { flex: 1; min-width: 0; }
/* The remove control rides its cell's top-right corner and appears on
   hover — the cell stays a clean glyph until the pointer asks. */
.char-custom-cell { position: relative; display: block; min-width: 0; }
.char-custom-cell .char-cell { width: 100%; }
.char-rm { position: absolute; top: -5px; right: -5px; width: 16px; height: 16px;
  border-radius: 50%; border: none; background: var(--on-surface-var); color: var(--surface);
  font-size: 11px; line-height: 16px; padding: 0; cursor: pointer; display: none; }
.char-custom-cell:hover .char-rm { display: block; }
.char-close-row { display: flex; gap: 8px; align-items: center; margin-top: 14px;
  font-size: 13px; color: var(--on-surface-var); cursor: pointer; }

/* QA severity as an ICON (30 Jul 2026, founder direction): the
   "Error"/"Warning" chip and the check name are gone from the card —
   the explanation carries the meaning, and the machine name lives in
   this icon's tooltip. */
.sev-ico { width: 17px; height: 17px; flex: none; }
.sev-ico.sev-error { color: var(--bad); }
.sev-ico.sev-warning { color: var(--warn-mark); }
.sev-ico.sev-info { color: var(--info); }
.card.qa.sev-info { border-color: var(--info-container); }

/* ================= WELCOME dashboard (30 Jul 2026) =================
   Concept A of the three mockups in design/, chosen by the founder: a
   tile per destination carrying the one number that makes it worth a
   look, above a Continue row that is the fastest way back into work.
   The bar/pct/progress-cell classes are the FILES TABLE's, reused
   verbatim — the same progress reads the same everywhere. */
.page { flex: 1; min-height: 0; overflow-y: auto; }
/* A fixed measure centred in the window, so at 1440px the margin is
   the design and not leftover space. */
.wrap { max-width: 1120px; margin: 0 auto; padding: 34px 32px 48px; }
.page-head { display: flex; align-items: flex-start; gap: 12px; margin-bottom: 30px; }
.page-head h1 { font-size: 20px; font-weight: 500; margin: 0; letter-spacing: -.2px; }
.page-head .sub { margin: 3px 0 0; font-size: 13px; color: var(--on-surface-var); }
/* Section captions borrow the files-table header type, so a caption
   here reads as the same RANK of label it does everywhere else. */
.cap { font-size: 11px; letter-spacing: .8px; text-transform: uppercase;
  color: var(--on-surface-var); font-weight: 600; margin: 0 0 10px; }

.resume { margin-bottom: 34px; }
.resume-row { display: flex; align-items: center; gap: 12px; padding: 10px 12px;
  margin: 0 -12px; border-radius: 12px; text-decoration: none; color: inherit; cursor: pointer; }
.resume-row:hover { background: var(--surface-low); }
.resume-row:focus-visible { outline: 2px solid var(--primary); outline-offset: -2px; }
.resume-row svg { width: 20px; height: 20px; color: var(--on-surface-var); flex: none; }
/* overflow:hidden is what lets a nowrap flex item shrink at all; without
   it the line has a cliff at one breakpoint instead of degrading. */
.resume-row .fname { font-weight: 500; min-width: 0; overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; }
.resume-row .meta { font-size: 13px; color: var(--on-surface-var); white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis; }

.grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 14px; }
.tile { display: flex; flex-direction: column; min-height: 152px;
  padding: 20px 22px; border: 1px solid var(--outline-var); border-radius: 14px;
  text-decoration: none; color: inherit; background: var(--surface); }
.tile:hover { background: var(--surface-low); }
.tile:focus-visible { outline: 2px solid var(--primary); outline-offset: -1px; }
.tile > svg { width: 22px; height: 22px; color: var(--on-surface-var); }
.tile .name { margin-top: auto; font-size: 14px; }
.tile .figure { display: flex; align-items: baseline; gap: 7px; margin-top: 4px; }
.tile .figure b { font-size: 27px; font-weight: 500; letter-spacing: -.5px;
  font-variant-numeric: tabular-nums; }
.tile .figure span { font-size: 13px; color: var(--on-surface-var);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Projects is the destination reached for every day: first in the grid,
   one step darker at the border, one size larger in the figure. Two
   notches of emphasis, no colour. */
.tile.lead { border-color: var(--outline); }
.tile.lead .figure b { font-size: 34px; }
/* A destination whose PAGE does not exist yet: SAME SIZE as the others —
   the grid stays a grid — but recessive, and plainly not a link. It
   drops the big figure too: a 34px number inside a tile you cannot open
   promises a page that isn't there (caught on the first render, 30 Jul
   2026, when three short tiles left the grid with a stubby second row). */
.tile.later { background: var(--surface-low); cursor: default; }
.tile.later .name { color: var(--on-surface-var); }
.tile.later .figure span { white-space: normal; }
/* The "In design" strips: below the grid, half the height, a sentence
   instead of a metric. Several stack now that Customers, QA settings
   and Profile joined Reports there (30 Jul 2026). */
#home-later { display: flex; flex-direction: column; gap: 10px; }
#home-later-cap { margin-top: 30px; }
.tile.strip { min-height: 0; background: var(--surface-low); border-color: var(--outline-var);
  flex-direction: row; align-items: center; gap: 12px; padding: 14px 20px; cursor: default; }
.tile.strip .name { margin-top: 0; color: var(--on-surface-var); }
.tile.strip .figure { margin-top: 0; }
.tile.strip .figure span { white-space: normal; }
@media (max-width: 1080px) { .grid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 700px) {
  .wrap { padding: 24px 18px 40px; }
  .grid { grid-template-columns: 1fr; }
  /* Below one column a tall tile is a letterbox: the glyph moves beside
     the text and the tile keeps only the height its content needs. */
  .tile { flex-direction: row; align-items: center; gap: 14px; min-height: 0; padding: 16px 18px; }
  .tile .name { margin-top: 0; }
  .tile .figure { margin-left: auto; }
  .tile .figure b, .tile.lead .figure b { font-size: 20px; }
}

/* ================= CUSTOMERS (30 Jul 2026) =================
   Built from design/customers-a-side-panel.html, the variant the founder
   chose: the list stays on screen and the record opens beside it, the
   same geometry the editor's QA and Comments panels use. */
/* The record opens BESIDE the list. Renamed off `cust-` because the
   Files view wants the identical row, and a `cust-` class on a Files
   page is how the next copy goes wrong. min-height:0 is load-bearing:
   without it the panel grows the page instead of scrolling
   .cpanel-body — and #view-files had no wrapper AT ALL until 2 Aug
   2026, so its 384px panel stacked above the table and pushed it down
   the page, which is what the founder's screenshot showed. */
.side-wrap { flex: 1; display: flex; min-height: 0; }
.side-wrap > .files-wrap { flex: 1; min-width: 0; }
.cpanel { width: 384px; flex: none; border-left: 1px solid var(--outline-var);
  display: flex; flex-direction: column; min-height: 0; background: var(--surface); }
.cpanel-head { display: flex; align-items: center; gap: 8px;
  padding: 10px 8px 10px 16px; border-bottom: 1px solid var(--outline-var); flex: 0 0 auto; }
.cpanel-head h2 { font-size: 15px; font-weight: 500; margin: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cpanel-body { flex: 1; min-height: 0; overflow-y: auto; padding: 16px; }
.cpanel-foot { display: flex; align-items: center; gap: 10px; padding: 10px 16px;
  border-top: 1px solid var(--outline-var); flex: 0 0 auto; }
.cpanel-foot .meta { font-size: 12px; color: var(--on-surface-var); }

/* Active / Not active is TEXT, never a capsule — the same call the files
   table and the Projects rows made this morning. */
.cstate { font-size: 13px; font-weight: 500; white-space: nowrap; }
.cs-on { color: var(--primary); }
.cs-off { color: var(--on-surface-var); }
/* A Not-active row RECEDES rather than hides: its name gives up the
   weight and the ink that made it first in the row. Opacity was rejected
   — at any value low enough to read as dimmed it drags text under 4.5:1.
   Not sorted to the bottom either: this list is the one place a customer
   can be brought back, so a row you cannot see is a row you cannot
   reactivate (founder direction, 30 Jul 2026). */
.files-table tbody tr.off .fname { font-weight: 400; color: var(--on-surface-var); }
.files-table tbody tr.sel { background: var(--surface-high);
  box-shadow: inset 3px 0 0 var(--primary); }
.files-table tbody tr[role="button"] { cursor: pointer; }
.files-table tbody tr:focus-visible { outline: 2px solid var(--primary); outline-offset: -2px; }

.fld { margin-bottom: 12px; }
.fld label { display: block; font-size: 12px; color: var(--on-surface-var); margin-bottom: 4px; }
.fld input, .fld textarea { width: 100%; padding: 8px 10px; border-radius: 8px;
  border: 1px solid var(--outline); background: var(--surface); color: var(--on-surface); }
.fld input:focus, .fld textarea:focus { outline: 2px solid var(--primary); outline-offset: -1px; }
.fld textarea { min-height: 76px; resize: vertical; }
.fld .hint { font-size: 12px; color: var(--on-surface-var); margin-top: 4px; }
.fld.bad input { border-color: var(--bad); }
.fld.bad .hint { color: var(--bad); }
.fld-row { display: flex; gap: 10px; }
.fld-row .fld { flex: 1; min-width: 0; }
.cp-sec { font-size: 11px; letter-spacing: .8px; text-transform: uppercase;
  color: var(--on-surface-var); font-weight: 600; margin: 22px 0 10px; }
.cp-sec:first-child { margin-top: 0; }
.ro { display: flex; gap: 10px; align-items: baseline; padding: 7px 0;
  border-bottom: 1px solid var(--outline-var); font-size: 13px; }
.ro .k { color: var(--on-surface-var); flex: none; min-width: 104px; }
.ro .v { color: var(--on-surface); min-width: 0; }
.chk { display: flex; gap: 9px; align-items: flex-start; font-size: 13px; }
.chk input { margin: 2px 0 0; flex: none; }

/* DELETE, the last resort: a closed disclosure at the very bottom, never
   a peer of Save or Deactivate. Red appears only after a deliberate
   click, one level in. */
.dz { margin-top: 26px; border-top: 1px solid var(--outline-var); padding-top: 12px; }
.dz > summary { list-style: none; display: flex; align-items: center; gap: 4px;
  cursor: pointer; font-size: 12.5px; color: var(--on-surface-var);
  padding: 5px 6px; margin: 0 -6px; border-radius: 8px; }
.dz > summary::-webkit-details-marker { display: none; }
.dz > summary:hover { background: var(--surface-low); color: var(--on-surface); }
.dz > summary:focus-visible { outline: 2px solid var(--primary); outline-offset: -2px; }
.dz-body { padding-top: 10px; font-size: 13px; color: var(--on-surface-var); }
.dz-body .card { padding: 12px 14px; }
.rbtn.danger { color: var(--bad); font-weight: 500; }
/* Below 1000px the ROW stacks instead of the panel shrinking. Shrinking
   was the worse half of a bad trade: 320px left 287px of content, which
   is where every width bug in this stylesheet bit hardest, and the list
   beside it was cramped anyway. Stacked, the panel gets the whole window
   and so does the table — no measurement in this panel has two answers
   any more. Child selector, so it beats .cpanel's own width regardless
   of source order. */
@media (max-width: 1000px) {
  .side-wrap { flex-direction: column; }
  .side-wrap > .cpanel { width: auto; flex: none; border-left: none;
    border-top: 1px solid var(--outline-var); }
}

/* ================= TRANSLATION MEMORY (30 Jul 2026) =================
   Memories on the left, the concordance on the right — because
   searching is what a translator does with a memory all day, and
   browsing is what they do once.
   A memory was a customer × language pair here until 1 Aug 2026, when
   it became a named record of its own (schema/021): the rail lists
   things now, not groupings, and an archived one recedes in place
   rather than vanishing. */
.tm-wrap { flex: 1; display: flex; min-height: 0; }
/* The rail is a column now: list scrolls, footer stays put. The padding
   moved onto the scroller so the footer's top border can run the full
   width of the rail, like the QA panel's does. */
.tm-side { width: 300px; flex: none; border-right: 1px solid var(--outline-var);
  display: flex; flex-direction: column; min-height: 0; }
.tm-side-scroll { flex: 1; overflow-y: auto; padding: 16px; min-height: 0; }
.tm-side .cap { margin-top: 0; }
.tm-main { flex: 1; min-width: 0; display: flex; flex-direction: column; padding: 16px 20px; }
.tm-mem { display: block; width: 100%; text-align: left; padding: 10px 12px; margin: 0 -12px 2px;
  border-radius: 10px; cursor: pointer; }
.tm-mem:hover { background: var(--surface-low); }
.tm-mem.sel { background: var(--surface-high); box-shadow: inset 3px 0 0 var(--primary); }
.tm-mem:focus-visible { outline: 2px solid var(--primary); outline-offset: -2px; }
.tm-mem .who { font-weight: 500; }
.tm-mem .pair { font-size: 13px; color: var(--on-surface-var); }
.tm-mem .n { font-size: 12px; color: var(--on-surface-var); font-variant-numeric: tabular-nums; }
.tm-mem.off .who { font-weight: 400; color: var(--on-surface-var); }
/* The 052 rail sections: Project resources vs General. A label, not a
   control — the cap style, scaled to sit inside the list. */
.tm-sect { font-size: 11px; font-weight: 600; letter-spacing: 0.06em;
  text-transform: uppercase; color: var(--on-surface-var);
  margin: 14px 0 4px; }
.tm-info-promote { margin-top: 14px; display: flex; gap: 10px; align-items: baseline; }
.tm-info-promote .meta { font-size: 12px; }

.tm-search { display: flex; gap: 10px; align-items: center; margin-bottom: 12px; }
.tm-search input { flex: 1; min-width: 0; padding: 9px 12px; border-radius: 10px;
  border: 1px solid var(--outline); background: var(--surface); color: var(--on-surface); }
.tm-search input:focus { outline: 2px solid var(--primary); outline-offset: -1px; }
.tm-results { flex: 1; min-height: 0; overflow-y: auto; }
/* A unit reads like a segment pair, because that is what it is. */
.tm-unit { padding: 12px 14px; border: 1px solid var(--outline-var); border-radius: 12px;
  margin-bottom: 8px; }
.tm-unit:hover { background: var(--surface-low); }
.tm-unit .s { color: var(--on-surface-var); font-size: 13.5px; }
.tm-unit .t { font-size: 13.5px; margin-top: 3px; }
.tm-unit .t[contenteditable="true"] { outline: 2px solid var(--primary); outline-offset: 2px;
  border-radius: 4px; }
.tm-unit .foot { display: flex; align-items: center; gap: 10px; margin-top: 8px;
  font-size: 12px; color: var(--on-surface-var); }
.tm-unit .foot .grow { flex: 1; }
.tm-unit mark { background: var(--warn-container); color: inherit; border-radius: 3px; }
@media (max-width: 1000px) { .tm-side { width: 240px; } }

/* ---- termbase (docs/08 §7.4) ----
   Rides the TM page's layout and unit styling; only what a termbase has
   and a memory does not is styled here. */
.tb-import { display: flex; flex-wrap: wrap; gap: 8px; align-items: center;
  padding: 10px 12px; margin-bottom: 12px; border: 1px solid var(--outline-var);
  border-radius: 12px; background: var(--surface-low); }
.tb-import select, .tb-import input[type="file"] { font: inherit; color: inherit; }
.tb-import select, .tb-import .lang { height: 32px; padding: 0 10px; border-radius: 8px;
  border: 1px solid var(--outline); background: var(--surface); }
.tb-import .lang { width: 92px; }
.tb-import .arrow { color: var(--on-surface-var); }
.tb-import .meta { flex-basis: 100%; font-size: 12px; color: var(--on-surface-var); }
/* ---- the entries table (2 Aug 2026) ----
   Replaces the result cards. Source and Translation take the width;
   everything ABOUT a unit rather than IN it gets a narrow column after
   them. The wrapper scrolls horizontally on its own so the page body
   never does. */
.tm-table-wrap { flex: 1; min-height: 0; overflow: auto; }
.tm-table { width: 100%; border-collapse: collapse; font-size: 13.5px; }
.tm-table thead th { position: sticky; top: 0; z-index: 1; background: var(--surface);
  text-align: left; font-size: 12px; font-weight: 500; color: var(--on-surface-var);
  padding: 8px 10px; border-bottom: 1px solid var(--outline-var); white-space: nowrap; }
.tm-table td { padding: 10px; border-bottom: 1px solid var(--outline-var);
  vertical-align: top; }
.tm-table tbody tr:hover { background: var(--surface-low); }
.tm-table tr.picked { background: var(--primary-container); }
.tm-table tr.picked:hover { background: var(--primary-container); }
.tm-table .c-sel { width: 34px; }
.tm-table .c-src, .tm-table .c-tgt { min-width: 220px; }
.tm-table .c-lang, .tm-table .c-tm, .tm-table .c-from,
.tm-table .c-when { white-space: nowrap; font-size: 12.5px; color: var(--on-surface-var); }
.tm-table .c-pen { white-space: nowrap; text-align: right; }
.tm-table .muted { color: var(--on-surface-var); opacity: .7; }
/* A penalty is a subtraction, so it is drawn as one. Amber rather than
   red: a downgraded unit is distrusted, not broken. */
.tm-table .pen { font-weight: 600; color: var(--warn); font-variant-numeric: tabular-nums; }
/* The pencil appears on hover or focus, as in the reference. Focus is
   not optional: a control that only exists under a mouse cannot be
   reached by keyboard at all. */
.tm-table .c-tgt { position: relative; padding-right: 34px; }
.tm-pencil { position: absolute; top: 6px; right: 6px; opacity: 0; padding: 3px;
  border-radius: 6px; cursor: pointer; color: var(--primary); }
.tm-table tr:hover .tm-pencil, .tm-pencil:focus-visible { opacity: 1; }
.tm-pencil svg { width: 15px; height: 15px; display: block; }
.tm-edit { width: 100%; min-height: 60px; font: inherit; color: inherit; padding: 8px;
  border-radius: 8px; border: 1px solid var(--primary); background: var(--surface);
  resize: vertical; }
.tm-edit-actions { display: flex; justify-content: flex-end; gap: 6px; margin-top: 6px; }

/* The selection bar sits where the search row was, so the actions never
   appear beside a box that would change what they act on. */
.tm-selbar { display: flex; align-items: center; gap: 8px; margin-bottom: 12px;
  padding: 8px 12px; border-radius: 10px; background: var(--primary-container);
  color: var(--on-primary-container); font-size: 13px; font-weight: 500; }
.tm-pen-dialog { display: flex; flex-wrap: wrap; align-items: center; gap: 8px;
  padding: 10px 12px; margin-top: 12px; border: 1px solid var(--outline-var);
  border-radius: 12px; background: var(--surface-low); font-size: 13px; }
.tm-pen-dialog input[type="number"] { width: 72px; height: 32px; padding: 0 8px;
  border-radius: 8px; border: 1px solid var(--outline); background: var(--surface);
  color: inherit; font: inherit; }
.tm-pen-dialog .meta { flex-basis: 100%; font-size: 12px; color: var(--on-surface-var); margin: 0; }
#tm-pager select { height: 28px; border-radius: 8px; border: 1px solid var(--outline);
  background: var(--surface); color: inherit; font: inherit; }

/* A memory's own record: rename, archive, delete. Same 12px-radius
   bordered block as the import rows above it, so the main pane reads as
   one column of controls rather than three unrelated widgets. */
.tm-record { display: flex; flex-wrap: wrap; gap: 8px; align-items: center;
  padding: 10px 12px; margin-bottom: 12px; border: 1px solid var(--outline-var);
  border-radius: 12px; background: var(--surface-low); }
.tm-name { height: 32px; padding: 0 10px; border-radius: 8px; font: inherit;
  color: inherit; border: 1px solid var(--outline); background: var(--surface);
  min-width: 180px; font-weight: 600; }
.tm-rec-status { font-size: 12px; font-weight: 600; }
/* Colour as TEXT, never a chip — the same call the Customers list made
   about Active / Not active on 30 Jul 2026. */
.tm-rec-status.on { color: var(--good); }
.tm-rec-status.off { color: var(--on-surface-var); }
.tm-record .meta { flex-basis: 100%; font-size: 12px; color: var(--on-surface-var); margin: 0; }
/* Delete lives behind a closed disclosure, pinned below the archive
   control and never adjacent to it: reachable, but never the thing you
   hit while reaching for the reversible one. Red only after a
   deliberate click has opened it. */
.tm-danger { flex-basis: 100%; margin-top: 4px; }
.tm-danger > summary { cursor: pointer; font-size: 12.5px; color: var(--on-surface-var);
  list-style: revert; width: fit-content; }
.tm-danger[open] > summary { color: var(--bad); font-weight: 600; margin-bottom: 6px; }
.tm-danger .warn-line { font-size: 13px; margin: 0 0 6px; }
.tm-danger[open] { border-top: 1px solid var(--outline-var); padding-top: 8px; }

/* The TM import's overwrite switch. Reads as a sentence rather than a
   bare box because what it does is destructive to units the translator
   confirmed themselves — the label IS the warning. */
.tm-imp-replace { display: flex; align-items: center; gap: 6px; font-size: 13px;
  color: var(--on-surface-var); }
/* A disabled icon button still has to look disabled: export is off until
   one memory is picked, and a live-looking button that does nothing
   reads as a bug. */
.icon-btn:disabled { opacity: .38; cursor: default; }
/* A forbidden term is still a term — kept and marked, never hidden, so
   the next person can see it was considered and ruled out (doc 02). The
   strike is on the TRANSLATION only: the source term is not the problem. */
.tb-entry.forbidden .t { text-decoration: line-through; color: var(--on-surface-var); }
.tb-entry.forbidden { border-color: var(--bad-container); }
.tb-flag { flex: none; font-size: 11px; font-weight: 600; letter-spacing: .3px;
  text-transform: uppercase; color: var(--bad); background: var(--bad-container);
  border-radius: 999px; padding: 1px 8px; }
.tb-entry .note { font-size: 12.5px; color: var(--on-surface-var); margin-top: 4px;
  padding-left: 10px; border-left: 2px solid var(--outline-var); }

/* The dashboard attention strip (design/dashboard-b-work-first.html).
   One line, muted like everything else on the page — it earns its place
   by being short and by not being there when there is nothing to say. */
/* Bottom margin as well as top: in the mockup this line ended a section,
   here it sits ABOVE Continue, and without it the "CONTINUE" caption
   crowds the strip and the two read as one block. */
.attn { display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  margin: 16px 0 18px 2px; font-size: 13px; color: var(--on-surface-var); }
.attn svg { width: 16px; height: 16px; flex: none; }
.attn .pname { color: var(--on-surface); font-weight: 500; text-decoration: none; }
.attn .pname:hover { text-decoration: underline; }
/* Overdue is the one fact that changes what you do today, so it is the
   one that takes a colour. Derived status wears colour, never a capsule
   (30 Jul 2026) — same rule as .pstatus above. */
.attn .pname.ps-late { color: var(--bad); }
/* The middot separates two facts, so it is READ, not decoration: it keeps
   the line's own muted ink like the "client · langs · step" dots in the
   projects rows. At --outline it sat at 1.9:1 and vanished. */
.attn .sep { color: inherit; }
/* Dismiss. Present but nearly silent until you go near the fact — the ×
   is an escape hatch, not part of the sentence, and a row of dark
   crosses would read as the line's punctuation. It keeps a full-size hit
   area while looking small. */
.attn .fact { display: inline-flex; align-items: center; gap: 2px; }
.attn .attn-x { flex: none; width: 22px; height: 22px; border-radius: 50%;
  font-size: 11px; line-height: 1; color: var(--on-surface-var); opacity: 0;
  transition: opacity .12s ease; }
.attn .fact:hover .attn-x, .attn .attn-x:focus-visible { opacity: .75; }
.attn .attn-x:hover { opacity: 1; background: var(--surface-high); }
/* Never invisible to the keyboard: opacity 0 still takes focus, so the
   focus ring has to bring it back into view. */
.attn .attn-x:focus-visible { outline: 2px solid var(--primary); outline-offset: -2px; opacity: 1; }

/* (The header due-date chip and its swap-in field are gone — 17 Aug
   2026, founder: redundant; the date is set in project settings and
   shown in the Details panel.) */

/* A filter chip that is actually narrowing something. Same paint as
   .chip[aria-pressed="true"], but a CLASS: these chips open a listbox,
   and aria-pressed would announce them as toggle buttons they are not.
   The label already carries the meaning; this is for the eye scanning
   the row for "what is limiting my results". */
.chip.on { background: var(--primary-container); color: var(--on-primary-container);
  border-color: transparent; }
/* A workspace can hold dozens of projects, so these two menus scroll
   instead of running off the bottom of the window. */
.status-menu.tall { max-height: 320px; overflow-y: auto; }

/* ---- workspace search (doc 08 §7) ----
   No side rail: the TM page has memories to pick between, this one has
   the whole workspace and the grouping IS the result. Otherwise it wears
   the TM page's search bar and mark styling verbatim, because the two
   are siblings and a translator should not have to learn each twice. */
.sr-wrap { flex: 1; min-height: 0; display: flex; flex-direction: column;
  padding: 16px 20px; max-width: 1100px; width: 100%; }
/* Three chips beside the box need somewhere to go on a narrow window;
   the input keeps a floor so it never collapses to a slit. */
.sr-wrap .tm-search { flex-wrap: wrap; }
.sr-wrap .tm-search input { min-width: 260px; }
.sr-results { flex: 1; min-height: 0; overflow-y: auto; }
.sr-file { border: 1px solid var(--outline-var); border-radius: 12px; margin-bottom: 8px;
  overflow: hidden; }
.sr-head { display: flex; align-items: center; gap: 10px; width: 100%; text-align: left;
  padding: 11px 14px; cursor: pointer; }
.sr-head:hover { background: var(--surface-low); }
.sr-head:focus-visible { outline: 2px solid var(--primary); outline-offset: -2px; }
/* The PROJECT leads the row (founder direction, 1 Aug 2026). A package
   often puts the same file name in several projects, so a file-led list
   was a column of identical strings with the distinguishing part pushed
   into the muted line behind it.
   The project must also be the LAST thing to truncate, not the first:
   it is what tells two rows apart, so the muted line yields its width
   first. A 38% cap took "Project 10000006" down to "Project 100…" in a
   narrow pane, which is precisely the information the row exists to
   carry. */
.sr-head .pname { font-weight: 500; overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; max-width: 60%; flex: 0 0 auto; }
.sr-head .meta { font-size: 12.5px; color: var(--on-surface-var); overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; flex: 0 1 auto; min-width: 0; }
.sr-head .n { flex: none; font-variant-numeric: tabular-nums; font-size: 12.5px;
  color: var(--on-surface-var); background: var(--surface-container);
  border-radius: 999px; padding: 1px 9px; }
/* The chevron points down when the group is open — the same direction
   the content it reveals travels. */
.sr-chev { flex: none; transition: transform .12s ease; transform: rotate(-90deg); }
.sr-chev.open { transform: none; }
.sr-hits { border-top: 1px solid var(--outline-var); }
.sr-hits .pad { padding: 10px 14px; margin: 0; }
.sr-hit { display: flex; gap: 12px; width: 100%; text-align: left; padding: 9px 14px;
  cursor: pointer; border-bottom: 1px solid var(--outline-var); }
.sr-hit:last-child { border-bottom: none; }
.sr-hit:hover { background: var(--surface-low); }
.sr-hit:focus-visible { outline: 2px solid var(--primary); outline-offset: -2px; }
.sr-hit .seq { flex: none; width: 44px; font-variant-numeric: tabular-nums; font-size: 12px;
  color: var(--on-surface-var); padding-top: 2px; }
.sr-hit .pair { min-width: 0; flex: 1; }
.sr-hit .s { display: block; color: var(--on-surface-var); font-size: 13.5px; }
.sr-hit .t { display: block; font-size: 13.5px; margin-top: 2px; }
.sr-hit .t .empty { color: var(--on-surface-var); }
.sr-hit mark { background: var(--warn-container); color: inherit; border-radius: 3px; }

/* Replace-in-targets (change sets, doc 08 §7.3): a disclosure under the
   search filters. The proposal renders whole — rows AND exclusions — so
   Apply is a decision about visible text, never a leap. */
.sr-replace { margin: 6px 0 2px; }
.sr-replace-toggle { font-size: 13px; color: var(--primary); padding: 4px 0; }
.sr-replace-toggle:hover { text-decoration: underline; }
.sr-panel { border: 1px solid var(--outline-var); border-radius: 12px;
  padding: 12px 14px; margin-top: 6px; background: var(--surface-low); }
.sr-fields { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.sr-fields input { flex: 1; min-width: 180px; height: 32px; padding: 0 10px;
  border: 1px solid var(--outline-var); border-radius: 8px; background: var(--surface); }
.sr-arrow { color: var(--on-surface-var); flex: none; }
.sr-prop { margin-top: 10px; border-top: 1px solid var(--outline-var); }
.sr-prop-row { display: grid; grid-template-columns: 44px minmax(90px, 160px) 1fr 1fr;
  gap: 10px; padding: 6px 0; border-bottom: 1px solid var(--outline-var);
  font-size: 13px; align-items: baseline; }
.sr-prop-row .seq { font-variant-numeric: tabular-nums; font-size: 12px;
  color: var(--on-surface-var); }
.sr-prop-row mark { background: var(--warn-container); color: inherit; border-radius: 3px; }
.sr-prop-row .sr-a mark { background: var(--primary-container); }
.sr-file-name { color: var(--on-surface-var); font-size: 12px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sr-b { color: var(--on-surface-var); }
.sr-a .empty { color: var(--on-surface-var); }
.sr-excl { margin-top: 8px; }
.sr-excl-row { display: grid; grid-template-columns: 44px minmax(90px, 160px) 1fr;
  gap: 10px; padding: 3px 0; font-size: 12.5px; color: var(--on-surface-var); }
.sr-why { font-style: italic; }
.sr-prop-foot { display: flex; gap: 8px; align-items: center; margin-top: 10px; }
.sr-tally { display: flex; flex-direction: column; gap: 4px; padding: 8px 0; font-size: 13.5px; }

/* The tenant journal (023), collapsed on the TM page. Deliberately plain
   — it is a record, not a feature to show off — but a destructive entry
   is marked, because "what happened to my units" is the question that
   brings anyone here. */
.tm-activity { margin-top: 16px; border-top: 1px solid var(--outline-var); padding-top: 10px; }
.tm-activity > summary { cursor: pointer; font-size: 12.5px; color: var(--on-surface-var);
  width: fit-content; }
.tm-event { display: flex; gap: 10px; padding: 4px 0; font-size: 12.5px; }
.tm-event .when { flex: none; width: 150px; color: var(--on-surface-var);
  font-variant-numeric: tabular-nums; }
.tm-event.gone .what { color: var(--bad); }

/* ---- THE STRIP (concept A, founder choice 2 Aug 2026) ----
   The record is one line above the table: identity left, the four things
   you do to the memory as glyphs right. It replaced a Details box
   containing an Advanced box — two folds deep, an unpredictable height,
   and a destructive action in the same flow as the routine ones. */
.tm-strip { display: flex; align-items: center; gap: 10px; padding: 6px 2px 10px;
  border-bottom: 1px solid var(--outline-var); margin-bottom: 12px; }
/* The name is a LABEL, edited the way a translation is: double-click, or
   the pencil in the glyph cluster. A permanent box made it read as a
   form field rather than as what the memory is called. */
.tm-strip .name { font-weight: 600; font-size: 15px; padding: 2px 5px; margin-left: -5px;
  border-radius: 6px; cursor: text; white-space: nowrap; }
.tm-strip .name:hover { background: var(--surface-high); }
.tm-strip .name:focus-visible { outline: 2px solid var(--primary); outline-offset: 0; }
.tm-strip .name input { font: inherit; font-weight: 600; padding: 0 4px; width: 180px;
  border-radius: 6px; border: 1px solid var(--primary); background: var(--surface); color: inherit; }
.gbtn { width: 30px; height: 30px; flex: none; display: grid; place-items: center; border: 0;
  background: none; border-radius: 8px; cursor: pointer; color: var(--on-surface-var); }
.gbtn:hover { background: var(--surface-high); color: var(--on-surface); }
.gbtn:disabled { opacity: .38; cursor: default; background: none; }
.gbtn svg { width: 18px; height: 18px; display: block; }
/* The glyph in a strip that DESTROYS something — today only the
   termbase Delete. `danger` was on the markup with nothing behind it,
   so the one irreversible verb in that row rendered pixel-identical to
   Rename and Archive: the class said destructive and the button said
   nothing, which is the .rbtn.danger case (the red "Delete customer")
   left half-done for glyphs.
   Red on the deliberate movement toward it — the rule .pset-verbs
   .detach and .tm-danger[open] already keep. Not at rest: this strip is
   on screen the whole time a termbase is open, and a glyph sitting
   permanently red in a header teaches red to mean nothing by the time
   it matters. What actually guards the act is the confirmation that
   counts the terms it would destroy. */
.gbtn.danger:hover:not(:disabled),
.gbtn.danger:focus-visible { color: var(--bad); }

/* ---- the manage dialog: three intentions, three tabs ----
   Stacking change-text, go-back-in-time and destroy in one column is
   what made the old box read as a settings dump. Delete is a tab you
   choose, not something you scroll past on the way elsewhere. */
.tm-manage-dlg { width: 560px; max-width: 92vw; padding: 0; }
.tm-manage-head { display: flex; align-items: center; gap: 10px; padding: 14px 18px;
  border-bottom: 1px solid var(--outline-var); }
.tm-manage-head h2 { margin: 0; font-size: 15px; }
.tm-tabs { display: flex; gap: 2px; padding: 0 12px; border-bottom: 1px solid var(--outline-var); }
.tm-tab { border: 0; background: none; font: inherit; font-size: 13px; padding: 9px 12px;
  cursor: pointer; color: var(--on-surface-var); border-bottom: 2px solid transparent; }
.tm-tab.on { color: var(--primary); border-bottom-color: var(--primary); font-weight: 600; }
.tm-pane { padding: 16px 18px; }
.tm-pane .meta { font-size: 12.5px; color: var(--on-surface-var); margin: 10px 0 0; }
/* Info: a two-column list, so a value can be found rather than read
   past. The strip ran these together with middots, which scans fine and
   answers badly when you want one of them. */
.tm-info { display: grid; grid-template-columns: max-content 1fr; gap: 6px 18px;
  margin: 0; font-size: 13px; }
.tm-info dt { color: var(--on-surface-var); }
.tm-info dd { margin: 0; }
.tm-adv-row { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.tm-adv-row.end { justify-content: flex-end; margin-top: 10px; }
.tm-adv-row input { height: 34px; padding: 0 10px; border-radius: 8px; font: inherit;
  color: inherit; border: 1px solid var(--outline); background: var(--surface); flex: 1; min-width: 140px; }
.tm-rep-eg { display: flex; gap: 10px; font-size: 12.5px; margin-top: 4px; }
.tm-rep-eg .was { color: var(--on-surface-var); text-decoration: line-through; }
.tm-rep-eg .now { color: var(--on-surface); }
.tm-restore-row { display: flex; align-items: center; gap: 10px; padding: 7px 0; font-size: 12.5px;
  border-top: 1px solid var(--outline-var); }
.tm-restore-row .when { flex: none; width: 140px; font-variant-numeric: tabular-nums; }
.tm-restore-row .what { flex: 1; color: var(--on-surface-var); }

/* ---- project settings (026, 2 Aug 2026) ----
   Which memories a project reads, in what order, and the one it writes
   to. Same .cpanel shell as the Customers record and the same single
   explicit Save — because it is the same kind of thing: a record edited
   beside the list it belongs to. It shares .side-wrap with that page. */
.pset-dirs { display: flex; gap: 2px; margin-bottom: 10px; flex-wrap: wrap; }
/* Unsaved work is per DIRECTION, and so is the marker for it. The footer
   note can only describe the tab in front, so an edit parked on another
   direction had nothing to show for itself until the discard prompt on
   close named it. */
.pset-dirs .pset-unsaved { margin-left: 5px; color: var(--warn); font-weight: 700; }

/* ---- the attached-resource row: TWO LINES, one skeleton for both
   sections (realigned 2 Aug 2026) ----
   Line 1 is what the resource IS — the write radio, then the name and
   nothing else. Line 2 is what you can DO about it — the facts, then
   the verbs as glyphs in a fixed track on the right.
   The single line asked seven controls to share 351px and lost: .nm was
   the only shrinkable item, so it shrank to 0px and painted its glyphs
   across the unit count ("Tedstunits", founder screenshot). Measured,
   the fixed controls alone came to 365px, so the name's allowance was
   NEGATIVE at every name length including the empty string — there was
   no width to clip into, and overflow:hidden alone would only have
   traded the overlap for an invisible name.
   GRID, and the verbs get their OWN COLUMN rather than riding a flex
   spacer on line 2. That is the point: a spacer only holds the line it
   is on, so a wrapping fact run would dump the verb cluster at the LEFT
   margin of a new line and the detach glyph would zig-zag down the list
   — the founder's complaint, reintroduced by its own fix. As a track,
   the verbs sit at the same x on every row of both sections.
   ONE track set for memories AND termbases: a termbase has no write
   role, so its gutter is empty rather than absent, and the two lists
   read as one table instead of two indents. */
.pset-row { display: grid; grid-template-columns: 24px 1fr 78px;
  grid-template-rows: minmax(24px, auto) auto;
  column-gap: 8px; row-gap: 2px; align-items: center; padding: 7px 0 8px;
  border-bottom: 1px solid var(--outline-var); font-size: 13px; }
/* The name gets a WHOLE line because tenants disambiguate at the END —
   "Main (en-US → da-DK)" — and an ellipsis eats exactly the part that
   tells two memories apart. The four-property block is the one used
   verbatim at .resume-row .fname, .cpanel-head h2 and .sr-head .pname. */
.pset-row .nm { grid-row: 1; grid-column: 2 / -1; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 500; }
/* An archived row RECEDES in place rather than shouting — the call
   .tm-mem.off already made for the memory rail. It still SAYS "archived"
   on line 2: receding is a hint, and a hint is not a label. The word
   moved off the name because appended there it is the first thing an
   ellipsis eats, i.e. it vanished exactly on the long names that need it. */
.pset-row.off .nm { font-weight: 400; color: var(--on-surface-var); }
/* The write role is a RADIO: exactly one memory per direction takes
   confirmed segments, and a checkbox would invite none (which loses
   them) or several (which splits them). What goes is the word "Updated"
   repeated on every row — a column heading flattened onto its rows,
   which is the thing that was colliding with the name. The label fills
   the whole 24px gutter, so the hit target is the cell rather than the
   15px control, and the per-row accessible name lives on the input. */
.pset-writes { grid-row: 1; grid-column: 1; display: grid; place-items: center;
  width: 24px; height: 24px; margin: 0; cursor: pointer; }
.pset-writes input { margin: 0; width: 15px; height: 15px;
  accent-color: var(--primary); cursor: pointer; }
/* Line 2, quiet by construction: 12px, muted, and the verbs are
   borderless glyphs rather than three bordered .rbtn boxes. The borders
   are what made five attached memories read as a form. It may wrap
   inside its own cell, and nothing moves horizontally when it does. */
.pset-sub { grid-row: 2; grid-column: 2 / 3; display: flex; align-items: center;
  gap: 6px; flex-wrap: wrap; row-gap: 2px; min-height: 28px;
  font-size: 12px; color: var(--on-surface-var); }
/* The separator is a real element emitted by JS, not a ::before. A
   ::before on .pset-pen would be blockified into that label's first flex
   item — inside its hit area, inheriting its weight, and unable to wrap
   where the layout assumes it can. Joining a filtered array of facts is
   also how a dropped fact leaves no dangling middot behind it. */
.pset-sub .sep { color: var(--outline); }
.pset-sub .n { font-variant-numeric: tabular-nums; white-space: nowrap; }
/* The one row that receives confirmed segments says so, once, in WORDS —
   the call .cstate already made about Active and Archived. Read-only is
   the ABSENCE of the word, which is what doc 02 says the model means. */
.pset-sub .wr { color: var(--primary); font-weight: 500; }
/* A penalty is a policy number set once, so it is a small field, not a
   spinner. CENTRED rather than right-aligned: with the "−" outside the
   box, right-aligning a one-digit value — the DEFAULT on every row —
   parked the minus 19px from its own digit. */
.pset-pen { display: inline-flex; align-items: center; gap: 0; white-space: nowrap; }
.pset-pen input { width: 30px; height: 22px; margin: 0 1px; padding: 0 1px;
  text-align: center; font: inherit; font-size: 12px; font-variant-numeric: tabular-nums;
  color: inherit; background: var(--surface); border: 1px solid var(--outline);
  border-radius: 6px; appearance: textfield; -webkit-appearance: textfield; }
/* Chromium reserves ~20px inside a number field for spin buttons even
   while they are invisible: at 30px that clipped "100" outright. ↑/↓
   still step the value, which is the spinner's whole job. */
.pset-pen input::-webkit-inner-spin-button,
.pset-pen input::-webkit-outer-spin-button { -webkit-appearance: none; margin: 0; }
.pset-pen input:focus { border-color: var(--primary); outline: none; }
.pset-pen input:focus-visible { outline: 2px solid var(--primary); outline-offset: 1px; }
/* The verbs are a TRACK, not a flex item, and they travel together. */
.pset-verbs { grid-row: 2; grid-column: 3; justify-self: end;
  display: flex; flex: none; gap: 0; }
.pset-verbs .gbtn { width: 26px; height: 26px; border-radius: 7px; }
.pset-verbs .gbtn svg { width: 16px; height: 16px; }
/* .gbtn has hover and :disabled but no focus ring anywhere in this
   sheet, and these three are now the only way to reorder or detach with
   a keyboard. */
.pset-verbs .gbtn:focus-visible { outline: 2px solid var(--primary); outline-offset: -2px; }
/* Detach is red only on the deliberate movement toward it — the rule
   .tm-danger already keeps. A column of red down the panel's right edge
   teaches red to mean nothing, and detaching is undone by one click of
   the select below. It is also NOT a ✕: this panel's close button is a
   ✕ two hundred pixels above it. */
.pset-verbs .detach:hover:not(:disabled),
.pset-verbs .detach:focus-visible { color: var(--bad); }
/* The section break between memories and termbases (028). A rule above
   it rather than a heavier weight: the two sections are peers, and the
   panel is narrow enough that a size jump would read as a new dialog. */
.pset-h { margin: 22px 0 4px; padding-top: 16px; font-size: 13px; font-weight: 600;
  border-top: 1px solid var(--outline-var); }
.pset-add { display: flex; gap: 8px; align-items: center; margin-top: 12px; flex-wrap: wrap; }
/* The select gets its OWN LINE. `flex: 1` resolved its basis to 0%,
   which made min-width:120px a CLIPPING floor rather than a floor:
   flexbox never learned the control was too narrow, so it never broke
   the line, and "nothing left to attach" hid under its own chevron with
   19px cut off. On its own line it has all 351px. */
.pset-add select { flex-basis: 100%; min-width: 0; height: 32px; border-radius: 8px;
  border: 1px solid var(--outline); background: var(--surface); color: inherit; font: inherit; }
/* There is no bare .meta rule in this stylesheet — all of them are
   descendant-scoped — so this panel's explanatory paragraphs rendered at
   the 14px body size in full ink, one step LARGER and darker than the
   13px rows they explain. Direct children only, so .tb-import .meta
   inside #pset-new-form keeps its own flex-basis. */
#pset .cpanel-body > .meta { font-size: 12px; color: var(--on-surface-var); }
/* The all-archived warning: its own line, its own colour, its own
   channel. It used to share a span with the word "unsaved" through a
   ternary, so editing an all-archived list suppressed the one warning
   the code calls "otherwise indistinguishable from a working list".
   Not .meta — it is a warning, and it must not be quiet. */
#pset-tb-note { margin: 10px 0 0; font-size: 12px; font-weight: 500; color: var(--warn); }
#pset-tb-note:empty { display: none; }
/* The footer status gets its own line rather than fighting the buttons
   for one: it had 1px of slack with a four-character memory name, and
   went to a silent double line the moment it said "unsaved". Scoped to
   #pset so the Customers footer is untouched. */
#pset .cpanel-foot { flex-wrap: wrap; row-gap: 6px; }
#pset #pset-note:not(:empty) { flex-basis: 100%; }

/* ================= ANALYSIS (3 Aug 2026, as billing) =================
   The band roll-up on a project's own page. It was the doc-06 money
   loop's surface until the money side was removed (10 Aug 2026);
   what stayed is deliberately plain — a report, not a dashboard.

   A GRAY SOFT BOX, the editor panel's card (founder direction, 10 Aug
   2026). Same three properties .panel-main uses and no others —
   --surface-low, 16px radius, no border — because that card is the
   product's word for "a body of content that is not the main one", and
   the analysis is exactly that next to the file table. The hairline
   border-top it used to wear is gone WITH the box rather than as well
   as it: a rule above a card draws a second edge a few pixels from the
   card's own, which is the "no table borders" note that produced the
   Slides look in the first place.
   Padding is .panel-scroll's 14px, so the distance from the box's edge
   to its content is the same here as in the panel. */
.billing { margin: 24px 0; padding: 14px;
  background: var(--surface-low); border-radius: 16px; }
.billing-head { display: flex; align-items: center; gap: 10px; margin-bottom: 8px; }
.billing-head h2 { font-size: 15px; font-weight: 600; margin: 0; }
/* --surface-low does double duty: it is this card's GROUND and it is
   .rbtn's hover fill, so Re-analyze and Mark delivered would hover to
   exactly the colour behind them and the feedback would vanish. The
   panel hit this the day it became a card and stepped its hover to
   --surface-container; the same step, for the same reason, one surface
   over. (Found here by looking for it, not by a screenshot — a hover
   state is invisible in a still.) */
.billing .rbtn:hover:not(.primary) { background: var(--surface-container); }
/* The band table, one per language pair (founder direction, 10 Aug
   2026). It borrows .files-table's type and rhythm so the two tables on
   this page read as one page — but NOT its row rules: a file row is
   clickable and these are not, so no pointer and no hover fill. Narrow
   on purpose (max-width rather than full bleed): four short numeric
   columns stretched across a wide screen are harder to read across, not
   easier. */
/* TWO COLUMNS — what came in, and what is there now (founder direction,
   11 Aug 2026). Side by side rather than stacked because the whole point
   is the comparison: the eye reads Total against Total across, not down
   a scroll. They wrap under each other below ~760px of card, where side
   by side would squeeze four numeric columns twice over. */
.an-cols { display: flex; flex-wrap: wrap; gap: 32px; }
.an-col { flex: 1 1 320px; min-width: 0; }
.an-col h3 { font-size: 12px; font-weight: 600; letter-spacing: .06em;
  text-transform: uppercase; color: var(--on-surface-var); margin: 0 0 8px; }
/* The Current/Initial toggle (17 Aug 2026) — the same tab idiom the TM
   manager uses, capped to the table width so the rule under the tabs
   does not run across the whole column. */
.an-toggle { display: flex; gap: 2px; margin: 0 0 10px;
  border-bottom: 1px solid var(--outline-var); max-width: 560px; }
/* The project facts beside the analysis. The description is the one
   unbounded field: typed line breaks survive (pre-wrap), a pasted URL
   cannot force the column wide (anywhere), and links look like links. */
.an-project .info-row { max-width: 560px; }
/* The Project heading shares its line with the toggle's tab text
   (founder annotation, 17 Aug 2026: "align the first line on the right
   to match the first line on the left") — the 10px mirrors the
   tm-tab's own top padding. */
.an-project > h3:first-child { padding-top: 10px; }
/* The description sits clear of the fact rows and inside a BORDERED
   box (founder annotations, 17 Aug 2026) — the outline says "text
   area" the way the settings panel's editor does, and it holds its
   shape even while the description is still the empty-state hint.
   The 24px is the breathing room between the Due date row and the
   heading (third annotation, same day: "increase the space between
   Due date and Description"). The element selector is load-bearing:
   .an-col h3's `margin: 0 0 8px` shorthand outranks a bare class
   (0,1,1 over 0,1,0) and silently zeroed an earlier margin-top here —
   h3.an-desc-h ties the specificity and wins by order. */
h3.an-desc-h { margin-top: 24px; }
/* The box's BOTTOM edge lands on the analysis table's total rule
   (founder annotation, 17 Aug 2026: arrow from the box's bottom edge
   down to the rule above Total) — the two columns close on one shared
   line, with Total sitting below it as the footer. 124.5px is the
   measured run from the box's top (24px gap above) to that rule:
   header row plus the six band rows, a height the table always has
   because every band is listed even at zero. The .5 is the line
   boxes' own fraction — the column's type lands on half pixels. A
   longer description still grows past the line; content wins. */
.an-desc-box { border: 1px solid var(--outline-var); border-radius: 8px;
  padding: 10px 12px; min-height: 124.5px; max-width: 560px; }
.an-desc-box .meta { margin: 0; }
.an-desc { font-size: 13.5px; line-height: 1.55;
  white-space: pre-wrap; overflow-wrap: anywhere; }
.an-desc a { color: var(--primary); }
.an-table { border-collapse: collapse; font-size: 14px; width: 100%;
  max-width: 560px; margin: 4px 0 18px; }
.an-table caption { text-align: left; font-weight: 500; padding: 0 0 6px;
  color: var(--on-surface); }
.an-table th { text-align: left; font-size: 11px; letter-spacing: .8px;
  text-transform: uppercase; color: var(--on-surface-var);
  padding: 0 12px 6px 0; font-weight: 600; }
.an-table td { padding: 7px 12px 7px 0; border-top: 1px solid var(--outline-var); }
.an-table th.num, .an-table td.num { text-align: right; font-variant-numeric: tabular-nums;
  padding-right: 0; }
/* An empty band is LISTED, so that "none" and "not counted" cannot look
   alike — and recessive, so the bands that carry the job still lead. */
.an-table tr.zero td { color: var(--on-surface-var); }
.an-table tfoot td { font-weight: 600; border-top: 2px solid var(--outline); }
.files-table .num { text-align: right; white-space: nowrap; }

/* ================= QA settings (3 Aug 2026) =================
   One row per check: name + description on the left, a severity select
   on the right. Plain and scannable — a settings list, not a dashboard. */
.qa-row { display: flex; align-items: flex-start; gap: 16px; padding: 12px 0;
  border-bottom: 1px solid var(--outline-var); }
.qa-meta { flex: 1; min-width: 0; }
.qa-row .nm { display: block; font-weight: 500; font-size: 14px; }
.qa-row .desc { display: block; font-size: 12px; color: var(--on-surface-var); margin-top: 2px; }
.qa-sev { flex: 0 0 auto; height: 32px; border-radius: 8px; border: 1px solid var(--outline);
  background: var(--surface); color: inherit; font: inherit; padding: 0 8px; }

/* Ask Syntag (doc 08, 4 Aug 2026): the chat pane. One column, tenant
   turns right-aligned in the primary container, assistant turns plain,
   system notes muted. Tool chips name what ran; a finding is the §8
   rule-4 callout — amber, because it is a warning about content, not a
   fault. Classes here are set via className in asAppendMessage (never
   innerHTML), so keep this census in step with app.js by hand. */
.as-wrap { display: flex; flex-direction: column; height: calc(100vh - 57px); max-width: 760px; }
.as-thread { flex: 1; overflow-y: auto; display: flex; flex-direction: column;
  gap: 12px; padding: 16px 4px; }
.as-msg { max-width: 85%; }
.as-tenant { align-self: flex-end; }
.as-tenant .as-body { background: var(--primary-container); color: var(--on-primary-container);
  border-radius: 14px 14px 4px 14px; padding: 8px 12px; }
.as-assistant { align-self: flex-start; }
.as-assistant .as-body { background: var(--surface-container);
  border-radius: 14px 14px 14px 4px; padding: 8px 12px; }
.as-sys { align-self: center; max-width: 92%; }
.as-sys .as-body { color: var(--on-surface-var); font-size: 13px; text-align: center; }
.as-pending { color: var(--on-surface-var); font-size: 13px; }
.as-body { white-space: pre-wrap; overflow-wrap: anywhere; font-size: 13.5px; }
.as-tools { display: flex; flex-wrap: wrap; gap: 4px; margin-bottom: 4px; }
/* The assistant's proposal inbox (doc 08 T3): suggestions waiting on a
   tenant decision, above the tenant's own compose panel. */
.sr-inbox { border: 1px solid var(--outline-var); border-radius: 10px;
  padding: 10px 12px; margin-bottom: 10px; background: var(--surface-low); }
.sr-inbox-head { font-size: 13px; font-weight: 600; margin-bottom: 6px; }
.sr-inbox-row { display: flex; align-items: center; gap: 8px; padding: 5px 0;
  border-top: 1px solid var(--outline-var); font-size: 13px; }
.sr-inbox-row:first-of-type { border-top: none; }
.sr-inbox-what { font-weight: 500; }
/* The bookmark flag rides the number gutter (doc 03): a private
   navigation mark, deliberately NOT in the communication lane. */
.seg .num .bm-flag { margin-left: 4px; cursor: pointer; }
/* display:inline-block is load-bearing: the global .sicon rule is
   display:BLOCK, which pushed the flag onto its own line under the
   number and grew the row ~11px past the absolute top the virtualizer
   had already written (adversarial review, 6 Aug 2026 — visible in the
   founder-facing screenshot and read past). */
.seg .num .bm-flag svg { width: 13px; height: 13px; display: inline-block; vertical-align: -2px; }
.as-link { color: var(--primary); text-decoration: underline; text-underline-offset: 2px; }
/* Fuzzy-card source diff (the Studio/memoQ convention): struck = the
   TM's words the segment lacks; marked = the segment's words the TM
   lacks. Both keep their own container tint so the marks read at a
   glance without the colors carrying QA meaning. */
.card .pair .s del.diff-del { background: var(--bad-container); color: var(--bad);
  text-decoration: line-through; border-radius: 3px; padding: 0 2px; }
.card .pair .s ins.diff-ins { background: var(--good-container); color: var(--good);
  text-decoration: underline; text-underline-offset: 2px; border-radius: 3px; padding: 0 2px; }
/* TM candidate cards (doc 03 next/prev match): the grid cell is the
   selection, the outline just mirrors which card is in the target. */
.card.cand { cursor: pointer; }
/* --primary, not --primary-container (adversarial sweep, 10 Aug 2026):
   this outline is the only indicator of which candidate sits in the
   target, and the container green measured 1.18:1 against the panel's
   new gray ground — an aggravation of a failure it already had on white
   (1.24:1). The QA analog beside it (.card.qa.qa-active) always used
   the full --primary at 2px; the two "this one is live" outlines now
   say it the same way, at 6:1. */
.card.cand-active { outline: 2px solid var(--primary); }
.card.cand-dead { cursor: default; opacity: 0.6; }
.as-chip { font-size: 11px; padding: 1px 8px; border-radius: 10px;
  border: 1px solid var(--outline-var); background: var(--surface-low);
  color: var(--on-surface-var); }
.as-chip.bad { border-color: var(--bad); color: var(--bad); }
.as-finding { display: flex; flex-direction: column; gap: 3px; margin-bottom: 6px;
  border: 1px solid var(--warn-mark); border-radius: 10px; padding: 8px 10px;
  background: var(--warn-container); }
.as-finding-head { font-size: 12px; color: var(--warn); font-weight: 600; }
.as-finding-quote { font-size: 12px; font-style: italic; overflow-wrap: anywhere; }
.as-composer { display: flex; gap: 8px; padding: 10px 4px 16px; }
.as-composer input { flex: 1; height: 40px; padding: 0 14px;
  border: 1px solid var(--outline-var); border-radius: 20px; background: var(--surface-low); }
.as-composer .btn-filled { height: 40px; padding: 0 20px; border-radius: 20px; }
.as-composer .btn-filled[disabled] { opacity: .5; cursor: default; }

/* Memory (doc 08 §6): the assistant's working-style page. One label per
   row, the field beside it — the QA-settings rhythm, since both are
   "a workspace-wide preference you set once and forget". */
.mem-form { display: flex; flex-direction: column; gap: 2px; margin-top: 12px; }
.mem-row { display: grid; grid-template-columns: minmax(140px, 200px) 1fr; gap: 14px;
  align-items: center; padding: 10px 0; border-bottom: 1px solid var(--outline-var); }
.mem-row .nm { font-size: 13.5px; }
.mem-row input { height: 34px; padding: 0 11px; border: 1px solid var(--outline-var);
  border-radius: 8px; background: var(--surface); }
/* The description textarea rides the same rhythm as the text inputs;
   the row top-aligns so the label sits at the top of the tall box
   rather than floating beside its middle. */
.mem-row textarea { padding: 8px 11px; border: 1px solid var(--outline-var);
  border-radius: 8px; background: var(--surface); font: inherit;
  font-size: 13.5px; resize: vertical; }
.mem-row.mem-row-tall { align-items: start; }
.mem-row.mem-row-tall .nm { padding-top: 8px; }
/* A CHECKBOX row breaks the two-column rhythm above on purpose: the box
   is the control and the words are its label, so a 200px first column
   would strand a 16px checkbox in the middle of it. The project
   settings panel's no-LLM fence is the one of these (042, 11 Aug 2026).
   Overrides .mem-row input, which sizes TEXT fields. */
.mem-row.pset-toggle { grid-template-columns: auto 1fr; gap: 10px; align-items: start; }
.mem-row.pset-toggle input { height: auto; padding: 0; border: 0; margin-top: 3px;
  width: auto; background: none; }
