/* ==========================================================================
   SCS-078 — SaddguruShri brand tokens for the internal tools.

   Layer 1 is copied VERBATIM from the canonical source so a diff is trivial:
     310-saddgurushri-website/branding/design-system/tokens.css
   which is itself derived from the live site CSS. If this file and the site
   CSS ever disagree, the site CSS wins and this gets corrected. Never edit a
   layer-1 value here to fix something — fix it upstream and re-copy.

   Layer 2 is this file's own, and it exists because the canonical system is a
   design system for a public devotional WEBSITE. These are dense internal data
   tools: a 7,166-row catalogue and a transcript editor a volunteer reads for
   hours. Applied literally, the site's 14px radii, pill buttons and 1.65 line
   height cost rows per screen. So layer 2 re-tunes density and adds the few
   things the brand has no answer for — and says so, rather than quietly
   diverging and calling it the brand.

   Served by both tool servers at ./brand.css (see catalog-ui/server.mjs).
   ========================================================================== */

/* ── Layer 1 · Brand tokens (canonical — do not edit here) ───────────────── */
:root {
  /* Light ground: white and cream (purity, the robes) */
  --white:       #ffffff;
  --cream:       #fdf8ee;
  --sand:        #f6e8cf;

  /* Gold and saffron: light, divinity. The primary accent family */
  --gold:        #c89b3c;
  --gold-deep:   #a87b22;
  --gold-soft:   #e7cd8f;
  --amber:       #b45309;
  --marigold:    #e07b2a;
  --saffron:     #f4a23b;
  --lotus:       #c2406a;

  /* Indigo: depth, the inner journey. All dark surfaces */
  --indigo:      #211c4e;
  --indigo-deep: #100b2b;

  /* Text and rules */
  --ink:         #241f3a;
  --muted:       #6f6884;
  --line:        rgba(33, 28, 78, 0.12);
  --line-gold:   rgba(200, 155, 60, 0.35);

  /* Type. --deva is why Hindi and Marathi set correctly without a per-string
     font choice: build on the var, never on a family name.

     SCS-080: the Noto families are appended to the BASE stacks rather than
     swapped in under [lang="hi"], which is the one thing this deliberately does
     differently from the website. Font fallback is per CHARACTER, not per
     element: Inter has no Devanagari coverage, so a Devanagari codepoint falls
     through to Noto Sans Devanagari on its own while the Latin around it stays
     Inter. That matters because these screens are permanently bilingual whatever
     the UI language — "SGV-1803 · 42 खंड" is one string, and a rule that swapped
     the whole element's family would set the code and the digits in a
     Devanagari face too. It also means Guruji's titles set correctly in ENGLISH
     mode, which is where they were already being read.

     Sans for the UI, serif for content, matching the design system's own
     per-script table (Serif -> --serif, Sans -> --sans). */
  --serif: 'Merriweather', Georgia, 'Times New Roman', 'Noto Serif Devanagari', serif;
  --deva:  'Noto Serif Devanagari', var(--serif);
  --sans:  'Inter', system-ui, -apple-system, 'Segoe UI', Roboto, 'Noto Sans Devanagari', sans-serif;

  /* Layout */
  --maxw:      1120px;
  --gutter:    clamp(1rem, 4vw, 2.5rem);
  --radius:    14px;
  --shadow:    0 18px 50px -24px rgba(16, 11, 43, 0.45);
  --shadow-sm: 0 6px 22px -14px rgba(16, 11, 43, 0.5);
}

/* ── Layer 2 · Tool layer (this file's own — extensions, not brand) ──────── */
:root {
  /* Radii. --radius stays 14px and keeps meaning what the brand says it means;
     tool chrome uses these instead. A 14px corner on a 28px-tall filter select
     reads as a lozenge, not a field. */
  --r:      8px;    /* panels, menus, the list container */
  --r-sm:   6px;    /* buttons, selects, inputs, small chrome */
  --r-pill: 999px;  /* badges and stage chips only */

  /* Status. THE BRAND HAS NO GREEN AND NO RED — it is white, gold, amber,
     marigold, saffron, lotus, indigo. That is right for a website about
     stillness and wrong for a verification tool, which signals good audio,
     verified, and engines-agree dozens of times per screen, to volunteers who
     read green as "done" without being taught it. Owner's call 2026-08-04:
     keep the green. So --ok is retuned from the old #15803d to a deeper olive
     that sits beside gold instead of fighting it, and --bad is derived from
     --lotus rather than a generic red. These four are the only colours here
     with no counterpart in the canonical system; everything else is brand. */
  --ok:       #3a6e4a;  --ok-bg:   rgba(58, 110, 74, 0.13);  --ok-line: rgba(58, 110, 74, 0.30);
  --warn: var(--gold-ink);  --warn-bg: rgba(200, 155, 60, 0.18);
  --bad:      #a8325a;  --bad-bg:  rgba(194, 64, 106, 0.11);   /* darkened --lotus */
  --info:     #211c4e;  --info-bg: rgba(33, 28, 78, 0.09);     /* = --indigo */

  /* The brand's accents AT TEXT WEIGHT. Measured 2026-08-04 after the owner
     called the contrast bad, and they were right: --gold-deep is 3.80:1 on
     white and 3.59:1 on cream, so every gold label on this screen sat under
     WCAG AA. That is not a fault in the brand — --gold-deep is a LINK and
     eyebrow colour on a website, used at 0.74rem+ over generous whitespace.
     A tool sets it at 10.5px uppercase in a table header, which is the one
     place a 3.8:1 accent has no business being.

     Both keep their source hue and saturation exactly (gold 40.5deg/66%,
     amber 24deg/84%) and only drop lightness, so they read as the same
     colours, just weighted for small text.

     --gold-ink went 32% -> 29% lightness on 2026-08-05, when the table header
     gained a --sand fill to separate it from the rows. Sand is darker than the
     white it had been measured against, which took the header label from 5.43
     to 4.49 — under AA by a hair, and the hover state (a deeper sand) to 4.05.
     Darkening the ink rather than lightening the fill keeps the separation the
     owner asked for, and can only ever RAISE contrast on the lighter grounds
     it also appears on. Cleared against every ground it now touches:
       --gold-ink   6.26 white / 5.92 cream / 5.18 sand / 4.68 sand-hover / 5.38 --warn-bg
       --amber-ink  6.71 white / 5.71 on the verifying tint
     Use these for any gold or amber TEXT. Keep --gold, --gold-deep, --amber
     and --marigold for fills, rules, borders and icons, where they are right. */
  --gold-ink:  #7b5b19;
  --amber-ink: #96440d;

  /* Neutral chrome fill, for anything that is a surface rather than a signal. */
  --tint:   rgba(33, 28, 78, 0.045);
  --hover:  rgba(200, 155, 60, 0.09);   /* row and control hover: a gold whisper */

  /* ── The rail (owner's call 2026-08-04: rust) ────────────────────────────
     The ask was "red or amber background instead of gold". Applied to the
     PAGE that would have darkened the surface under a 7,166-row table and
     undone the contrast pass; what was actually missing was separation, since
     chrome and data sat at one value and gold was carrying it alone. So the
     warm dark goes on the rail and the data keeps its light ground.

     --rust is --amber (#b45309) taken down to 22% lightness at the same hue.
     It is a ground, which --amber itself cannot be: against gold-soft --amber
     reaches only 3.23:1, which is why the design system has it as a fill.
     Measured on --rust: white 10.72, --sand 8.86, --gold-soft 6.90.

     --side-mut is white at 78% rather than a grey, so it stays inside the
     rust hue instead of going muddy against it — 7.13:1.
     --side-card is a 7% white wash. It reads as a surface at 1.20:1 against
     the ground, which is a boundary the eye finds and a contrast ratio no
     text ever has to survive; the text on it is measured against the
     composite (--sand 7.37, white 8.91), not against the wash. */
  --rust:      #6b2a08;
  --side-bg:   var(--rust);
  --side-fg:   var(--white);
  --side-mut:  rgba(255, 255, 255, 0.78);
  --side-acc:  var(--sand);
  --side-line: rgba(246, 232, 207, 0.22);
  --side-card: rgba(255, 255, 255, 0.07);

  /* Semantic surface aliases. The tools were written against these names
     before there was a brand layer; keeping them means each screen's own CSS
     needs no sweeping rename, and the mapping to brand lives in one place. */
  --bg:     var(--cream);
  --card:   var(--white);
  --border: var(--line);
  --text:   var(--ink);
}

/* ── Stage ramp (SCS-056 lifecycle, shared by catalog and verify) ─────────
   Six steps that must be told apart at a glance in a table. The ordering is
   the point: neutral -> indigo -> marigold -> green -> solid green -> lotus,
   so lightness and saturation both climb toward "done" and the last step
   steps sideways into a different hue because translation is a different kind
   of event, not a seventh degree of verified.

   4-verified and 5-approved deliberately stay in ONE hue family, tint then
   solid. They are the same fact at two levels of authority (a volunteer
   finished; the owner signed off), and a volunteer who has learned green
   should not have to learn a second colour to read the stronger version. */
:root {
  /* Neutral, not sand: cataloged means nothing has happened yet, and in a list
     that is mostly stage 1 a warm tan pill shouts louder than the stages that
     actually carry news. The quietest fill on the page is the correct one. */
  --stage-cataloged-bg:   var(--tint);      --stage-cataloged-t:   var(--muted);
  --stage-transcribed-bg: var(--info-bg);   --stage-transcribed-t: var(--info);
  --stage-verifying-bg:   rgba(224, 123, 42, 0.16); --stage-verifying-t: var(--amber-ink);
  --stage-verified-bg:    var(--ok-bg);     --stage-verified-t:    var(--ok);
  --stage-approved-bg:    var(--ok);        --stage-approved-t:    var(--white);
  --stage-translated-bg:  rgba(194, 64, 106, 0.12); --stage-translated-t: #9c2f56;
}

/* ── Shared primitives ───────────────────────────────────────────────────
   Only what both tools already draw identically. Layout stays per-screen. */

/* The ground is not flat cream. Two fixed radial washes — saffron top-right,
   lotus mid-left — sit under it so the page reads warm and faintly radiant.
   This is the site's own body background, verbatim.

   A tiled jali lattice sat on top of these washes briefly on 2026-08-05 and
   the owner had it removed the same day: it did not look good over a dense
   data table, whatever the opacity. Removed rather than dialled down, because
   the problem was the idea and not the number. The washes alone are what
   carries the brand on this surface. */
body {
  background:
    radial-gradient(1200px 700px at 100% -5%, rgba(244, 162, 59, 0.10), transparent 60%),
    radial-gradient(1100px 680px at 0% 12%, rgba(194, 64, 106, 0.06), transparent 55%),
    var(--bg);
  background-attachment: fixed;
  color: var(--text);
  font-family: var(--sans);
}

/* ── The app shell ───────────────────────────────────────────────────────
   Owner 2026-08-04: "move much of the top stuff to the left; the list should
   be its own view to fill the page with search and pagination."

   So the page stops scrolling and the LIST scrolls. .shell is exactly one
   viewport tall, the rail and the main column each own their overflow, and
   the table header sticks inside .main's scroller. The practical win is that
   pagination never moves: on the old layout, seven stat pills, a search box,
   eight filters and a tool row pushed the list so far down that Next was
   below the fold on a short window, and reaching it scrolled the filters away.

   Both tools use this. Their rails hold different things — the catalog's is
   stats and filters, verify's also keeps the instruction note, the stage flow
   and the legend from SCS-073 — but the frame is one definition. */
/* 100vh divided by --zoom (published by shared/ui.js): inside a zoomed root,
   100vh resolves in the zoomed space, so at 1.1 an undivided 100vh makes the
   shell taller than the actual viewport and the page grows an outer scrollbar
   — which is exactly the thing this layout exists to prevent. Falls back to 1
   when no preference has been set yet. */
.shell { display: flex; align-items: stretch; height: calc(100vh / var(--zoom, 1)); overflow: hidden; }

.side {
  width: 258px; flex: none; position: relative; overflow: hidden auto;
  background: var(--side-bg); color: var(--side-fg);
  border-right: 1px solid var(--side-line);
  padding: 15px 14px 18px; display: flex; flex-direction: column; gap: 9px;
  scrollbar-gutter: stable;
  /* Owner 2026-08-05: "without any shadow... looks busy" — a flat-filled rail
     sitting flush against the list read as one undifferentiated warm mass. A
     real cast shadow gives the rail actual depth against the page it sits in
     front of, which is what a photo-plus-flat-colour combination needs to
     read as composed rather than as clutter. */
  box-shadow: 8px 0 26px -10px rgba(16, 11, 43, .45);
}
.main {
  flex: 1; min-width: 0; height: calc(100vh / var(--zoom, 1));
  display: flex; flex-direction: column; gap: 8px; padding: 14px 16px 12px;
}

/* ── The list's own toolbar ───────────────────────────────────────────────
   Owner 2026-08-05: Columns and Views move out of the rail to the top right
   of the list. That is where they belong — they act on the LIST (which
   columns, which saved shape), not on the query, so grouping them with the
   filters was putting a list control in the query panel. Now the rail is
   filters-and-facts and the list carries its own controls, which is also why
   the pager and these can share one row.

   Back to popovers here, not the rail's inline disclosures: this row has no
   overflow of its own to be clipped by (that was the rail's constraint), and
   pushing the list down every time someone opens Columns would be worse. */
.mainbar { display: flex; align-items: center; gap: 10px; flex: none; }
.mainbar .pager { flex: 1; }
.listtools { display: flex; gap: 6px; align-items: center; flex: none; }
.listtools .menuwrap { position: relative; display: inline-flex; }
.listtools .tbtn {
  border: 1px solid var(--border); background: var(--card); border-radius: var(--r-sm);
  padding: 6px 11px; font-size: 12.5px; font-weight: 600; cursor: pointer;
  color: var(--text); font-family: var(--sans); white-space: nowrap;
}
.listtools .tbtn:hover { background: var(--hover); border-color: var(--line-gold); }
.listtools .tbtn .n { color: var(--muted); font-weight: 400; margin-left: 3px; }
/* Anchored to the RIGHT edge: these buttons sit at the right of the row, so a
   left-anchored panel would open off the side of the window. */
.listtools .menu {
  position: absolute; top: calc(100% + 5px); right: 0; z-index: 30;
  background: var(--card); border: 1px solid var(--border); border-radius: var(--r);
  box-shadow: var(--shadow-sm); min-width: 240px; padding: 7px;
  max-height: 60vh; overflow-y: auto;
}
.listtools .menu h4 {
  font-size: 10px; color: var(--muted); font-weight: 700; padding: 2px 6px 5px; margin: 0;
  letter-spacing: .08em; text-transform: uppercase;
}
.listtools .menu label {
  display: flex; align-items: center; gap: 7px; padding: 4px 6px;
  border-radius: 5px; font-size: 12.5px; cursor: pointer; color: var(--text);
}
.listtools .menu label:hover { background: var(--hover); }
.listtools .menu input[type=checkbox] { accent-color: var(--gold); }
.listtools .menu .xtra {
  margin-left: auto; font-size: 9.5px; color: var(--muted);
  background: var(--tint); border-radius: 4px; padding: 0 5px;
}
.listtools .menu .foot {
  display: flex; gap: 10px; border-top: 1px solid var(--border);
  margin-top: 5px; padding: 6px 6px 2px;
}
.listtools .menu .foot button {
  background: none; border: none; color: var(--gold-ink);
  font-size: 11.5px; cursor: pointer; padding: 0; font-weight: 600;
}
.listtools .menu .foot button:disabled { opacity: .45; cursor: default; }
/* The one element allowed to grow and scroll. min-height:0 is load-bearing —
   without it a flex child refuses to shrink below its content and the list
   pushes the bottom pager off screen instead of scrolling. */
.listbox { flex: 1; min-height: 0; overflow: auto; }

/* ── Guruji in the rail: REMOVED 2026-08-05 ──────────────────────────────
   A masked archive portrait sat at the bottom of the rail from 2026-08-04.
   The owner had it removed the day after the second pass: it did not look
   good, and two rounds of tuning (multiply -> plain opacity, 62% -> 44%
   height, mirrored inward, heavier top mask) improved it without making it
   right. Removed rather than dimmed further — at the opacity where it stopped
   competing with the filters it was not worth having.

   The `.side > * { position: relative; z-index: 1 }` companion rule went with
   it: its only job was to lift the rail's contents above the portrait, and
   nothing inside the rail positions absolutely (the Columns/Views popovers
   moved out to .listtools).

   shared/assets/guruji-rail.jpg and the /guruji-rail.jpg route on both servers
   are deliberately KEPT — the owner said "for now", and the asset is 110 KB
   sitting behind one unreferenced route rather than something to re-source
   later. Git history has the full treatment if it comes back.

   Note if it does: the reason it was in the rail at all still holds. The brand
   rule is real archive photography, never altered, and laying UI chrome and
   running Devanagari across Guruji's face is a kind of alteration — so a
   future attempt belongs beside the work, not under it. */

/* ── Rail furniture ─────────────────────────────────────────────────────── */
.side .brand { font-family: var(--serif); font-weight: 700; font-size: 15px; line-height: 1.25; }
.side .brand em {
  display: block; font-style: normal; font-family: var(--sans); font-weight: 400;
  font-size: 11.5px; color: var(--side-mut); margin-top: 2px;
}
/* The rail's section heading. Not .eyebrow — that is marigold for a light
   ground and would fail here; sand is 8.86:1 on rust. Latin-only by rule:
   uppercase and tracking break Devanagari conjuncts. */
/* margin-top does the sectioning. The rail is one flex column with a uniform
   9px gap, so every heading sat the same distance from the control above it as
   the controls sat from each other and nothing read as a group boundary
   (owner 2026-08-05: "add some spacing between sections"). 16px on top of the
   gap groups without needing a rule to do it. */
.side .sgrp {
  font-size: 9.5px; font-weight: 700; letter-spacing: .14em; text-transform: uppercase;
  color: var(--side-acc); border-bottom: 1px solid var(--side-line);
  padding-bottom: 4px; margin-top: 16px;
}
.side .sgrp:first-of-type { margin-top: 8px; }
.side .srow {
  display: flex; justify-content: space-between; align-items: baseline; gap: 8px;
  font-size: 11.5px; color: var(--side-mut); padding: 1px 0;
}
.side .srow b { font-family: var(--serif); font-size: 13px; color: var(--side-fg); }
/* The unit rides inside the same <b> so "735" and "hrs" sit on one baseline
   as a single figure, not a number with a caption underneath. Sans, smaller,
   muted — it is a label on the number, not part of the number. */
.side .srow b .u { font-family: var(--sans); font-size: 10px; font-weight: 500; color: var(--side-mut); margin-left: 2px; }
.side .srow.dim { opacity: .55; }
/* "Filtered" used to be plain text in --side-acc, which measures fine against
   flat rust but had nothing of its own to fall back on over the photo's
   texture near the bottom of a tall rail. A solid pill carries its own
   background, so it stays legible regardless of what is behind it. */
.side .fbadge {
  display: inline-block; background: var(--sand); color: var(--rust);
  font-weight: 700; font-size: 10.5px; letter-spacing: .04em;
  border-radius: var(--r-sm); padding: 2px 8px;
}
/* Filter's heading carries the Clear action inline, next to what it clears,
   instead of a separate full-width button down in List — the owner's "clear
   filter should be next to filters." Text-only and quiet by default so it
   does not compete with the heading; only shown once a filter is actually
   set (JS still owns that toggle via #clearBtn's inline style). */
.side .sgrp-row { display: flex; align-items: baseline; justify-content: space-between; }
.side .sclear-inline {
  background: none; border: none; padding: 0; cursor: pointer;
  font-family: var(--sans); font-size: 9.5px; font-weight: 700; letter-spacing: .06em;
  text-transform: none; color: var(--side-mut);
}
.side .sclear-inline:hover { color: var(--side-acc); text-decoration: underline; }
.side select, .side .sbtn {
  width: 100%; background: var(--side-card); color: var(--side-fg);
  border: 1px solid var(--side-line); border-radius: var(--r-sm);
  padding: 6px 8px; font-size: 12px; font-family: var(--sans);
}
/* The native arrow is drawn in the OS text colour and vanishes on a dark
   ground, so it is redrawn as a data-URI chevron in --side-acc. */
.side select {
  appearance: none; -webkit-appearance: none; cursor: pointer;
  background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='7' viewBox='0 0 10 7'%3E%3Cpath d='M1 1l4 4 4-4' stroke='%23f6e8cf' stroke-width='1.6' fill='none' stroke-linecap='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right 8px center; padding-right: 24px;
}
.side select option { background: var(--rust); color: var(--white); }
.side .sbtn { text-align: left; font-weight: 600; cursor: pointer; }
.side .sbtn:hover, .side select:hover { border-color: var(--side-acc); }
.side .sbtn .n { color: var(--side-mut); font-weight: 400; margin-left: 3px; }
.side .srowbtns { display: flex; gap: 6px; }
.side .srowbtns .sbtn { text-align: center; }
.side a.navb {
  flex: 1; text-align: center; text-decoration: none; font-size: 11.5px; font-weight: 600;
  color: var(--side-acc); border: 1.5px solid var(--side-acc); border-radius: var(--r-pill);
  padding: 4px 0; display: inline-flex; align-items: center; justify-content: center; gap: 5px;
}
.side a.navb:hover { background: rgba(246, 232, 207, .14); }
.side .navs { display: flex; gap: 6px; }

/* ── Selects on the light ground ──────────────────────────────────────────
   The rail's selects were themed for rust in the last pass; these are their
   counterparts everywhere else — the editor's playback speed, the pager's
   rows-per-page. They were raw browser controls sitting beside brand-styled
   buttons, which is what made the speed dropdown stand out enough to circle.

   Same construction as .side select: appearance:none plus a data-URI chevron,
   because the native arrow is drawn by the OS in the OS's own colour and
   cannot be styled. Drawn in --gold-ink here to match the surrounding text
   rather than --sand, which is a colour for dark grounds only. */
select.sel, .pager select, .ctl select {
  appearance: none; -webkit-appearance: none; cursor: pointer;
  background-color: var(--card); color: var(--text);
  border: 1px solid var(--border); border-radius: var(--r-sm);
  padding: 5px 24px 5px 9px; font-size: 12.5px; font-family: var(--sans); font-weight: 600;
  background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='7' viewBox='0 0 10 7'%3E%3Cpath d='M1 1l4 4 4-4' stroke='%237b5b19' stroke-width='1.6' fill='none' stroke-linecap='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right 8px center;
}
select.sel:hover, .pager select:hover, .ctl select:hover { border-color: var(--line-gold); }

/* ── The rail's stage flow and legend ─────────────────────────────────────
   Owner 2026-08-05: "show stages only and details can be a tooltip"; the
   markings list was "ugly". Both were chip-plus-sentence pairs stacked down
   the rail — six for the flow, five for the legend — which is eleven lines of
   explanatory prose permanently occupying a 258px column that also has to
   hold every filter. Reference material was being shown as though it were
   content.

   Now both are wrapped rows of the chips alone, with the sentence moved into
   each chip's own `title`. Nothing is lost: the stage chips already carried
   their hint as a tooltip (stageBadge writes STAGE_HINT into title), so this
   only stops printing what hovering already told you. The numbers 1..6 carry
   the ordering the connector lines used to draw. */
.flow, .legend-chips { display: flex; flex-wrap: wrap; gap: 5px; }
.flow .stage, .legend-chips .pill, .legend-chips .lang { cursor: help; }

/* ── Tooltips ─────────────────────────────────────────────────────────────
   A real tooltip rather than the native `title`. The owner has asked for the
   EN/MR one twice now while `title` was already set on both chips — which is
   the tell that native tooltips do not land: ~1s delay, no hover affordance,
   and nothing on screen suggesting there is anything to hover. That was
   survivable when the text was also printed beside the chip; now that the
   stage flow and the markings legend are chips ALONE, the tooltip is the only
   way to read them and it has to be instant and obvious.

   SCS-086: ONE element on <body>, positioned by shared/ui.js, replacing the
   `[data-tip]::after` pseudo-element this used to be. Owner 2026-08-05:
   "tooltip still overlapping.. should show away from the text."

   The pseudo-element could only ever be positioned RELATIVE TO ITS OWN
   ANCESTRY, and every option inside a 258px rail was bad. Below the chip it
   covered the next two steps of the journey — the owner's screenshot. Above
   it covered the previous two. Beside it needed a width the rail does not
   have, and pushing past the rail's edge is not available at all, because the
   rail is `overflow: hidden auto` and clips anything that leaves it.

   Lifting it out of the rail solves all of that at once. Fixed positioning is
   relative to the viewport, so the bubble sits BESIDE the chip over the list —
   empty space that belongs to no control — and can be as wide as it needs.

   It also retires a whole class of bug rather than working around it. An
   absolutely-positioned descendant still counts toward its ancestor's
   scrollable overflow, which is what produced the horizontal scrollbar the
   owner reported on the rail (measured then: scrollWidth 347 against
   clientWidth 316). Two separate fixes had already been spent containing that
   — anchoring to the row, then capping at `calc(100% - 2px)` because the
   border-box landed a hair past 100%. A fixed element on <body> contributes
   NOTHING to any ancestor's scroll box, so the rail cannot be widened by a
   tooltip again and both workarounds are gone with it.

   `cursor: help` stays on the trigger: it is the affordance that says there is
   something to hover, which is half of why the native tooltip never landed. */
[data-tip] { cursor: help; }
.scs-tip {
  position: fixed; z-index: 200; display: none;
  background: var(--indigo-deep); color: var(--white);
  font-family: var(--sans); font-size: 11.5px; font-weight: 500; line-height: 1.4;
  letter-spacing: 0; text-transform: none; white-space: normal;
  max-width: 260px; padding: 6px 9px; border-radius: var(--r-sm);
  box-shadow: var(--shadow-sm); pointer-events: none;
}
/* Devanagari needs the extra leading here more than most places on the screen:
   this is small light text on a dark ground, set in a script with more detail
   per glyph than the Latin the size was chosen for. */
[lang="hi"] .scs-tip { line-height: 1.55; }


/* ── Segmented filters ────────────────────────────────────────────────────
   Owner 2026-08-05: pill-row radio groups for low-cardinality filters, in
   place of a <select> that costs an open-then-choose pair of clicks. Same
   base look as .side select/.sbtn on purpose — a new visual language here
   would read as a different kind of control rather than a faster version of
   the one next to it. Logic lives in shared/ui.js (initSegsets/syncSegsets);
   this is presentation only. */
.side .segset { display: flex; flex-wrap: wrap; gap: 5px; }
.side .segset button {
  flex: 1 1 auto; min-width: 40px;
  background: var(--side-card); color: var(--side-fg);
  border: 1px solid var(--side-line); border-radius: var(--r-sm);
  padding: 6px 4px; font-size: 11px; font-weight: 600; font-family: var(--sans);
  cursor: pointer; text-align: center; white-space: nowrap;
}
.side .segset button:hover { border-color: var(--side-acc); }
/* Selected = filled sand, same pairing as the "Filtered" badge and the same
   8.86:1 the ground itself is measured against — reversed here as rust text
   on a sand fill, which is the same ratio. */
.side .segset button.on {
  background: var(--side-acc); color: var(--rust); border-color: var(--side-acc);
}

/* ── Multi-select filters (SCS-084) ───────────────────────────────────────
   Owner 2026-08-05: stage and length become multi-select, and "group the
   dropdown filters together and pills together." Two presentations over one
   hidden-input holder — see initMultis() in shared/ui.js.

   .mset is the flat one, for sets you want to see all of at once. Same
   visual language as .segset beside it, because they are the same KIND of
   control; the only difference a person needs to feel is that these do not
   deselect each other. */
.side .mset:not(.journey) { display: flex; flex-wrap: wrap; gap: 4px; }
/* :not(.journey) is load-bearing, not tidiness. The journey uses .mset for its
   BEHAVIOUR (initMultis binds to the class) while looking nothing like a pill —
   each step carries a stage badge that already owns its colour. Without the
   exclusion, `.on` painted a solid sand fill across the whole button, the
   badge's translucent tint composited against sand instead of rust, and the
   selected step measured 1.12:1 with its tick at 1.0 — sand on sand. Caught in
   the contrast pass, not by eye. */
.side .mset:not(.journey) button {
  background: var(--side-card); color: var(--side-mut);
  border: 1px solid var(--side-line); border-radius: var(--r-pill);
  font-family: var(--sans); font-size: 11px; font-weight: 600;
  padding: 3px 9px; cursor: pointer; line-height: 1.5;
}
.side .mset:not(.journey) button:hover { border-color: var(--side-acc); color: var(--side-fg); }
.side .mset:not(.journey) button.on {
  background: var(--side-acc); color: var(--rust); border-color: var(--side-acc);
}

/* ── The journey, which is also the stage filter ──────────────────────────
   Owner 2026-08-05: "can we just use the 6 buttons under 'how a video moves'
   as the said filter?" Yes, and it removes a real redundancy — the flow was
   already the vocabulary, and a dropdown underneath it repeated the same six
   words in the same order for a second purpose.

   The connector stays. The flow's job was to say these are a SEQUENCE, and
   that reading is worth more than the couple of pixels it costs; a filter
   that also teaches the ladder is the whole reason this consolidation is an
   improvement rather than just one control fewer.

   Selection reads three ways, the same rule the transcript chips settled on
   in SCS-078: a ring, a check, and the fill. The stage badge inside already
   owns its colour and cannot carry selection too — the badge's colour means
   WHICH stage, so overloading it with "and this one is chosen" would make
   two facts fight over one channel. */
.journey { display: block; }
.journey .jstep {
  position: relative; display: block; width: 100%; text-align: left;
  background: none; border: 0; padding: 2px 4px 11px; cursor: pointer;
  font-family: var(--sans);
}
.journey .jstep:last-child { padding-bottom: 2px; }
.journey .jstep:not(:last-child)::before {
  content: ""; position: absolute; left: 13px; top: 21px; bottom: 2px;
  width: 2px; background: var(--side-line);
}
.journey .jstep .stage { position: relative; z-index: 1; }
.journey .jstep .tick {
  display: inline-block; width: 0; overflow: hidden; color: var(--side-acc);
  font-weight: 700; font-size: 11px; vertical-align: 1px;
}
.journey .jstep.on .tick { width: auto; margin-left: 5px; }
.journey .jstep:hover .stage { box-shadow: 0 0 0 2px rgba(246, 232, 207, .35); }
.journey .jstep.on .stage { box-shadow: 0 0 0 2px var(--side-acc); }
/* A light wash across the selected ROW is the third signal, after the ring and
   the tick — the same "state must read three ways" rule the transcript chips
   settled on in SCS-078, since one of the three is always the first a given
   person notices.
   It goes on the row rather than the badge because the badge's colour already
   means WHICH stage; a second meaning on the same channel would make two facts
   fight.

   DARKER, not lighter, and that is a contrast decision before it is a taste
   one. The rail is dark and every badge on it is light text; a sand wash lifts
   the ground toward the text and took 3-verifying from passing to 3.91:1. A
   dark inset moves the ground AWAY from the text, so selection reads as pressed
   AND every badge gains contrast instead of losing it. Same reasoning as the
   checkbox panel above, which is inset for the same reason. */
.journey .jstep.on { background: rgba(0, 0, 0, .20); border-radius: var(--r-sm); }
/* An earlier version dimmed every UNselected step to .45 opacity. It read well
   and failed AA outright: these are active controls, and 45% opacity took the
   stage badges from ~5.7:1 to roughly half that. Selection is shown by adding
   to the chosen ones, never by taking legibility off the rest. */

/* ── The checkbox disclosure ──────────────────────────────────────────────
   INLINE, not a floating panel. The rail is `overflow: hidden auto`, so an
   absolutely-positioned menu inside it is clipped at the rail's edge — the
   thing SCS-078 hit and solved by moving Columns and Views out entirely.
   Expanding in flow pushes the controls below it down, which the rail
   already scrolls. */
.side .mdrop { display: block; }
.side .mdrop-btn {
  width: 100%; display: flex; align-items: center; justify-content: space-between;
  gap: 8px; background: var(--side-card); color: var(--side-fg);
  border: 1px solid var(--side-line); border-radius: var(--r-sm);
  padding: 7px 10px; font-family: var(--sans); font-size: 12.5px; cursor: pointer;
  text-align: left;
}
.side .mdrop-btn:hover { border-color: var(--side-acc); }
.side .mdrop.has .mdrop-btn { border-color: var(--side-acc); color: var(--side-acc); font-weight: 600; }
.side .mdrop-btn .car { flex: none; font-size: 9px; opacity: .8; transition: transform .15s; }
.side .mdrop.open .mdrop-btn .car { transform: rotate(180deg); }
.side .mdrop-menu {
  margin: 5px 0 2px; padding: 4px; border-radius: var(--r-sm);
  background: rgba(0, 0, 0, .16); border: 1px solid var(--side-line);
  /* The facet lists are open-ended — 22 albums today, more as the archive grows.
     Capped and scrolled so one expanded panel can never push the journey filter
     off the bottom of the rail. */
  max-height: 232px; overflow-y: auto; overscroll-behavior: contain;
}
.side .mdrop-menu label {
  display: flex; align-items: center; gap: 8px; padding: 4px 6px;
  border-radius: 5px; font-size: 12px; color: var(--side-fg); cursor: pointer;
}
.side .mdrop-menu label:hover { background: rgba(255, 255, 255, .07); }
.side .mdrop-menu input { accent-color: var(--sand); flex: none; }
/* An album title is long and a count is short, so the name takes the slack and
   wraps rather than shoving the figure out of the panel. */
.side .mdrop-menu label span { flex: 1; min-width: 0; overflow-wrap: anywhere; }
.side .mdrop-menu label b {
  flex: none; font-size: 10.5px; font-weight: 600; color: var(--side-mut);
  font-variant-numeric: tabular-nums;
}

/* ── Text size ────────────────────────────────────────────────────────────
   Owner 2026-08-05: three sizes, ~10% apart, chosen on the left. Three "A"s
   at their own actual relative size are the control AND the preview — no
   label needed to explain what they do. Logic in shared/ui.js. */
.side .textsize { display: flex; gap: 5px; }
.side .textsize button {
  flex: 1; background: var(--side-card); color: var(--side-fg);
  border: 1px solid var(--side-line); border-radius: var(--r-sm);
  font-family: var(--serif); font-weight: 700; line-height: 1; cursor: pointer;
  padding: 5px 0;
}
.side .textsize button:hover { border-color: var(--side-acc); }
.side .textsize button.on { background: var(--side-acc); color: var(--rust); border-color: var(--side-acc); }

/* ── Language switcher (SCS-080) ──────────────────────────────────────────
   Sits directly beside the text-size control and is built from the same
   parts, because it is the same KIND of setting: how this screen reads for
   ME, stored per person rather than per screen. Grouping them says that
   without needing a heading to explain it.

   Two pills rather than a <select>: with exactly two options a dropdown
   costs an open-then-choose to reach a state that is already visible, and
   the same argument the owner made about the speed control applies here.

   The label is each language's own endonym — "हिन्दी", never "Hindi" —
   which is the one string on the screen a person who cannot read the
   current UI language still has to be able to find. */
.side .langsw { display: flex; gap: 5px; }
.side .langsw button {
  flex: 1; background: var(--side-card); color: var(--side-fg);
  border: 1px solid var(--side-line); border-radius: var(--r-sm);
  font-family: var(--sans); font-size: 11.5px; font-weight: 600; line-height: 1.35;
  cursor: pointer; padding: 5px 0;
}
.side .langsw button:hover { border-color: var(--side-acc); }
.side .langsw button.on { background: var(--side-acc); color: var(--rust); border-color: var(--side-acc); }

/* The editor has no rail — it is a scrolling document — so its copy lives in
   the header and takes the light-ground palette. Same shape, same behaviour,
   sized to sit in a row of header buttons without becoming one of them. */
.langsw { display: inline-flex; gap: 4px; }
.langsw button {
  background: var(--card); color: var(--muted);
  border: 1px solid var(--border); border-radius: var(--r-sm);
  font-family: var(--sans); font-size: 11.5px; font-weight: 600; line-height: 1.35;
  cursor: pointer; padding: 4px 9px;
}
.langsw button:hover { border-color: var(--line-gold); background: var(--hover); }
.langsw button.on { background: var(--gold-ink); border-color: var(--gold-ink); color: var(--white); }

/* ── Type and badges ─────────────────────────────────────────────────────── */

/* Headings carry the brand more than any colour does: Merriweather in indigo
   is the site's voice, and it costs nothing in a tool header. Scoped out of
   the rail, which has its own light-on-rust palette. */
:where(h1, h2, h3, .serif):not(.side *) { font-family: var(--serif); color: var(--indigo); }

/* Devanagari sets in the serif companion. Guruji's titles are the content
   here, not chrome, and the system Devanagari fallback the tools had been
   using is a UI font doing a job it was not cut for. */
.deva, .ttl, .seg-text { font-family: var(--deva); }

/* The eyebrow: uppercase, tracked. Latin-only by rule — six of the brand's
   eleven locales have no uppercase and tracking breaks conjuncts, so this
   class must never be put on Devanagari. */
.eyebrow {
  font-size: .7rem; font-weight: 600; letter-spacing: .16em;
  text-transform: uppercase; color: var(--amber-ink);
}

/* ── Devanagari typography (SCS-080) ──────────────────────────────────────
   The design system's own i18n card states the rule and this enforces it:
   uppercase is reserved for Latin-script labels, and TRACKING BREAKS
   CONJUNCTS. Devanagari has no upper case at all, so `text-transform` is
   merely inert there — the letter-spacing is the part that does damage. A
   conjunct like क्ष is drawn as one ligature and a matra hangs off the
   letter it belongs to; pushing every glyph apart by .14em separates them
   from their own base and turns संग्रह into something a reader has to
   decode. Measured on the live page before this rule existed: the rail
   headings were tracked 1.33px and the table headings 0.945px.

   Scoped to [lang="hi"] on <html>, so English keeps the eyebrow treatment
   that carries the brand — this removes the effect only where it harms.
   Specificity, not order, is what makes these win: each screen's own
   `.tbl th` (0,1,1) loads after this file, and `[lang="hi"] .tbl th`
   (0,2,1) outranks it wherever it sits.

   Weight is nudged up to compensate: at 9.5-10.5px, small-caps tracking was
   doing part of the work of making a heading read AS a heading, and simply
   removing it leaves the label limp against the rows beneath. */
[lang="hi"] .eyebrow,
[lang="hi"] .side .sgrp,
[lang="hi"] .tbl th,
[lang="hi"] .tlabel,
[lang="hi"] .side .fbadge,
[lang="hi"] .side .sclear-inline {
  letter-spacing: 0;
  text-transform: none;
}
/* Devanagari at 9.5px with no tracking is genuinely hard to read — the
   script carries more detail per glyph than Latin does, and these are the
   smallest labels on the screen. A point back on each, which the rail and
   the header both have room for. */
[lang="hi"] .side .sgrp { font-size: 10.5px; }
[lang="hi"] .tbl th     { font-size: 11.5px; }

.stage, .pill {
  border-radius: var(--r-pill); font-weight: 600; white-space: nowrap;
  display: inline-block;
}
.st-cataloged   { background: var(--stage-cataloged-bg);   color: var(--stage-cataloged-t); }
.st-transcribed { background: var(--stage-transcribed-bg); color: var(--stage-transcribed-t); }
.st-verifying   { background: var(--stage-verifying-bg);   color: var(--stage-verifying-t); }
.st-verified    { background: var(--stage-verified-bg);    color: var(--stage-verified-t); }
.st-approved    { background: var(--stage-approved-bg);    color: var(--stage-approved-t); }
.st-translated  { background: var(--stage-translated-bg);  color: var(--stage-translated-t); }

/* The rail's copy of the ramp. The one above is built for a white ground —
   --tint on rust is invisible and its dark ink unreadable — so the rail gets
   the same SEQUENCE at the other polarity: neutral, blue, saffron, green,
   solid green, rose. The ordering and the "4 and 5 are one hue, tint then
   solid" rule both survive; only the polarity flips.

   SCS-084 moved this out of verify-ui's own stylesheet. It lived there from
   SCS-078, which was harmless while that was the only screen with stage badges
   in its rail — the catalog's stage filter was a <select>. The moment the
   journey filter put the same badges in the CATALOG rail they fell back to the
   white-ground ramp and measured 1.38-2.50:1. Both rails read it from here
   now, which is what the shared layer is for. */
.side .stage         { font-size: 11px; padding: 1px 8px; }
.side .st-cataloged  { background: rgba(255, 255, 255, .12); color: rgba(255, 255, 255, .88); }
.side .st-transcribed{ background: rgba(190, 208, 255, .18); color: #cfdaff; }
.side .st-verifying  { background: rgba(244, 162,  59, .22); color: #fac98a; }
.side .st-verified   { background: rgba(140, 216, 166, .18); color: #9fdcb4; }
.side .st-approved   { background: #9fdcb4;                  color: #173d26; }
.side .st-translated { background: rgba(255, 168, 201, .18); color: #ffc2d6; }

/* ── Chips inside a table cell ────────────────────────────────────────────
   Owner 2026-08-05: "the table headers seem not aligned with the data below."
   Measured on the live catalog, and it was two separate faults:

   1. `.tbl td.num` is right-aligned but the matching `th.num` rule was never
      written, so every numeric column had a LEFT-aligned heading over
      RIGHT-aligned figures — Sequence was out by 51px, Resolution by 29,
      Length by 22. Fixed beside each screen's own td.num rule.

   2. This one. A chip carries its own horizontal padding, so while its BOX
      starts on the cell's text edge like every other column, its GLYPHS start
      another 9-10px in. Three columns of chips reading as indented against
      nine columns of plain text is the raggedness the owner could see.

   The chip moves, not the heading. The table's left rhythm is set by the
   DATA — that is what the eye scans down — so the outlier is pulled into
   line with the other columns rather than the heading being pushed out to
   meet it, which would have left the heading row itself ragged. Nothing is
   clipped: there are no vertical rules between cells, so the chip's fill
   simply reaches nearer its own cell edge.

   `--cx` is each chip's own left padding, declared next to that padding so
   the two cannot drift apart. Anything without it — a bare date, a link, the
   progress bar, the shape icon — resolves the var to 0px and is untouched,
   which is why this is one rule rather than a list of selectors to maintain. */
.tbl td > :first-child { margin-left: calc(-1 * var(--cx, 0px)); }
/* Right-aligned columns hang off the other edge, or the pull would push the
   chip further from the figure it is aligned to. */
.tbl td.num > :first-child { margin-left: 0; margin-right: calc(-1 * var(--cx, 0px)); }

.pill.green  { background: var(--ok-bg);   color: var(--ok); }
.pill.yellow { background: var(--warn-bg); color: var(--warn); }
.pill.red    { background: var(--bad-bg);  color: var(--bad); }
.pill.blue   { background: var(--info-bg); color: var(--info); }
.pill.grey   { background: var(--tint);    color: var(--muted); }

/* Focus is a gold ring, everywhere, on everything. The tools are keyboard-
   driven (Tab walks segments in verify) and the browser default outline was
   invisible against cream. */
:where(a, button, input, select, textarea, [tabindex]):focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 1px;
  border-radius: var(--r-sm);
}

/* ── Narrow viewports ─────────────────────────────────────────────────────
   Placed LAST on purpose. Every rule this overrides — .shell, .side, .main,
   .listbox, .textsize — is unconditional and defined earlier in this same
   file; at equal specificity CSS breaks the tie by source order, so a
   media-scoped override has to come after everything it means to beat or it
   silently loses regardless of whether the query matches. Learned by
   shipping it too early once: the media query was matching correctly but
   every property still lost to the later unconditional rule.

   The desktop shell has a fixed 258px rail beside a flex column with no
   width of its own to give up — below ~860px the main column runs out of
   room for the search box and pager row (neither wraps), and with
   `body{overflow:hidden}` set per screen there is no way to scroll sideways
   to reach them: they are simply unreachable, not just tight. Found while
   verifying this pass at the width the owner's own screenshots were taken
   at, not something asked for directly, but "cut off with no way to see it"
   is a step past "needs polish."

   Below the breakpoint the shell stops being two panes and becomes one
   scrolling column: the rail's content sits at natural width above the
   list, the page itself scrolls, and the list drops its own internal
   scroller (double scrollbars — one for the page, one for a box within it —
   are the thing this breakpoint exists to avoid). */
@media (max-width: 860px) {
  /* !important because each screen's own <style> sets this unconditionally
     (verify's is `body.queueing`, more specific still) and loads AFTER this
     file, so a plain override here would lose the cascade even while the
     media query matches. Without it the breakpoint below would remove every
     inner scroller and leave the page with nowhere to scroll at all — worse
     than the sideways cut-off it exists to fix. */
  body { overflow: auto !important; }
  /* The text-size control is a desktop reading-comfort choice made against
     spare rail width. At this breakpoint there is no spare width — zoom
     inflates every box by the same factor as the viewport, so a saved 110%
     or 120% preference reintroduced exactly the horizontal cut-off this
     breakpoint exists to remove. !important because the preference is set
     as an inline style (shared/ui.js), which this has to outrank. */
  html { zoom: 1 !important; }
  .shell { flex-direction: column; height: auto; min-height: 100vh; }   /* zoom is forced to 1 above, so no division here */
  .side {
    width: auto; height: auto; overflow: visible; scrollbar-gutter: auto;
    box-shadow: 0 8px 26px -10px rgba(16, 11, 43, .45);
  }
  .main { height: auto; }
  .listbox { overflow: visible; }
  /* Forced inert by the zoom override above — showing a control that no
     longer does anything is worse than not showing it. */
  .side .textsize { display: none; }
}

/* ── The table (SCS-094) ──────────────────────────────────────────────────
   This SHOULD have been here all along. brand.css already carried the
   `[lang="hi"] .tbl th` adjustment while the base it adjusts lived duplicated in
   catalog-ui and verify-ui — the shared file was overriding a rule it did not
   own, which is how the studio's first table rendered as raw browser default:
   centred 16px headings, no fill, 1px padding.

   These are the rules both tools already declare IDENTICALLY. Their own copies
   are left in place for now and simply re-declare the same values, so neither
   tool's rendering can change; removing those duplicates is a separate, checkable
   edit rather than something to fold into a feature branch. Anything genuinely
   per-tool — the catalog's `td.ttl` width behaviour, verify's row hover — stays
   where it is. */
.tbl { width: 100%; border-collapse: collapse; font-size: 13px; }
.tbl th {
  background: var(--sand); color: var(--gold-ink);
  font-size: 10.5px; font-weight: 700; letter-spacing: .09em; text-transform: uppercase;
  text-align: left; padding: 10px 11px; white-space: nowrap;
  border-bottom: 2px solid var(--gold);
}
.tbl td {
  padding: 9px 11px; border-top: 1px solid var(--border);
  vertical-align: middle; white-space: nowrap;
}
.tbl td.num, .tbl th.num { text-align: right; }
.tbl td.num { font-variant-numeric: tabular-nums; }

/* ── SCS-091 · Sign in, identity, session expiry ──────────────────────────── */

/* The login screen is the one page in the suite that is NOT a dense data tool,
   so it is the one place the brand gets to be itself: the site's own washes on
   cream, a Merriweather mark, and generous spacing. The tool layer's tightened
   radii and 11px labels would make a sign-in card look like a filter panel. */
.authbody {
  min-height: 100vh;
  display: flex; align-items: center; justify-content: center;
  padding: 32px 20px;
}
.authcard {
  width: 100%; max-width: 380px;
  background: var(--card);
  border: 1px solid var(--border); border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
  padding: 30px 32px 24px;
}
.authhead { text-align: center; margin-bottom: 22px; }
/* --serif, NOT --deva. The mark is bilingual now (SCS-091, owner 2026-08-05) and
   --deva puts Noto Serif Devanagari first, which does carry Latin glyphs — so the
   English form would have set in Noto's Latin instead of Merriweather, quietly
   off-brand. --serif leads with Merriweather and falls through to Noto for the
   Devanagari, which is the per-CHARACTER rule the rest of this file already
   follows and the reason those families are appended to the base stacks. */
.authmark {
  font-family: var(--serif); font-size: 22px; font-weight: 500;
  color: var(--indigo); line-height: 1.3;
}
.authsub { font-size: 12.5px; color: var(--muted); margin-top: 3px; line-height: 1.5; }

.authttl { font-family: var(--serif); font-size: 17px; color: var(--indigo); margin-bottom: 6px; }
.authnote { font-size: 13px; color: var(--muted); line-height: 1.6; margin: 0 0 18px; }

.authlab {
  display: block; font-family: var(--sans); font-size: 12px; font-weight: 600;
  color: var(--muted); margin-bottom: 5px;
}
.authin {
  width: 100%; box-sizing: border-box; height: 38px; padding: 0 11px;
  background: var(--white); color: var(--text);
  border: 1px solid var(--border); border-radius: var(--r-sm);
  font-family: var(--sans); font-size: 14px; margin-bottom: 14px;
}
.authin:focus {
  outline: none; border-color: var(--gold);
  box-shadow: 0 0 0 3px rgba(200, 155, 60, 0.18);
}
.authgo {
  width: 100%; height: 40px; margin-top: 4px;
  background: var(--indigo); color: var(--cream);
  border: none; border-radius: var(--r-sm);
  font-family: var(--sans); font-size: 14px; font-weight: 600; cursor: pointer;
}
.authgo:hover:not(:disabled) { background: var(--indigo-deep); }
.authgo:disabled { opacity: .65; cursor: default; }

/* Error text, not an error BOX: on a card this narrow a tinted panel with its own
   border reads as a bigger failure than "that passphrase was wrong" deserves. */
.autherr { color: var(--bad); font-size: 12.5px; line-height: 1.5; margin: 0 0 10px; }

/* The rules go grey -> green as they are met, and start as ○ rather than ✗ —
   nothing has failed yet when the field is still empty, and opening a form with
   two red crosses on it is a small hostility. */
.authrules { list-style: none; margin: 0 0 12px; padding: 0; font-size: 12.5px; }
.authrules li { color: var(--muted); line-height: 1.85; }
.authrules li.on { color: var(--ok); }
.authtick { display: inline-block; width: 15px; }
.authhint { font-size: 12px; color: var(--muted); line-height: 1.55; margin: 0 0 14px; }

.authask {
  text-align: center; font-size: 12px; color: var(--muted);
  margin: 16px 0 0; padding-top: 14px; border-top: 1px solid var(--border);
}
.authlang { display: flex; justify-content: center; margin-top: 14px; }

/* ── The identity chip, in the rail footer ────────────────────────────────
   SCS-092: the studio's rail is .st-rail rather than .side (it is a different
   app with its own layout), so each selector names both. One definition, because
   this chip answering differently in one of the three tools would be exactly the
   drift the shared stylesheet exists to prevent.
   Sits on --side-bg with the rail's own light-on-rust palette, so it uses the
   rail tokens rather than the page ones. Pinned to the bottom of the rail
   because it answers a question nobody asks twice ("am I still Anita?") and
   should never compete with the filters for the top of the column. */
/* [hidden] is a UA rule (`[hidden]{display:none}`) and ANY author `display`
   declaration outranks it — so a component that sets its own display must
   restore the attribute's meaning explicitly, or the markup's `hidden` becomes
   decorative. Both of these ship hidden and are revealed from JS, so without
   this pair they render empty from first paint: the chip as a blank avatar and
   a nameless row, the expiry banner as a false alarm on a healthy session.
   Caught by reading computed style rather than trusting the attribute. */
.side .me[hidden], .st-rail .me[hidden], .expbar[hidden] { display: none !important; }

.side .me, .st-rail .me {
  display: flex; align-items: center; gap: 9px;
  margin-top: auto; padding-top: 12px;
  border-top: 1px solid rgba(255, 255, 255, 0.16);
}
.side .me-av, .st-rail .me-av {
  width: 29px; height: 29px; flex: 0 0 29px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  background: rgba(255, 255, 255, 0.16); color: var(--side-acc);
  font-family: var(--sans); font-size: 11.5px; font-weight: 700;
}
.side .me-who, .st-rail .me-who { min-width: 0; flex: 1; }
.side .me-name, .st-rail .me-name {
  font-size: 12.5px; color: var(--side-fg); font-weight: 600;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.side .me-role, .st-rail .me-role { font-size: 11px; color: var(--side-acc); }
.side .me-out, .st-rail .me-out {
  flex: 0 0 auto; background: none; border: none; cursor: pointer;
  color: var(--side-mut); padding: 4px; border-radius: var(--r-sm); line-height: 0;
}
.side .me-out:hover, .st-rail .me-out:hover { color: var(--side-fg); background: rgba(255, 255, 255, 0.12); }

/* ── Session expired, mid-edit ────────────────────────────────────────────
   Amber rather than red: nothing has been lost at this point — that is the
   entire message — and a red panel over a page still holding unsaved work
   says the opposite of what is true. */
.expbar {
  display: flex; align-items: flex-start; gap: 10px;
  background: var(--warn-bg); border: 1px solid var(--line-gold);
  border-radius: var(--r); padding: 11px 13px; margin-bottom: 12px;
}
.expbar .exp-t { font-size: 13px; color: var(--text); font-weight: 600; margin-bottom: 2px; }
.expbar .exp-s { font-size: 12px; color: var(--gold-ink); line-height: 1.55; }
.expbar .exp-b {
  flex: 0 0 auto; height: 30px; padding: 0 13px; align-self: center;
  background: var(--indigo); color: var(--cream); border: none;
  border-radius: var(--r-sm); font-family: var(--sans); font-size: 12.5px;
  font-weight: 600; cursor: pointer;
}
.expbar .exp-b:hover { background: var(--indigo-deep); }
/* A segment holding text the server has not accepted. Dimmed rather than
   disabled: it must stay readable and selectable so the words can be copied
   out if everything else fails. */
.seg.stale { opacity: .62; }

/* SCS-091: cosmetic only — every one of these is re-checked server-side, and the
   rule is !important because paintStage() sets display inline. A volunteer seeing
   Approve and being refused on click is worse than never seeing it. */
body:not(.is-coordinator) #approve,
body:not(.is-coordinator) #reopen { display: none !important; }

/* SCS-101: the studio answers a volunteer 403 (server/index.mjs), so its entry in
   the catalog and verify rails was a link nobody below coordinator may follow.
   Same reasoning as the rule above. Cosmetic only; the studio enforces its own
   floor. The `.navb.dash` half of this rule went with the Dashboard entries when
   the owner asked for them off these two screens. */
body:not(.is-coordinator) .navb.studio,
body:not(.is-coordinator) .btn.tool-studio { display: none !important; }
