/* Translation review pages. The design system is docs/design.md: tokens, type, components and the rules behind them.
   Class names are the contract with common.js, review.js and admin.js; a renamed class is renamed everywhere at once. */

/* 1 Tokens: light by default, dark from the system or forced, more contrast, reduced transparency, touch sizes */
:root {
  color-scheme: light;
  --font: -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
  --mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  --bg: #f2f2f7; --surface: #ffffff; --elevated: #ffffff;
  --fill: rgba(118, 118, 128, 0.12); --fill-2: rgba(118, 118, 128, 0.2);
  --text: #1d1d1f; --text-2: #6e6e73; --text-3: #aeaeb2; --gray-text: #6b6b70;
  --separator: rgba(60, 60, 67, 0.29); --field-border: #8e8e93;
  --link: #0066cc; --accent-fill: #0071e3; --focus: #0071e3; --on-fill: #ffffff;
  --blue: #0066cc; --blue-soft: rgba(0, 113, 227, 0.12);
  --green: #207c37; --green-soft: rgba(52, 199, 89, 0.16); --green-fill: #23863c; --green-graphic: #23863c;
  --orange: #c93400; --orange-soft: rgba(255, 149, 0, 0.16); --orange-graphic: #ff9500;
  --red: #d70015; --red-soft: rgba(255, 59, 48, 0.12); --red-fill: #ea0c00;
  --purple: #8542a7; --purple-soft: rgba(175, 82, 222, 0.14);
  --pair: #ff9f0a; --pair-edge: #d38000; --box: #2997ff; --box-bad: #ff453a; --box-done: #30d158;
  --seg-thumb: #ffffff; --seg-shadow: 0 3px 8px rgba(0, 0, 0, 0.12), 0 3px 1px rgba(0, 0, 0, 0.04);
  --chrome: rgba(249, 249, 249, 0.94); --chrome-solid: #f9f9f9;
  --toast-bg: rgba(29, 29, 31, 0.9); --toast-solid: #1d1d1f; --scrim: rgba(0, 0, 0, 0.32);
  --shadow-float: 0 12px 32px rgba(0, 0, 0, 0.18), 0 2px 8px rgba(0, 0, 0, 0.08);
  /* the spacing scale of docs/design.md (--sp-11, 44 px, has no user today) */
  --sp-1: 0.25rem; --sp-2: 0.5rem; --sp-3: 0.75rem; --sp-4: 1rem; --sp-5: 1.25rem; --sp-6: 1.5rem; --sp-8: 2rem; --sp-11: 2.75rem;
  --r-xs: 4px; --r-chip: 6px; --r-md: 10px; --r-lg: 14px; --r-xl: 20px; --r-full: 999px;
  --control: 2.5rem; --control-sm: 2rem; --row: 2.75rem;
  --page-w: 760px;
  --head: 2.75rem; --foot: 0px;          /* replaced at run time by common.js (measured) */
  --ease-out: cubic-bezier(0.2, 0.8, 0.2, 1);
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    color-scheme: dark;
    --bg: #000000; --surface: #1c1c1e; --elevated: #2c2c2e;
    --fill: rgba(118, 118, 128, 0.24); --fill-2: rgba(118, 118, 128, 0.32);
    --text: #f5f5f7; --text-2: #98989d; --text-3: #636366; --gray-text: #a1a1a6;
    --separator: rgba(84, 84, 88, 0.6); --field-border: #8e8e93;
    --link: #2997ff; --accent-fill: #0071e3; --focus: #2997ff;
    --blue: #409cff; --blue-soft: rgba(10, 132, 255, 0.18);
    --green: #30d158; --green-soft: rgba(48, 209, 88, 0.18); --green-fill: #23863c; --green-graphic: #30d158;
    --orange: #ff9f0a; --orange-soft: rgba(255, 159, 10, 0.18); --orange-graphic: #ff9f0a;
    --red: #ff6961; --red-soft: rgba(255, 69, 58, 0.16); --red-fill: #ea0c00;
    --purple: #da8fff; --purple-soft: rgba(191, 90, 242, 0.18);
    --pair-edge: #ff9f0a;
    --seg-thumb: #636366; --seg-shadow: none;
    --chrome: rgba(28, 28, 30, 0.94); --chrome-solid: #1c1c1e;
    --toast-bg: rgba(72, 72, 74, 0.92); --toast-solid: #3a3a3c; --scrim: rgba(0, 0, 0, 0.55);
    --shadow-float: 0 12px 32px rgba(0, 0, 0, 0.6);
  }
}
/* Forced dark: an exact copy of the block above, kept in sync by hand (CSS can't share a declaration block) */
:root[data-theme="dark"] {
  color-scheme: dark;
  --bg: #000000; --surface: #1c1c1e; --elevated: #2c2c2e;
  --fill: rgba(118, 118, 128, 0.24); --fill-2: rgba(118, 118, 128, 0.32);
  --text: #f5f5f7; --text-2: #98989d; --text-3: #636366; --gray-text: #a1a1a6;
  --separator: rgba(84, 84, 88, 0.6); --field-border: #8e8e93;
  --link: #2997ff; --accent-fill: #0071e3; --focus: #2997ff;
  --blue: #409cff; --blue-soft: rgba(10, 132, 255, 0.18);
  --green: #30d158; --green-soft: rgba(48, 209, 88, 0.18); --green-fill: #23863c; --green-graphic: #30d158;
  --orange: #ff9f0a; --orange-soft: rgba(255, 159, 10, 0.18); --orange-graphic: #ff9f0a;
  --red: #ff6961; --red-soft: rgba(255, 69, 58, 0.16); --red-fill: #ea0c00;
  --purple: #da8fff; --purple-soft: rgba(191, 90, 242, 0.18);
  --pair-edge: #ff9f0a;
  --seg-thumb: #636366; --seg-shadow: none;
  --chrome: rgba(28, 28, 30, 0.94); --chrome-solid: #1c1c1e;
  --toast-bg: rgba(72, 72, 74, 0.92); --toast-solid: #3a3a3c; --scrim: rgba(0, 0, 0, 0.55);
  --shadow-float: 0 12px 32px rgba(0, 0, 0, 0.6);
}
@media (prefers-contrast: more) {
  :root { --text-2: #3c3c43; --gray-text: #3c3c43; --link: #0040dd; --blue: #0040dd; --separator: #8e8e93; }
}
@media (prefers-contrast: more) and (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) { --text-2: #c7c7cc; --gray-text: #c7c7cc; --link: #409cff; --blue: #409cff; --separator: #8e8e93; }
}
@media (prefers-contrast: more) {
  :root[data-theme="dark"] { --text-2: #c7c7cc; --gray-text: #c7c7cc; --link: #409cff; --blue: #409cff; --separator: #8e8e93; }
}
/* Reduced transparency: the bars and the toast paint opaque (section 8 also drops their blur). The dark selectors are
   repeated so this outranks the dark blocks above in every theme. */
@media (prefers-reduced-transparency: reduce) {
  :root, :root:not([data-theme="light"]), :root[data-theme="dark"] { --chrome: var(--chrome-solid); --toast-bg: var(--toast-solid); }
}
@media (pointer: coarse) { :root { --control: 2.75rem; --control-sm: 2.75rem; } }

/* 2 Base and type */
* { box-sizing: border-box; }
[hidden] { display: none !important; }
html { font-size: 100%; -webkit-text-size-adjust: 100%; -webkit-tap-highlight-color: transparent; }
body { margin: 0; background: var(--bg); color: var(--text); font-family: var(--font); font-size: 1.0625rem;
  line-height: 1.47; font-optical-sizing: auto; overflow-x: clip; }
button, input, textarea, select { font: inherit; color: inherit; }
a { color: var(--link); text-decoration-thickness: 1px; text-underline-offset: 0.18em;
  text-decoration-color: color-mix(in srgb, currentColor 40%, transparent); }
@media (hover: hover) { a:hover { text-decoration-color: currentColor; } }
h1, h2, h3 { text-wrap: balance; }
p, li { text-wrap: pretty; }
:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
.small { font-size: 0.8125rem; line-height: 1.38; letter-spacing: 0.006em; }
.muted { color: var(--text-2); }
.mono, code, .cmd, .key, .linkfield { font-family: var(--mono); }
code { font-size: 0.8125rem; background: var(--fill); padding: 1px 4px; border-radius: var(--r-xs); }
.spacer { flex: 1; }
.sr-only { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
.label { display: block; font-size: 0.75rem; line-height: 1.33; font-weight: 600; letter-spacing: 0.05em;
  text-transform: uppercase; color: var(--text-2); margin: var(--sp-3) 0 var(--sp-1); }
.label-note { text-transform: none; letter-spacing: 0.006em; font-weight: 400; }
/* a label opening an editor slot sits on the padding, not 12 px under it */
.edit-slot > .label:first-child, .srow-edit > .label:first-child { margin-top: 0; }
.context { font-size: 0.9375rem; line-height: 1.4; color: var(--text-2); margin: var(--sp-1) 0 0; }
.context b { color: var(--text); font-weight: 600; }
.actions { display: flex; flex-wrap: wrap; gap: var(--sp-2); align-items: center; margin-top: var(--sp-3); }
.list-count { font-size: 0.8125rem; color: var(--text-2); margin: var(--sp-2) 0; font-variant-numeric: tabular-nums; }
/* design-system utility (docs/design.md, build-spec §1.6): no page uses it today */
@media (max-width: 559px) { .hide-phone { display: none; } }

/* 3 Layout and chrome: page column, nav bar, large title, footer toolbar, toast, dialog */
.wrap { max-width: var(--page-w); margin-inline: auto;
  padding-inline: max(var(--sp-4), env(safe-area-inset-left)) max(var(--sp-4), env(safe-area-inset-right)); }
.wide { --page-w: 1180px; }
/* --foot already holds the footer's safe-area padding; max() covers pages without a footer and the footer hidden while
   editing */
main.wrap { padding-top: var(--sp-2); padding-bottom: calc(max(var(--foot), env(safe-area-inset-bottom)) + var(--sp-8)); }

/* Nav bar, iOS scroll-edge appearance: transparent while nothing is under it, material and hairline together once
   content scrolls under it (common.js toggles .scrolled) */
.navbar { position: sticky; top: 0; z-index: 20; padding-top: env(safe-area-inset-top); background-color: transparent;
  transition: background-color 200ms; }
.navbar.scrolled { background-color: var(--chrome); -webkit-backdrop-filter: saturate(180%) blur(20px);
  backdrop-filter: saturate(180%) blur(20px); }
.navbar::after { content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 1px; background: var(--separator);
  opacity: 0; transition: opacity 200ms; pointer-events: none; }
.navbar.scrolled::after { opacity: 1; }
@media (min-resolution: 2dppx) { .navbar::after { transform: scaleY(0.5); transform-origin: bottom; } }
/* The side tracks never go below their content (the back control and the trailing area keep their full width) and the
   title track takes what is left, so a long bar title truncates before the back control does (iOS); with room to
   spare, the equal 1fr sides keep the title centred */
.navbar-row { display: grid; grid-template-columns: minmax(max-content, 1fr) minmax(0, max-content) minmax(max-content, 1fr);
  grid-template-areas: "lead title trail"; align-items: center; column-gap: var(--sp-2); min-height: var(--row); }
/* The chevron sits 6 px into the gutter, as on iOS. A shift, not a negative margin: the grid sizes the track from the
   margin box, so a -6px margin made the track 6 px too narrow and the label ellipsised ("Overvi…") */
.nav-back { grid-area: lead; justify-self: start; display: inline-flex; align-items: center; gap: 2px; min-height: var(--row);
  position: relative; left: -6px; padding-right: var(--sp-2); color: var(--link); text-decoration: none;
  font-size: 1.0625rem; max-width: 100%; transition: opacity 160ms var(--ease-out); }
.nav-back::before { content: ""; width: 0.625rem; height: 0.625rem; border: solid currentColor; border-width: 0 0 2.5px 2.5px;
  transform: rotate(45deg); margin: 0 4px 0 8px; flex: none; }
#nav-back-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.nav-back:active { opacity: 0.5; transition-duration: 0ms; }
.nav-title { grid-area: title; min-width: 0; font: 600 1.0625rem/1.3 var(--font); text-align: center; white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis; opacity: 0; transition: opacity 200ms; }
.navbar.titled .nav-title { opacity: 1; }
.nav-trail { grid-area: trail; justify-self: end; min-width: 0; }
.nav-who { display: block; font-size: 0.9375rem; font-weight: 500; color: var(--text); white-space: nowrap; overflow: hidden;
  text-overflow: ellipsis; }
.nav-seg { grid-area: trail; justify-self: end; }
@media (max-width: 559px) {
  .nav-trail { display: none; }
  /* The owner's segmented control takes its own full-width row; the first row keeps the 44 px of an iOS bar.
     .navbar .nav-seg outranks .seg { margin: 0 } of section 4. contain: inline-size: a row-spanning item's max-content
     would otherwise be shared out to the two minmax(max-content, 1fr) side tracks and squeeze the title track;
     contained, it has no intrinsic width and simply stretches across the row. */
  .page-admin .navbar-row { grid-template-areas: "lead title trail" "seg seg seg"; grid-template-rows: minmax(var(--row), auto) auto; }
  .navbar .nav-seg { grid-area: seg; justify-self: stretch; margin-bottom: var(--sp-2); contain: inline-size; }
  .nav-seg .seg-item { flex: 1 1 0; }
}
/* 2 px indeterminate line on the bar's bottom edge, shown by common.js when a page takes longer than 300 ms */
.nav-progress { position: absolute; left: 0; right: 0; bottom: 0; z-index: 1; height: 2px; overflow: hidden; }
.nav-progress::before { content: ""; position: absolute; inset: 0; width: 40%; background: var(--accent-fill);
  animation: nav-progress 1s var(--ease-out) infinite; }
@keyframes nav-progress { from { transform: translateX(-100%); } to { transform: translateX(250%); } }

/* Large title: the page's h1, first in the content; the bar shows the same title once it scrolls away */
.large-title-block { margin: var(--sp-2) 0 var(--sp-4); }
.large-title { margin: 0; font: 700 2.125rem/1.2 var(--font); letter-spacing: -0.022em; overflow-wrap: break-word; }
/* focused only by script after a navigation (tabindex="-1"); neither is interactive */
.large-title:focus, .state-title:focus { outline: none; }
.large-sub { margin: var(--sp-1) 0 0; font-size: 0.9375rem; line-height: 1.4; color: var(--text-2); }
/* Screen titles run to 73 characters (median 29): on a phone the screen page's title is title 1 */
@media (max-width: 559px) { .page-head .large-title { font-size: 1.75rem; line-height: 1.21; letter-spacing: -0.02em; } }

/* Footer toolbar: fixed, chrome material, top hairline always */
.footer-bar { position: fixed; left: 0; right: 0; bottom: 0; z-index: 20; background: var(--chrome);
  -webkit-backdrop-filter: saturate(180%) blur(20px); backdrop-filter: saturate(180%) blur(20px);
  padding-bottom: env(safe-area-inset-bottom); }
.footer-bar::before { content: ""; position: absolute; left: 0; right: 0; top: 0; height: 1px; background: var(--separator);
  pointer-events: none; }
@media (min-resolution: 2dppx) { .footer-bar::before { transform: scaleY(0.5); transform-origin: top; } }
.footer-row { display: flex; align-items: center; gap: var(--sp-2); min-height: calc(var(--control) + var(--sp-4));
  padding-block: var(--sp-2); }
.foot-status { flex: 1; min-width: 0; display: flex; flex-direction: column; line-height: 1.25; font-variant-numeric: tabular-nums; }
/* on the material, text is --text, never --text-2 (vibrancy) */
.foot-main { font-size: 0.9375rem; font-weight: 600; color: var(--text); }
.foot-sub { font-size: 0.8125rem; color: var(--text); }
.footer-row .btn { white-space: nowrap; }
/* Skip is a plain text button in --link: 5.29:1 on the bar */
.foot-skip { flex: none; }
.foot-error:not([hidden]) { padding-bottom: var(--sp-2); }
/* The longest labels ("Mark verified, next screen ›", "Next to decide" + "Approve batch…") leave the status 11–20 px on
   one 375 px row: under 560 px the status takes the first line and the actions share the second (--foot is measured) */
@media (max-width: 559px) {
  .footer-row { flex-wrap: wrap; row-gap: var(--sp-1); }
  .foot-status { flex: 1 0 100%; flex-direction: row; flex-wrap: wrap; column-gap: var(--sp-2); align-items: baseline; }
  .footer-row > .btn:not(.foot-skip) { flex: 1 1 0; }
}
/* the keyboard and the editor get the room */
@media (max-width: 819px) { body.editing .footer-bar { display: none; } }

/* Toast: centred above the footer or the home indicator. No transform and no opacity here: motion.js owns both. */
.toast { position: fixed; left: 0; right: 0; margin-inline: auto; width: max-content; max-width: min(30rem, calc(100% - 2rem));
  bottom: calc(var(--sp-4) + env(safe-area-inset-bottom)); z-index: 40; padding: var(--sp-3) var(--sp-4);
  border-radius: var(--r-lg); background: var(--toast-bg); -webkit-backdrop-filter: blur(20px); backdrop-filter: blur(20px);
  color: #fff; font-size: 0.9375rem; font-weight: 500; line-height: 1.35; text-align: center; box-shadow: var(--shadow-float); }
/* an error toast closes on tap */
.toast.bad { background: var(--red-fill); cursor: pointer; }
/* with the footer hidden while editing, --foot is 0px: the toast still clears the home indicator */
body.has-footer .toast { bottom: calc(max(var(--foot), env(safe-area-inset-bottom)) + var(--sp-3)); }

/* Dialog: the native <dialog> covers the viewport and is the scrim; its panel is centred at every width. After it
   closes, common.js reopens it non-modal for the exit animation, with pointer-events off. motion.js owns the opacity of
   both and the scale of the panel. */
dialog.dialog { position: fixed; inset: 0; z-index: 50; width: 100%; height: 100%; max-width: none; max-height: none; margin: 0;
  padding: max(var(--sp-4), env(safe-area-inset-top)) max(var(--sp-4), env(safe-area-inset-right))
    max(var(--sp-4), env(safe-area-inset-bottom)) max(var(--sp-4), env(safe-area-inset-left));
  border: 0; background: var(--scrim); color: var(--text); display: grid; place-items: center; overflow: auto;
  overscroll-behavior: contain; }
dialog.dialog:not([open]) { display: none; }
dialog.dialog::backdrop { background: transparent; }
.dialog-panel { width: min(100%, 30rem); max-height: calc(100vh - 2rem); max-height: calc(100dvh - 2rem); overflow: auto;
  background: var(--elevated); border-radius: var(--r-xl); box-shadow: var(--shadow-float); padding: var(--sp-6); }
.dialog-title { margin: 0 0 var(--sp-2); font: 600 1.0625rem/1.3 var(--font); }
.dialog-text { color: var(--text-2); font-size: 0.9375rem; line-height: 1.4; }
.dialog-text p { margin: 0 0 var(--sp-2); }
.dialog-text b { color: var(--text); }
.dialog-field { width: 100%; margin-top: var(--sp-2); }
/* Cancel left, the action right; a single action spans */
.dialog-actions { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-2); margin-top: var(--sp-5); }
.dialog-actions > :only-child { grid-column: 1 / -1; }
.notice-steps, .notice-privacy { padding-left: var(--sp-5); margin: var(--sp-2) 0; }
.notice-steps li, .notice-privacy li { margin: var(--sp-1) 0; }

/* 4 Components */

/* Buttons: capsules, as on iOS/macOS 26 and like the filter chips; .btn.icon is a circle. a.btn looks exactly like
   button.btn. --spin colours the busy spinner, which can't use currentColor: the label is transparent while it turns. */
.btn { --spin: var(--text); display: inline-flex; align-items: center; justify-content: center; gap: var(--sp-2);
  min-height: var(--control); padding: 0 var(--sp-4); border: 0; border-radius: var(--r-full); background-color: var(--fill);
  color: var(--text); font-size: 1rem; font-weight: 600; line-height: 1.2; letter-spacing: 0; text-align: center;
  text-decoration: none; cursor: pointer; touch-action: manipulation; position: relative;
  transition: transform 160ms var(--ease-out); }
.btn.primary { --spin: #fff; background-color: var(--accent-fill); color: var(--on-fill); }
.btn.ok { --spin: #fff; background-color: var(--green-fill); color: var(--on-fill); }
.btn.destructive { --spin: #fff; background-color: var(--red-fill); color: var(--on-fill); }
.btn.plain { --spin: var(--link); background-color: transparent; color: var(--link); padding-inline: var(--sp-2); }
.btn.danger { --spin: var(--red); color: var(--red); }
.btn.small { min-height: var(--control-sm); padding-inline: var(--sp-3); font-size: 0.9375rem; }
.btn.plain.small { padding-inline: var(--sp-2); }
.btn.block { display: flex; width: 100%; }
.btn.icon { width: var(--control); padding: 0; }   /* design-system variant: no page uses it today */
/* A toggle (Show on the picture) shows its pressed state, not only in aria-pressed. A plain one takes the blue tint
   with --blue words (4.72:1 light, 4.83:1 dark): --link on --fill falls to 4.23:1 in dark. */
.btn[aria-pressed="true"] { background-color: var(--fill-2); }
.btn.plain[aria-pressed="true"] { background-color: var(--blue-soft); color: var(--blue); }
/* hover before press: a pressed button under the mouse shows the press */
@media (hover: hover) {
  .btn:hover:not(:disabled) { background-image: linear-gradient(rgba(128, 128, 128, 0.1), rgba(128, 128, 128, 0.1)); }
}
/* Press at once on pointer-down, ease back in 160 ms on release */
.btn:active:not(:disabled) { transform: scale(0.97); transition-duration: 0ms;
  background-image: linear-gradient(rgba(0, 0, 0, 0.1), rgba(0, 0, 0, 0.1)); }
.btn.plain:active:not(:disabled) { background-image: none; background-color: var(--fill-2); }
.btn:disabled { background-color: var(--fill); background-image: none; color: var(--text-3); cursor: not-allowed; transform: none; }
.btn.plain:disabled { background-color: transparent; }
/* A busy button is also disabled, but keeps its own look, not the disabled grey */
.btn[aria-busy="true"]:disabled { cursor: progress; background-color: var(--fill); color: var(--text); }
.btn.primary[aria-busy="true"]:disabled { background-color: var(--accent-fill); color: var(--on-fill); }
.btn.ok[aria-busy="true"]:disabled { background-color: var(--green-fill); color: var(--on-fill); }
.btn.destructive[aria-busy="true"]:disabled { background-color: var(--red-fill); color: var(--on-fill); }
.btn.plain[aria-busy="true"]:disabled { background-color: transparent; color: var(--link); }
.btn.danger[aria-busy="true"]:disabled { color: var(--red); }
/* After 150 ms of waiting the label hides behind the activity indicator (spinner declarations below); the width was
   locked by busy() */
.btn.spinning { color: transparent !important; }
.btn.spinning::after { content: ""; position: absolute; inset: 0; margin: auto; }

/* Badges: static, never clickable. Every tint is painted over --surface, so a badge measures the same on --bg (a batch
   status in .large-sub) as in a card: on --bg directly, the light badges fall to 4.15–4.26:1. */
.badge { display: inline-flex; align-items: center; min-height: 1.375rem; padding: 0 var(--sp-2); border-radius: var(--r-chip);
  font-size: 0.75rem; font-weight: 600; letter-spacing: 0.01em; line-height: 1; white-space: nowrap;
  font-variant-numeric: tabular-nums; background: linear-gradient(var(--fill), var(--fill)), var(--surface); color: var(--gray-text); }
.badge.gray { background: linear-gradient(var(--fill), var(--fill)), var(--surface); color: var(--gray-text); }
.badge.blue { background: linear-gradient(var(--blue-soft), var(--blue-soft)), var(--surface); color: var(--blue); }
.badge.green { background: linear-gradient(var(--green-soft), var(--green-soft)), var(--surface); color: var(--green); }
.badge.orange { background: linear-gradient(var(--orange-soft), var(--orange-soft)), var(--surface); color: var(--orange); }
.badge.red { background: linear-gradient(var(--red-soft), var(--red-soft)), var(--surface); color: var(--red); }
/* a tone badge() accepts (build-spec §1.2): no page uses it today */
.badge.purple { background: linear-gradient(var(--purple-soft), var(--purple-soft)), var(--surface); color: var(--purple); }
.badge.outline { background: transparent; box-shadow: inset 0 0 0 1.5px var(--blue); color: var(--blue); }

/* Segmented control, a capsule with labels at Apple's 13 pt. Two kinds, same look: navigation (nav.seg >
   a.seg-item[aria-current]) and choice (fieldset.seg.seg-choice > label.seg-item > input[type=radio] + span). */
.seg { display: inline-flex; gap: 2px; padding: 2px; margin: 0; border: 0; border-radius: var(--r-full); background: var(--fill);
  min-width: 0; max-width: 100%; }
.seg-item { position: relative; flex: 0 1 auto; display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  min-height: calc(var(--control-sm) - 4px); padding: 0 var(--sp-3); border-radius: var(--r-full); color: var(--text);
  font-size: 0.8125rem; font-weight: 600; line-height: 1.2; text-decoration: none; white-space: nowrap; cursor: pointer;
  touch-action: manipulation; transition: background-color 160ms var(--ease-out); }
@media (pointer: coarse) { .seg-item { font-size: 0.9375rem; } }
/* Selected: the new thumb appears at once (no sliding thumb); only the press tint eases out */
.seg-item[aria-current="page"], .seg-item:has(> input:checked) { background: var(--seg-thumb); box-shadow: var(--seg-shadow);
  transition-duration: 0ms; }
@media (hover: hover) {
  .seg:not(:disabled) > .seg-item:not([aria-current]):not(:has(> input:checked)):hover { background-color: var(--fill); }
}
/* Pressed (the selected one keeps its thumb; a disabled choice doesn't react): tint at once, eases back on release */
.seg:not(:disabled) > .seg-item:not([aria-current]):not(:has(> input:checked)):active { background-color: var(--fill-2);
  transition-duration: 0ms; }
.seg-choice .seg-item > input { position: absolute; inset: 0; margin: 0; opacity: 0; cursor: pointer; }
.seg-item:has(> input:focus-visible) { outline: 2px solid var(--focus); outline-offset: 1px; }
/* Tone dot, reserved on every toned item so the control never changes width when the choice changes (Apple segmented
   controls don't resize) */
.seg-item[data-tone] > span::before { content: ""; display: inline-block; width: 8px; height: 8px; margin-right: 6px;
  border-radius: 50%; vertical-align: 1px; background: transparent; }
.seg-item[data-tone="green"] > input:checked + span::before { background: var(--green-graphic); }
.seg-item[data-tone="orange"] > input:checked + span::before { background: var(--orange-graphic); }
.seg-choice:disabled .seg-item, .seg-choice:disabled .seg-item > input { cursor: default; }
.seg-choice:disabled .seg-item:not(:has(> input:checked)) { color: var(--text-3); }
/* Counts are set apart from the 600 label by weight: --text-2 fails on the track over --bg (3.97:1) and on the dark
   thumb (2.09:1) */
.seg-n { color: var(--text); font-weight: 400; font-variant-numeric: tabular-nums; }
.seg-n:empty { display: none; }

/* Filters (owner Strings page): language segments, then chips with counts, then search */
.filters { display: grid; gap: var(--sp-3); margin: 0 0 var(--sp-3); }
.filters .seg { justify-self: start; }
.filter-bar { display: flex; flex-wrap: wrap; gap: var(--sp-2); align-items: center; }
.filter { display: inline-flex; align-items: center; gap: 6px; min-height: var(--control-sm); padding: 0 var(--sp-3);
  border-radius: var(--r-full); background: var(--surface); color: var(--text); font-size: 0.9375rem; font-weight: 600;
  text-decoration: none; white-space: nowrap; touch-action: manipulation; transition: background-color 160ms var(--ease-out); }
@media (hover: hover) { .filter:not([aria-current="page"]):hover { background-image: linear-gradient(var(--fill), var(--fill)); } }
.filter:active { background-color: var(--fill-2); transition-duration: 0ms; }
.filter-n { color: var(--text-2); font-weight: 500; font-variant-numeric: tabular-nums; }
.filter.needs .filter-n { color: var(--orange); font-weight: 700; }
.filter[aria-current="page"] { background: var(--accent-fill); color: var(--on-fill); transition-duration: 0ms; }
.filter[aria-current="page"] .filter-n { color: var(--on-fill); }
.filter-sep { flex: none; align-self: center; width: 1px; height: 1.25rem; background: var(--separator); margin: 0 var(--sp-1); }
/* Phone: one horizontally scrolling row with faded edges; the 4 px above and below keep the chips' focus rings inside
   the scroller */
@media (max-width: 559px) {
  .filters .seg { justify-self: stretch; }
  .filters .seg .seg-item { flex: 1 1 0; }
  .filter-bar { flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none; margin-inline: calc(-1 * var(--sp-4));
    padding: var(--sp-1) var(--sp-4);
    -webkit-mask-image: linear-gradient(90deg, transparent, #000 12px, #000 calc(100% - 24px), transparent);
    mask-image: linear-gradient(90deg, transparent, #000 12px, #000 calc(100% - 24px), transparent); }
  .filter-bar::-webkit-scrollbar { display: none; }
}
.search { display: flex; gap: var(--sp-2); }
.search input { flex: 1; min-width: 0; }

/* Fields: 16 px text at least, so iOS doesn't zoom on focus */
input[type="text"], input[type="search"], select, textarea { min-height: var(--control); padding: 0 var(--sp-3);
  border: 1px solid var(--field-border); border-radius: var(--r-md); background: var(--surface); color: var(--text);
  font-size: max(1rem, 16px); }
input[type="search"] { -webkit-appearance: none; appearance: none; }
textarea { width: 100%; min-height: 4.5rem; padding: var(--sp-2) var(--sp-3); line-height: 1.45; resize: vertical;
  field-sizing: content; }
:is(input, select, textarea):focus-visible { outline-offset: 0; border-color: var(--focus); }
::placeholder { color: var(--text-2); opacity: 1; }
input[type="checkbox"] { width: 1.125rem; height: 1.125rem; margin: 0; accent-color: var(--accent-fill); }
.toggle { display: inline-flex; align-items: center; gap: var(--sp-2); min-height: var(--control-sm); font-size: 0.9375rem;
  cursor: pointer; }
.toggle-count { color: var(--text-2); font-variant-numeric: tabular-nums; }
/* the selects keep 7.5rem, room for "180 days" or "Spanish" and a one-line label: a percentage width zeroes their
   min-content, so plain 1fr columns let them shrink to a cut-off value between 560 and 680 px */
.form-card { display: grid; grid-template-columns: minmax(0, 2fr) minmax(7.5rem, 1fr) minmax(7.5rem, 1fr) auto; gap: var(--sp-3); align-items: end; }
.form-field { display: grid; gap: var(--sp-1); min-width: 0; }
.form-label { font-size: 0.8125rem; font-weight: 600; color: var(--text-2); }
.form-field :is(input, select) { width: 100%; }
.form-error { grid-column: 1 / -1; }
@media (max-width: 559px) {
  .form-card { grid-template-columns: 1fr 1fr; }
  .form-card > .form-field:first-child, .form-card > .btn { grid-column: 1 / -1; }
}
/* The one-time link: a readonly field that selects itself on focus. Its text stays at the fields' 16 px: under that,
   iOS zooms in when it takes the focus. */
.linkfield { width: 100%; }
.link-head { margin: 0 0 var(--sp-1); }

/* Editor (common.js Editor): the reviewed text's own size; its focus ring is the border plus a soft 3 px halo */
.ed { min-height: 3rem; padding: 10px 12px; border: 1px solid var(--field-border); border-radius: var(--r-md);
  background: var(--surface); font-size: 1.0625rem; line-height: 1.47; white-space: pre-wrap; word-break: break-word; outline: none; }
.ed:focus { border-color: var(--focus); box-shadow: 0 0 0 3px color-mix(in srgb, var(--focus) 30%, transparent); }
.ed .nl { color: var(--gray-text); font-weight: 700; }

/* Text blocks: the reviewed texts, 17 px at full contrast. English sits bare; translations on --fill. */
.text { white-space: pre-wrap; word-break: break-word; font-size: 1.0625rem; line-height: 1.47; background: var(--fill);
  border-radius: var(--r-md); padding: var(--sp-2) var(--sp-3); }
.text.en { background: transparent; padding: 0; }
.ws { color: var(--gray-text); font-weight: 700; }
.chip { display: inline-block; background: var(--purple-soft); color: var(--purple); border-radius: var(--r-chip); padding: 0 5px;
  margin: 0 1px; font: 600 0.875rem/1.5 var(--mono); -webkit-user-select: all; user-select: all; }
/* Diff (the Change line the owner judges): its tints stack on the .text fill, where the hue as text falls to
   3.9–4.1:1. The words are --text (about 12:1), the hue stays in the tint and the line, and the line style keeps
   colour from being the only signal. */
.diff del { color: var(--text); background: var(--red-soft); text-decoration: line-through 1.5px var(--red); }
.diff ins { color: var(--text); background: var(--green-soft); text-decoration: underline 1.5px var(--green);
  text-underline-offset: 2px; }
/* A text block inside a message sits on --surface, not --fill, so its placeholder chips keep 4.5:1 (on fill over a
   tint they fall to 3.6–4.2:1) */
.msg .text { background: var(--surface); }

/* Hints and the fit meter, right under a field or the editor */
.hint { font-size: 0.8125rem; line-height: 1.38; margin: var(--sp-2) 0 0; color: var(--text-2); }
.hint.ok { color: var(--green); }
.hint.warn { color: var(--orange); }
.hint.bad { color: var(--red); }
.meter { height: 6px; background: var(--fill); border-radius: var(--r-full); overflow: hidden; margin-top: var(--sp-2); }
.meter > div { height: 100%; background: var(--green-graphic); }
.meter.ok > div { background: var(--green-graphic); }
.meter.warn > div { background: var(--orange-graphic); }
.meter.bad > div { background: var(--red); }

/* Grouped lists: inset groups on --surface, rows 44 px, separators inset to the row's text */
.group { margin: var(--sp-8) 0 0; }
.large-title-block + .group { margin-top: var(--sp-2); }
.group-head { display: flex; align-items: baseline; gap: var(--sp-2); flex-wrap: wrap; margin: 0 0 var(--sp-2);
  font: 600 1.25rem/1.3 var(--font); letter-spacing: -0.012em; }
.group-meta { font-size: 0.9375rem; font-weight: 400; letter-spacing: 0; color: var(--text-2); font-variant-numeric: tabular-nums; }
.list { list-style: none; margin: 0; padding: 0; background: var(--surface); border-radius: var(--r-lg); overflow: hidden; }
/* isolation: the row's own tints (::after) paint behind its text, above its background */
.row { position: relative; isolation: isolate; display: flex; align-items: center; gap: var(--sp-3); min-height: var(--row);
  padding: 11px var(--sp-4); color: var(--text); text-decoration: none; transition: background-color 160ms var(--ease-out); }
.list > li + li > .row::before { content: ""; position: absolute; top: 0; right: 0; left: var(--sep-inset, var(--sp-4));
  height: 1px; background: var(--separator); }
@media (min-resolution: 2dppx) { .list > li + li > .row::before { transform: scaleY(0.5); transform-origin: top; } }
.screen-row { --sep-inset: calc(var(--sp-4) + 24px + var(--sp-3)); }
/* A row's keyboard ring is drawn inside it: the list clips its rows to its corners (overflow: hidden), so a ring
   outside would be cut off, entirely on a one-row list. -3px keeps the 3 px ring of more contrast inside too; the end
   rows take the list's corners, so the ring's corners aren't cut either (a language card's list is square on top). */
.list > li > a.row:focus-visible { outline-offset: -3px; }
.list > li:first-child > a.row { border-top-left-radius: var(--r-lg); border-top-right-radius: var(--r-lg); }
.list > li:last-child > a.row { border-bottom-left-radius: var(--r-lg); border-bottom-right-radius: var(--r-lg); }
.lang-card > .list > li:first-child > a.row { border-top-left-radius: 0; border-top-right-radius: 0; }
@media (hover: hover) { a.row:hover { background: var(--fill); } }
/* tint at once, eases out on release, like the buttons (no scale) */
a.row:active { background: var(--fill-2); transition-duration: 0ms; }
.row-lead { flex: none; display: flex; }
.row-main { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.row-title { font-size: 1.0625rem; line-height: 1.3; overflow-wrap: break-word; }
.row-sub { font-size: 0.8125rem; line-height: 1.38; letter-spacing: 0.006em; color: var(--text-2); overflow-wrap: anywhere; }
.row-tag { font-size: 0.8125rem; color: var(--text-2); margin-left: var(--sp-1); }
.row-trail { flex: none; display: flex; gap: var(--sp-1); align-items: center; }
.row-count { color: var(--text-2); font-variant-numeric: tabular-nums; }
.row-count.needs { color: var(--orange); font-weight: 700; }
.row-verb { flex: none; color: var(--link); font-size: 0.9375rem; }
.row-actions { flex: none; display: flex; gap: var(--sp-1); }
@media (max-width: 559px) {
  .row:has(.row-actions) { flex-wrap: wrap; }
  .row-actions { flex-basis: 100%; margin-left: calc(-1 * var(--sp-2)); }
}
/* Chevron: --text-3 is for a decorative chevron next to text; never the only glyph of a control, which needs 3:1 */
.chev { flex: none; width: 0.5rem; height: 0.5rem; border: solid var(--text-3); border-width: 2px 2px 0 0; transform: rotate(45deg);
  margin: 0 4px 0 2px; }
/* Screen row ring: the share decided; a verified screen is a filled disc with a check */
.ring { width: 24px; height: 24px; border-radius: 50%;
  background: conic-gradient(var(--green-graphic) calc(var(--p, 0) * 1turn), var(--fill) 0);
  -webkit-mask: radial-gradient(farthest-side, transparent calc(100% - 4px), #000 calc(100% - 4px));
  mask: radial-gradient(farthest-side, transparent calc(100% - 4px), #000 calc(100% - 4px)); }
.screen-row[data-state="verified"] .ring { -webkit-mask: none; mask: none; background: var(--green-graphic); display: grid;
  place-items: center; }
.screen-row[data-state="verified"] .ring::after { content: ""; width: 9px; height: 5px; border: solid #fff;
  border-width: 0 0 2px 2px; transform: translateY(-1px) rotate(-45deg); }
.screen-row[data-state="verified"] .row-title { color: var(--text-2); }
/* The row of the screen just left (reviewer home): the tint shows at once and fades when review.js removes the class */
.row::after { content: ""; position: absolute; inset: 0; z-index: -1; background: var(--blue-soft); opacity: 0;
  transition: opacity 1.2s ease-out; pointer-events: none; }
.row.just-left::after { opacity: 1; transition: none; }
/* An error under a reviewer row stays inside the group's inset */
.row-error { padding: 0 var(--sp-4) var(--sp-3); }
.row-error > .msg { margin-top: 0; }

/* Cards: flat on --bg, for things with inner structure */
.card { background: var(--surface); border-radius: var(--r-lg); padding: var(--sp-4); margin: var(--sp-3) 0; }

/* Disclosure: a 44 px summary row whose chevron points right closed and down open */
details.disclosure > summary { list-style: none; display: flex; align-items: center; gap: var(--sp-2); min-height: var(--row);
  cursor: pointer; transition: opacity 160ms var(--ease-out); }
details.disclosure > summary::-webkit-details-marker { display: none; }
.disclosure-title { flex: none; font-weight: 600; } /* the title stays on one line; a long meta wraps instead */
.disclosure-meta { flex: 0 1 auto; min-width: 0; color: var(--text-2); font-size: 0.9375rem; }
details.disclosure > summary .chev { margin-left: auto; transition: transform 200ms var(--ease-out); }
details.disclosure[open] > summary .chev { transform: rotate(135deg); }
details.disclosure.card > summary { margin: calc(-1 * var(--sp-3)) 0; }
details.disclosure > summary:active { opacity: 0.55; transition-duration: 0ms; }
details.disclosure[open] > summary + * { margin-top: var(--sp-2); }

/* Progress card: decided count, bar, screens verified, one filled Start/Continue and the screen it leads to */
.progress-card { padding: var(--sp-5) var(--sp-4); }
.pc-head { margin: 0; font-size: 1rem; color: var(--text-2); font-variant-numeric: tabular-nums; }
.pc-count { font: 700 1.375rem/1.27 var(--font); letter-spacing: -0.016em; color: var(--text); font-variant-numeric: tabular-nums; }
.progress { height: 8px; margin: var(--sp-3) 0 var(--sp-2); background: var(--fill); border-radius: var(--r-full); overflow: hidden; }
.progress-fill { height: 100%; background: var(--green-graphic); border-radius: inherit; }
.pc-sub { margin: 0; font-size: 0.9375rem; color: var(--text-2); font-variant-numeric: tabular-nums; }
.pc-go { margin-top: var(--sp-4); }
.pc-next { margin: var(--sp-2) 0 0; text-align: center; }

/* Messages by feedback kind. The tint is always painted over --surface, so a message measures the same on --bg (the
   preview banner, the batch banners) as in a card. Inside a tint the words are --text: secondary and link colours fall
   to 3.9–4.5:1 there; links keep their underline. Buttons inside are filled, never .plain. */
.msg { --tint: var(--fill); display: flex; gap: var(--sp-2); align-items: flex-start; flex-wrap: wrap; margin: var(--sp-2) 0 0;
  padding: 10px 12px; border-radius: var(--r-md); background: linear-gradient(var(--tint), var(--tint)), var(--surface);
  color: var(--text); font-size: 0.9375rem; line-height: 1.4; }
.msg::before { flex: none; width: 1.1em; text-align: center; font-weight: 700; }
.msg-text { flex: 1; min-width: 12rem; margin: 0; }
.msg-text p { margin: 0 0 var(--sp-1); }
.msg-text p:last-child { margin-bottom: 0; }
.msg :is(.muted, .context, .vote-comment, a:not(.btn)) { color: var(--text); }
.msg > .btn { flex: none; }
/* The icons are decoration (the words say it): each content is declared twice, the second with empty alternative text
   (/ "") so a screen reader doesn't read "multiplication x" before an error; a browser without that syntax drops the
   second declaration and keeps the first. */
.msg.status::before { content: "\24D8"; content: "\24D8" / ""; color: var(--gray-text); }
.msg.info { --tint: var(--blue-soft); }
.msg.info::before { content: "\24D8"; content: "\24D8" / ""; color: var(--blue); }
/* Apple's exclamation-mark triangle; the text-presentation selector FE0E keeps iOS from drawing the emoji */
.msg.warn { --tint: var(--orange-soft); }
.msg.warn::before { content: "\26A0\FE0E"; content: "\26A0\FE0E" / ""; color: var(--orange); }
.msg.ok { --tint: var(--green-soft); }
.msg.ok::before { content: "\2713"; content: "\2713" / ""; color: var(--green); }
.msg.bad { --tint: var(--red-soft); }
.msg.bad::before { content: "\2715"; content: "\2715" / ""; color: var(--red); }
.card-error:empty { display: none; }
.done-msg { color: var(--green); font-weight: 600; font-size: 0.9375rem; }

/* Empty, loading and error states; the static pages */
.state { margin: 0; text-align: center; padding: var(--sp-8) var(--sp-4); color: var(--text-2); }
.state-title { margin: 0 0 var(--sp-2); font: 700 1.375rem/1.27 var(--font); letter-spacing: -0.016em; color: var(--text); }
.state-text { margin: 0 auto; max-width: 32rem; font-size: 0.9375rem; line-height: 1.4; }
.state-actions { display: flex; flex-wrap: wrap; gap: var(--sp-2); justify-content: center; margin-top: var(--sp-4); }
.state-page { min-height: 100vh; min-height: 100dvh; display: grid; place-content: center; max-width: 30rem; margin: 0 auto; }

/* Spinner: Apple's activity indicator, 8 spokes whose brightness trails off, turning in 8 steps (not a ring with a
   gap). Shared by .spinner, a busy button's ::after and the render panel's icon while it renders; each sets --spin.
   The standalone .spinner is the design system's inline indicator: no page uses one today. */
.spinner, .btn.spinning::after,
.render-panel:is([data-state="waiting"], [data-state="running"], [data-state="cancelling"]) .rp-icon {
  width: 16px; height: 16px; flex: none; border: 0; border-radius: 50%;
  background: repeating-conic-gradient(from -11.25deg, var(--spin, currentColor) 0 22.5deg, transparent 22.5deg 45deg);
  -webkit-mask: radial-gradient(circle, transparent 3.5px, #000 4px), conic-gradient(rgba(0, 0, 0, 0.25), #000);
  -webkit-mask-composite: source-in;
  mask: radial-gradient(circle, transparent 3.5px, #000 4px), conic-gradient(rgba(0, 0, 0, 0.25), #000);
  mask-composite: intersect;
  animation: spin 0.8s steps(8) infinite; }
.spinner { display: inline-block; vertical-align: -3px; }
@keyframes spin { to { transform: rotate(360deg); } }

/* 5 Reviewer page (review.js) */
.preview-banner { padding-top: var(--sp-3); }
.preview-banner > .msg { margin-top: 0; }
.how { padding-block: var(--sp-3); }
.how-list { margin: var(--sp-2) 0 var(--sp-1); padding-left: var(--sp-5); font-size: 0.9375rem; }
.how-list li { margin: var(--sp-1) 0; }

/* Screen page, phone flow: title, picture, toggle, texts */
.layout > * + * { margin-top: var(--sp-3); }
.bar { display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap; }
.texts, #cards { min-width: 0; }
.section-head { margin: var(--sp-6) 0 var(--sp-2); font: 600 1.25rem/1.3 var(--font); letter-spacing: -0.012em; }

/* Split view on a wide window: the picture stays in view on the left while the texts scroll on the right. It is scaled
   to the window's height, between the nav bar and the footer, with room for the note under it. */
@media (min-width: 820px) {
  .split-view .wrap { --page-w: 1140px; }
  .split-view .layout { display: grid; grid-template-columns: 340px minmax(0, 1fr); grid-template-rows: auto auto 1fr;
    grid-template-areas: "shot head" "shot bar" "shot texts"; column-gap: 28px; align-items: start; }
  .split-view .layout > * + * { margin-top: 0; }
  .split-view .layout > .page-head { grid-area: head; }
  .split-view .layout > .bar { grid-area: bar; }
  .split-view .layout > .texts { grid-area: texts; }
  .split-view #shot { grid-area: shot; position: sticky; top: calc(var(--head) + 12px); }
  .split-view details.shot-details > summary { display: none; }
  .split-view .shot-wrap:not(.long) .shot { width: min(100%, calc((100vh - var(--head) - var(--foot) - 96px) * var(--ar, 0.46))); }
  .split-view .shot-wrap.long { max-height: calc(100vh - var(--head) - var(--foot) - 96px); overscroll-behavior: contain; }
  @supports (height: 100dvh) {
    .split-view .shot-wrap:not(.long) .shot { width: min(100%, calc((100dvh - var(--head) - var(--foot) - 96px) * var(--ar, 0.46))); }
    .split-view .shot-wrap.long { max-height: calc(100dvh - var(--head) - var(--foot) - 96px); }
  }
}
/* Phones use svh and a constant 5rem footer allowance, never dvh or --foot: dvh changes while the browser's toolbar
   collapses during a scroll, and --foot drops to 0 when the editor hides the footer; either would resize the picture
   and move every card below it. A long frame hands the scroll back to the page at its ends. */
@media (max-width: 819px) {
  #shot .shot-wrap:not(.long) .shot { width: min(100%, 330px, calc((100svh - var(--head) - 5rem) * var(--ar, 0.46))); }
  #shot .shot-wrap.long { max-height: 60svh; overscroll-behavior-y: auto; }
}
details.shot-details > summary { cursor: pointer; color: var(--link); font-weight: 600; min-height: var(--row); display: flex;
  align-items: center; gap: var(--sp-2); list-style: none; transition: opacity 160ms var(--ease-out); }
details.shot-details > summary::-webkit-details-marker { display: none; }
/* pressed like every disclosure summary: dims at once, eases back on release */
details.shot-details > summary:active { opacity: 0.55; transition-duration: 0ms; }
/* the summary folds the picture away on a phone: its chevron says so (right closed, down open) */
details.shot-details > summary::after { content: ""; width: 0.5rem; height: 0.5rem; border: solid currentColor;
  border-width: 2px 2px 0 0; transform: rotate(45deg); transition: transform 200ms var(--ease-out); }
details.shot-details[open] > summary::after { transform: rotate(135deg); }
.shot-note { text-align: center; font-size: 0.8125rem; color: var(--text-2); margin: var(--sp-2) 0 0; }

/* Text card. The active card (paired with its box) gets an inset amber ring that passes 3:1 on --surface. */
.str { scroll-margin-top: calc(var(--head) + var(--sp-3)); scroll-margin-bottom: calc(var(--foot) + var(--sp-3)); }
.str.active { box-shadow: inset 0 0 0 2px var(--pair-edge); }
.str-head { display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap; margin-bottom: var(--sp-1); }
.str-kind { font-size: 0.8125rem; font-weight: 600; color: var(--text-2); }
.str-head + .label { margin-top: var(--sp-1); }
.str-about { margin-top: var(--sp-1); }
.str-about p { margin: var(--sp-1) 0 0; font-size: 0.9375rem; color: var(--text-2); }
.str-about b { color: var(--text); font-weight: 600; }
/* The card's crop of its box (phones), also the live-preview host while editing (every width). A tap scrolls to the
   picture; it doesn't zoom. */
.card-crop:not(:empty) { margin-top: var(--sp-3); }
.card-crop .crop { margin-bottom: var(--sp-1); }
.crop-note { font-size: 0.8125rem; color: var(--text-2); margin: 0; }
@media (min-width: 820px) { .split-view .str:not(.editing) .card-crop { display: none; } }
.card-crop { cursor: pointer; transition: opacity 160ms var(--ease-out); }
.card-crop:active { opacity: 0.8; transition-duration: 0ms; }
.str.editing .card-crop { cursor: default; }
.str.editing .card-crop:active { opacity: 1; }
.str:is(.editing, .noting) > .actions { display: none; }
.edit-slot:not(:empty) { margin-top: var(--sp-3); }
.edit-slot .actions { margin-top: var(--sp-3); }
.ed-warn:empty { display: none; }
.note-about { display: flex; width: max-content; margin-bottom: var(--sp-1); }
/* Competing votes in a disputed message: all words --text, they sit in a tint */
.votes { list-style: none; margin: var(--sp-2) 0 0; padding: 0; }
.vote { margin: var(--sp-2) 0 0; }
.vote-who { font-weight: 600; }
.vote .text { margin: var(--sp-1) 0; }
.vote-comment { display: block; margin-top: var(--sp-1); }
.vote-acts { display: flex; gap: var(--sp-1); flex-wrap: wrap; margin-top: var(--sp-1); }
.also { margin: var(--sp-3) 0 0; font-size: 0.9375rem; color: var(--text-2); }
details.also > summary { min-height: var(--control-sm); }
.also-list { list-style: none; margin: var(--sp-1) 0 0; padding: 0; columns: 2 11rem; column-gap: var(--sp-4); }
.also-list li { break-inside: avoid; }
.also-list a { display: block; padding: 6px 0; text-decoration: none; }
@media (pointer: coarse) { .also-list a { min-height: 2.75rem; display: flex; align-items: center; } }
.guest-msg { margin: 0 0 var(--sp-3); }

/* 6 Owner pages (admin.js) */

/* Overview: language cards */
.lang-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 20rem), 1fr)); gap: var(--sp-3); }
.lang-grid > .card { margin: 0; }
.lang-card { padding-bottom: 0; overflow: hidden; }
.lang-head { display: flex; align-items: baseline; gap: var(--sp-2); }
.lang-name { flex: 1; margin: 0; font: 600 1.0625rem/1.3 var(--font); }
.lang-pct { font: 700 1.75rem/1.21 var(--font); letter-spacing: -0.02em; font-variant-numeric: tabular-nums; }
.lang-pct-label { font-size: 0.8125rem; color: var(--text-2); }
/* stacked bar: graphics take the -graphic tokens (--orange and --red text tokens are 1.02:1 apart in light) */
.stack { display: flex; height: 8px; margin: var(--sp-2) 0; border-radius: var(--r-full); overflow: hidden; background: var(--fill); }
.stack-seg { flex: none; }
.stack-seg.green { background: var(--green-graphic); }
.stack-seg.orange { background: var(--orange-graphic); }
.stack-seg.red { background: var(--red); }
.lang-sub { margin: 0; font-size: 0.8125rem; color: var(--text-2); font-variant-numeric: tabular-nums; }
/* a button's height even when it holds only a line of text, so the lists of cards side by side start level */
.lang-batch { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-2); margin-top: var(--sp-3);
  min-height: var(--control); }
/* the card's rows, full bleed, separators inset */
.lang-card > .list { margin: var(--sp-3) calc(-1 * var(--sp-4)) 0; border-radius: 0; box-shadow: inset 0 1px 0 var(--separator); }
.lang-card > .card-error { padding-bottom: var(--sp-4); }
.person.off .row-title { color: var(--text-2); }

/* Strings: one card per string */
.srow { scroll-margin-top: calc(var(--head) + var(--sp-3)); }
.srow-head { display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap; }
/* file:line sites and screen ids are long unbroken words: they wrap rather than widen a 375 px card */
.srow-meta { color: var(--text-2); min-width: 0; overflow-wrap: anywhere; }
.key { display: inline-block; max-width: 18rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  vertical-align: bottom; font-size: 0.8125rem; }
.options { list-style: none; margin: var(--sp-1) 0 0; padding: 0; }
.option { display: flex; align-items: flex-start; gap: var(--sp-3); padding: var(--sp-2) 0; border-top: 1px solid var(--separator); }
.option:first-child { border-top: 0; }
.option-main { flex: 1; min-width: 0; }
.option-who { display: block; font-size: 0.8125rem; font-weight: 600; color: var(--text-2); margin-bottom: var(--sp-1); }
/* a reviewer's reason under the option's text */
.option-main > p { margin: var(--sp-1) 0 0; }
.srow-edit:not(:empty) { margin-top: var(--sp-3); }
.history-list { list-style: none; margin: 0; padding: 0; font-size: 0.8125rem; color: var(--text-2); }
.history-list li { margin: var(--sp-1) 0; }
.history-list b { color: var(--text); }
.rebuild { margin-top: var(--sp-8); }

/* Batch page head */
.batch-head .large-sub { display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap; }
/* the plain button's own padding would push its word off the title's edge */
.batch-head-actions { margin-top: var(--sp-2); margin-left: calc(-1 * var(--sp-2)); }
.legend { font-size: 0.8125rem; color: var(--text-2); margin: 0 0 var(--sp-3); }
.cmd { font-size: 0.8125rem; background: var(--fill); padding: 2px 6px; border-radius: var(--r-xs); -webkit-user-select: all;
  user-select: all; overflow-wrap: anywhere; }
.view-seg { display: flex; width: max-content; margin: var(--sp-4) 0 var(--sp-2); }

/* Render panel: the message kinds of section 4, no second visual language (no coloured edge stripe, no ring icon).
   Same tint over --surface, words --text, an icon in the hue. */
.render-panel { --tint: var(--fill); margin: var(--sp-3) 0; padding: 10px 12px; border-radius: var(--r-md);
  background: linear-gradient(var(--tint), var(--tint)), var(--surface); color: var(--text); font-size: 0.9375rem; line-height: 1.4;
  overflow-wrap: anywhere; }
.render-panel:is([data-state="waiting"], [data-state="running"], [data-state="cancelling"]) { --tint: var(--blue-soft); }
.render-panel[data-state="done"] { --tint: var(--green-soft); }
.render-panel[data-state="failed"] { --tint: var(--red-soft); }
.render-panel:is([data-state="offline"], [data-state="undeployed"]) { --tint: var(--orange-soft); }
.rp-head { display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap; min-height: var(--control-sm); }
.rp-title { font-weight: 600; }
.rp-time { font-size: 0.8125rem; font-variant-numeric: tabular-nums; }
.rp-icon { flex: none; width: 1.1em; text-align: center; font-weight: 700; }
.rp-icon::before { content: "\24D8"; color: var(--gray-text); }
.render-panel[data-state="done"] .rp-icon::before { content: "\2713"; color: var(--green); }
.render-panel[data-state="failed"] .rp-icon::before { content: "\2715"; color: var(--red); }
.render-panel:is([data-state="offline"], [data-state="undeployed"]) .rp-icon::before { content: "\26A0\FE0E"; color: var(--orange); }
/* while it renders, the icon is the activity indicator (declarations with the spinner, section 4) */
.render-panel:is([data-state="waiting"], [data-state="running"], [data-state="cancelling"]) .rp-icon { --spin: var(--blue); }
.render-panel:is([data-state="waiting"], [data-state="running"], [data-state="cancelling"]) .rp-icon::before { content: none; }
.rp-detail, .rp-helper { margin: var(--sp-1) 0 0; font-size: 0.8125rem; }
.rp-helper:empty { display: none; }
.rp-log pre { margin: var(--sp-2) 0 0; max-height: 12em; overflow: auto; white-space: pre-wrap; font: 0.75rem/1.4 var(--mono); }

/* Batch items (no decision edge stripe: the checked segment, its tone dot and the item badge show the decision) */
.item { scroll-margin-top: calc(var(--head) + var(--sp-3)); }
.item.active { box-shadow: inset 0 0 0 2px var(--pair-edge); }
.item.nested { padding: var(--sp-3) 0 0 var(--sp-3); margin-top: var(--sp-3); border-top: 1px solid var(--separator); border-radius: 0; }
.item-head { display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap; }
.item-head > .small { min-width: 0; overflow-wrap: anywhere; }
.item-decide { display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap; margin-top: var(--sp-3); }
.item-error { flex-basis: 100%; }
/* The crops' column is capped at two 320 px crops and their 16 px gap (41rem, plus rounding room), and the text keeps
   18rem: an auto column took the crops' whole max-content (a third figure when Page preview is there) and left the
   English and the diff a few characters a line. A narrower column wraps the crops one under the other instead. Its
   minimum is 0, not fit-content: the figure inside the closed Page preview still counts for the min-content, which
   pushed the column to 800 px and the text column down to 126 px at 1024. */
@media (min-width: 900px) {
  .item-body { display: grid; grid-template-columns: minmax(18rem, 1fr) minmax(0, 41.5rem); gap: var(--sp-5); align-items: start; }
}
/* Accept | Keep current | Send back fit one line in 311 px only with widths by content (UIKit's
   apportionsSegmentWidthsByContent): equal thirds wrapped "Keep current" and "Send back". The wrap stays as a safety
   net, never a horizontal overflow. */
@media (max-width: 559px) {
  .decision { display: flex; width: 100%; }
  .decision .seg-item { flex: 1 1 auto; min-width: 0; white-space: normal; text-align: center; line-height: 1.15; padding-inline: 6px; }
}

/* Before and after crops: side by side when both fit the card, else stacked. A wrapping row rather than a fixed
   two-column grid: two 320 px crops need 656 px, more than a card has between 680 and 720 px. */
.compare { display: flex; flex-wrap: wrap; gap: var(--sp-3) var(--sp-4); align-items: flex-start; margin-top: var(--sp-3); }
.compare > .cmp { flex: 0 1 auto; }
.compare > .cmp-more { flex-basis: 100%; }
.cmp { margin: 0; min-width: 0; }
.cmp figcaption { font-size: 0.75rem; color: var(--text-2); margin-bottom: var(--sp-1); }
.cmp figcaption b { color: var(--text); text-transform: uppercase; letter-spacing: 0.05em; }
.cmp-note { font-size: 0.8125rem; color: var(--text-2); margin: var(--sp-1) 0 0; max-width: 20rem; }
.cmp-note.bad { color: var(--red); }

/* By screen: the whole screen before beside after, then its corrections */
.screen-pair { scroll-margin-top: calc(var(--head) + 12px); }
.pair-head { display: flex; align-items: baseline; gap: var(--sp-2); flex-wrap: wrap; }
.pair-title { flex: 1; margin: 0; font: 600 1.0625rem/1.3 var(--font); }
.pair-count { font-size: 0.8125rem; color: var(--text-2); font-variant-numeric: tabular-nums; }
.pair-switch { display: none; margin: var(--sp-3) auto 0; }
.pair-fig { margin: 0; }
.pair-fig figcaption { font-size: 0.75rem; font-weight: 600; letter-spacing: 0.05em; text-transform: uppercase;
  color: var(--text-2); margin-bottom: 6px; text-align: center; }
/* Phone: one figure at the card's width and a Before | After switch. Every selector carries .screen-pair: section 7's
   .pair { grid-template-columns: repeat(2, …) } would otherwise win at equal specificity. Visibility, not display:
   both long frames keep their scroll position. */
@media (max-width: 559px) {
  .screen-pair .pair-switch { display: flex; width: max-content; }
  .screen-pair .pair { grid-template-columns: minmax(0, 1fr); }
  .screen-pair .pair-fig { grid-area: 1 / 1; }
  .screen-pair .pair[data-show="after"] .pair-fig.before, .screen-pair .pair[data-show="before"] .pair-fig.after { visibility: hidden; }
}

/* 7 Picture and preview. Frozen geometry: these rules size and place the screenshot, its boxes, the correction drawn
   on it (common.js previewNode) and the crops (cropNode). Only colours and box styling may change. Every colour on the
   picture is the same in both themes: the app is navy in both, and theme text tokens (--orange #c93400) are 2.69:1
   on it. */
.shot-wrap { display: flex; justify-content: center; }
.shot-wrap.long { max-height: 72vh; overflow-y: auto; overscroll-behavior: contain; align-items: flex-start; }
.shot { position: relative; width: min(100%, 330px); border-radius: 22px; overflow: hidden; border: 1px solid var(--separator); }
.shot img { display: block; width: 100%; height: auto; }
/* outlines drawn outside the text (offset 1 px) so the letters stay readable; .trunc, .hover, .guest, .active and
   .pressed come after .done so they win */
.shot .box { position: absolute; border: 0; border-radius: 4px; outline: 1.5px solid var(--box); outline-offset: 1px;
  cursor: pointer; touch-action: manipulation; }
.shot .box.done { outline: 1px solid var(--box-done); opacity: 0.6; }
.shot .box.trunc { outline: 1.5px dashed var(--box-bad); opacity: 1; }
.shot .box.hover { outline: 1.5px dashed var(--pair); opacity: 1; }
.shot .box.guest { outline: 2px dotted var(--pair); cursor: default; opacity: 1; z-index: 2; }
.shot .box.active { outline: 2px solid var(--pair); box-shadow: 0 0 0 4px rgba(255, 159, 10, 0.35); opacity: 1; z-index: 2; }
/* a guest box is drawn active on the "Also on" landing: it keeps its dotted line (reviewed on another screen) */
.shot .box.guest.active { outline-style: dotted; }
.shot .box.pressed { outline: 2.5px solid var(--pair); background: rgba(255, 159, 10, 0.18); opacity: 1; z-index: 2; transition: none; }
.shot .box.changed { outline: 2px solid var(--pair); cursor: default; }
.pair { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px; align-items: start; margin-top: 10px; }
.pair .shot-wrap.long { overscroll-behavior: auto; }
@media (max-width: 559px) { .pair { gap: 8px; } }
/* sized in container units of the picture it sits on */
.shot, .crop-img { container-type: inline-size; }
.pv { position: absolute; z-index: 3; pointer-events: none; }
.pv-patch { position: absolute; }
.pv-text { position: absolute; left: 0; right: 0; }
.pv-line { white-space: pre; }
.pv.grows .pv-text { outline: 1.5px dashed #ff9f0a; outline-offset: 1px; }   /* constant: the picture is navy in both themes (6.92:1) */
.pv.cut .pv-text { outline: 1.5px dashed var(--box-bad); outline-offset: 1px; }   /* 4.17:1 on the navy */
.crop { position: relative; overflow: hidden; border-radius: 8px; border: 1px solid var(--separator); background: #000; }
.crop-img { position: absolute; }
.crop-img img { display: block; width: 100%; height: auto; max-width: none; }
.crop .box { position: absolute; border: 2px solid var(--pair); border-radius: 3px; }

/* 8 Input modality and motion preferences: three separate signals (reduced motion, reduced transparency, more
   contrast), then touch targets */
@media (prefers-reduced-motion: reduce) {
  .btn:active:not(:disabled) { transform: none; }
  /* the spokes stay drawn; the text beside them carries the progress */
  .spinner, .btn.spinning::after, .render-panel .rp-icon { animation: none !important; }
  .nav-progress::before { animation: none; width: 100%; opacity: 0.6; }
  details.disclosure > summary .chev, details.shot-details > summary::after { transition: none; }
}
@media (prefers-reduced-transparency: reduce) {
  .navbar.scrolled, .footer-bar { background: var(--chrome-solid); -webkit-backdrop-filter: none; backdrop-filter: none; }
  .toast:not(.bad) { background: var(--toast-solid); -webkit-backdrop-filter: none; backdrop-filter: none; }
}
@media (prefers-contrast: more) {
  /* .navbar.scrolled is listed: it outranks .navbar and would bring the translucent material back on scroll */
  .navbar, .navbar.scrolled, .footer-bar { background: var(--chrome-solid); -webkit-backdrop-filter: none; backdrop-filter: none; }
  /* solid with a border, also at the scroll edge */
  .navbar::after, .footer-bar::before { opacity: 1; transform: none; }
  .toast:not(.bad) { background: var(--toast-solid); -webkit-backdrop-filter: none; backdrop-filter: none; }
  .card, .list, .seg, .badge, .filter, .msg, .render-panel { box-shadow: inset 0 0 0 1px var(--separator); }
  .dialog-panel { box-shadow: var(--shadow-float), inset 0 0 0 1px var(--separator); }
  a { text-decoration-color: currentColor; }
  .also-list a { text-decoration: underline; }
  :focus-visible { outline-width: 3px; }
}
@media (pointer: coarse) {
  .row { min-height: 2.75rem; }
  .toggle { min-height: 2.75rem; }
  .filter { min-height: 2.75rem; }
}
