fix: resolve code-review findings from the clean-review restyle

Correctness:
- Make the local-artifact audit test skip on fresh clones (data/ is
  gitignored), so the suite passes outside this workstation
- Drop the transform from the viewRise entrance animation: an animated
  transform made .view.active a containing block for 320ms and threw
  the fixed decision panel off-screen on every workbench entry
- Collapse the queue toolbar at 1380px instead of 1180px; 1280x800
  laptops no longer get a horizontal scrollbar (verified live)
- Serve .woff2 as font/woff2 with an immutable cache header so the
  2MB bundled font is fetched once, not per page load (with test)
- Clip overflow on top-bar status chips (long apiError strings spilled
  over neighbors at 981-1180px)
- Give queue-row selection a selector that outranks the even-row
  zebra stripe (selection background was parity-dependent)

Cleanup:
- Replace the stale old-palette focus ring and ::selection literals
  with color-mix over var(--teal)
- Delete dead tokens: unused back-compat aliases (the comment claiming
  they were referenced was false), --rail-bot, --ochre-deep, and
  --font-stamp (identical to --font-ui since the Pretendard switch)
- Tokenize scattered raw colors: rail ink scale, soft tint levels,
  inset-well and bevel shadows, naver/internal source-chip triplets
- Remove the asset-preload div and three orphan SVGs nothing renders;
  tests now reject reintroducing them

Verified: 359 tests pass; Playwright audit at 1440/1280/390 shows zero
horizontal overflow on all views, Pretendard active, decision panel
fixed at the viewport corner mid-animation.
This commit is contained in:
유창욱 2026-06-11 11:13:46 +09:00
parent ed701bd436
commit 7cac0b3835
8 changed files with 102 additions and 114 deletions

View file

@ -12,6 +12,16 @@ from urllib.parse import unquote, urlparse
from rights_filter.server.image_store import LocalSubmissionImageStore, SUPPORTED_IMAGE_SUFFIXES from rights_filter.server.image_store import LocalSubmissionImageStore, SUPPORTED_IMAGE_SUFFIXES
from rights_filter.server.sqlite_store import CopyrighterStore from rights_filter.server.sqlite_store import CopyrighterStore
# Windows Python builds may not know .woff2, which would serve the bundled
# 2MB font as application/octet-stream.
mimetypes.add_type("font/woff2", ".woff2")
mimetypes.add_type("font/woff", ".woff")
# Bundled font binaries never change between releases; without any cache
# validator the browser re-downloads the full file on every page load.
_IMMUTABLE_SUFFIXES = {".woff2", ".woff"}
_IMMUTABLE_CACHE_CONTROL = "public, max-age=31536000, immutable"
def build_server( def build_server(
host: str, host: str,
@ -214,6 +224,8 @@ def build_server(
self.send_response(HTTPStatus.OK) self.send_response(HTTPStatus.OK)
self.send_header("Content-Type", content_type) self.send_header("Content-Type", content_type)
self.send_header("Content-Length", str(len(data))) self.send_header("Content-Length", str(len(data)))
if path.suffix.lower() in _IMMUTABLE_SUFFIXES:
self.send_header("Cache-Control", _IMMUTABLE_CACHE_CONTROL)
if untrusted: if untrusted:
# Neutralize stored XSS from operator-uploaded / externally # Neutralize stored XSS from operator-uploaded / externally
# collected media (an SVG can carry an inline <script>). `sandbox` # collected media (an SVG can carry an inline <script>). `sandbox`

View file

@ -1,6 +1,8 @@
import json import json
from pathlib import Path from pathlib import Path
import pytest
ROOT = Path(__file__).resolve().parents[2] ROOT = Path(__file__).resolve().parents[2]
APP_DIR = ROOT / "web" / "operator-gui" APP_DIR = ROOT / "web" / "operator-gui"
@ -270,6 +272,11 @@ def test_design_contract_has_accessibility_risk_states_and_responsive_layouts():
assert class_name in styles assert class_name in styles
@pytest.mark.skipif(
not UI_OVERHAUL_FINAL_RESULTS.exists(),
reason="local-only audit artifacts: data/ is gitignored, so fresh clones lack them; "
"regenerate with the live-server Playwright audit before release sign-off",
)
def test_ui_overhaul_final_audit_has_no_overflow_and_required_screenshots(): def test_ui_overhaul_final_audit_has_no_overflow_and_required_screenshots():
results = json.loads(_read(UI_OVERHAUL_FINAL_RESULTS)) results = json.loads(_read(UI_OVERHAUL_FINAL_RESULTS))
@ -772,11 +779,12 @@ def test_visual_assets_are_referenced_for_review_images():
html = _read(INDEX) html = _read(INDEX)
assets_dir = APP_DIR / "assets" assets_dir = APP_DIR / "assets"
for asset_name in [ assert (assets_dir / "case-portrait.svg").exists()
"case-portrait.svg", assert "assets/case-portrait.svg" in html or "assets/case-portrait.svg" in _read(APP_JS)
for orphan_asset in [
"case-character.svg", "case-character.svg",
"case-emblem.svg", "case-emblem.svg",
"match-web.svg", "match-web.svg",
]: ]:
assert (assets_dir / asset_name).exists() assert not (assets_dir / orphan_asset).exists(), f"{orphan_asset} is unused; do not reintroduce it"
assert f"assets/{asset_name}" in html or f"assets/{asset_name}" in _read(APP_JS)

View file

@ -1130,3 +1130,33 @@ def test_operator_gui_uses_api_bootstrap():
assert "/api/bootstrap" in app_js assert "/api/bootstrap" in app_js
assert "refreshFromApi" in app_js assert "refreshFromApi" in app_js
def test_http_server_serves_bundled_font_with_mime_and_immutable_cache(tmp_path: Path):
static_dir, image_store, store = _fixtures(tmp_path)
fonts_dir = static_dir / "assets" / "fonts"
fonts_dir.mkdir(parents=True)
(fonts_dir / "PretendardVariable.woff2").write_bytes(b"wOF2fake")
server = build_server(
host="127.0.0.1",
port=0,
store=store,
image_store=image_store,
static_dir=static_dir,
)
_start(server)
base = f"http://127.0.0.1:{server.server_port}"
try:
with urlopen(base + "/assets/fonts/PretendardVariable.woff2", timeout=5) as response:
font_headers = dict(response.headers)
font_body = response.read()
with urlopen(base + "/index.html", timeout=5) as response:
html_headers = dict(response.headers)
finally:
server.shutdown()
assert font_body == b"wOF2fake"
assert font_headers["Content-Type"] == "font/woff2"
assert font_headers["Cache-Control"] == "public, max-age=31536000, immutable"
assert "Cache-Control" not in html_headers

View file

@ -1,16 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 480" role="img" aria-labelledby="title desc">
<title id="title">Generic character submission</title>
<desc id="desc">A stylized character-style image used as a rights review sample.</desc>
<rect width="640" height="480" fill="#dfe8e4"/>
<path d="M104 410c28-121 103-188 216-188s188 67 216 188z" fill="#435b50"/>
<circle cx="320" cy="198" r="104" fill="#f0c7a8" stroke="#26302d" stroke-width="7"/>
<path d="M218 182c18-93 67-139 149-139 61 13 91 55 90 127-60-39-135-40-239 12z" fill="#5c352c"/>
<circle cx="284" cy="192" r="14" fill="#172124"/>
<circle cx="362" cy="192" r="14" fill="#172124"/>
<circle cx="280" cy="187" r="5" fill="#fffaf2"/>
<circle cx="358" cy="187" r="5" fill="#fffaf2"/>
<path d="M281 244c33 23 61 23 88 0" fill="none" stroke="#9a4c41" stroke-width="10" stroke-linecap="round"/>
<path d="M184 110l58-50 26 62M454 112l-58-50-26 62" fill="#435b50" stroke="#26302d" stroke-width="7" stroke-linejoin="round"/>
<rect x="72" y="66" width="118" height="28" fill="#d1a339"/>
<rect x="72" y="108" width="88" height="20" fill="#5c7f99"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

View file

@ -1,11 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 480" role="img" aria-labelledby="title desc">
<title id="title">Generic emblem submission</title>
<desc id="desc">A geometric emblem image used as a review sample.</desc>
<rect width="640" height="480" fill="#ebe7dc"/>
<rect x="74" y="54" width="492" height="372" rx="8" fill="#f7f4ee" stroke="#26343a" stroke-width="6"/>
<circle cx="320" cy="238" r="132" fill="#2f6272"/>
<path d="M320 113l40 83 91 14-66 64 16 90-81-43-81 43 16-90-66-64 91-14z" fill="#d6a33a" stroke="#26343a" stroke-width="8" stroke-linejoin="round"/>
<circle cx="320" cy="238" r="52" fill="#f4efe6" stroke="#26343a" stroke-width="7"/>
<path d="M252 86h136M222 392h196" stroke="#8d7961" stroke-width="12" stroke-linecap="round"/>
<rect x="460" y="92" width="58" height="24" fill="#c44b3f"/>
</svg>

Before

Width:  |  Height:  |  Size: 843 B

View file

@ -1,14 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 480" role="img" aria-labelledby="title desc">
<title id="title">Matched web result</title>
<desc id="desc">A generic web result thumbnail for evidence comparison.</desc>
<rect width="640" height="480" fill="#f1eee5"/>
<rect x="60" y="62" width="520" height="356" rx="8" fill="#ffffff" stroke="#29363a" stroke-width="6"/>
<rect x="92" y="94" width="168" height="132" fill="#dfe8e4" stroke="#6f7d7a" stroke-width="4"/>
<circle cx="176" cy="144" r="42" fill="#d39a72"/>
<path d="M127 210c14-44 45-68 87-68s73 25 87 68z" fill="#315f72"/>
<path d="M292 106h188M292 144h238M292 182h160" stroke="#465458" stroke-width="12" stroke-linecap="round"/>
<rect x="92" y="260" width="416" height="18" fill="#c7bca7"/>
<rect x="92" y="300" width="356" height="18" fill="#c7bca7"/>
<rect x="92" y="340" width="198" height="18" fill="#c7bca7"/>
<rect x="470" y="332" width="52" height="26" fill="#c44b3f"/>
</svg>

Before

Width:  |  Height:  |  Size: 977 B

View file

@ -13,13 +13,6 @@
<script src="app.js" defer></script> <script src="app.js" defer></script>
</head> </head>
<body data-internal-only="true"> <body data-internal-only="true">
<div class="asset-preload" aria-hidden="true">
<img src="assets/case-portrait.svg" alt="">
<img src="assets/case-character.svg" alt="">
<img src="assets/case-emblem.svg" alt="">
<img src="assets/match-web.svg" alt="">
</div>
<div class="app-shell"> <div class="app-shell">
<nav class="nav-rail" aria-label="내부 운영 콘솔"> <nav class="nav-rail" aria-label="내부 운영 콘솔">
<div class="brand-block"> <div class="brand-block">

View file

@ -16,7 +16,6 @@
:root { :root {
/* --- Type ---------------------------------------------------------- */ /* --- Type ---------------------------------------------------------- */
--font-ui: "Pretendard Variable", Pretendard, "Malgun Gothic", "Apple SD Gothic Neo", "Segoe UI", system-ui, sans-serif; --font-ui: "Pretendard Variable", Pretendard, "Malgun Gothic", "Apple SD Gothic Neo", "Segoe UI", system-ui, sans-serif;
--font-stamp: "Pretendard Variable", Pretendard, "Malgun Gothic", "Segoe UI Semibold", sans-serif;
--font-mono: "Cascadia Mono", "Consolas", ui-monospace, "DejaVu Sans Mono", monospace; --font-mono: "Cascadia Mono", "Consolas", ui-monospace, "DejaVu Sans Mono", monospace;
/* --- Surfaces (neutral cool) --------------------------------------- */ /* --- Surfaces (neutral cool) --------------------------------------- */
@ -39,17 +38,20 @@
--teal: #1f6f8b; --teal: #1f6f8b;
--teal-deep: #14495c; --teal-deep: #14495c;
--teal-tint: #e7f1f5; --teal-tint: #e7f1f5;
--teal-tint-soft: #eef6f9;
--teal-line: #a9cdda; --teal-line: #a9cdda;
--ochre: #b07d18; --ochre: #b07d18;
--ochre-deep: #8a5f0d;
--ochre-tint: #faf2dd; --ochre-tint: #faf2dd;
--ochre-tint-soft: #fdf9ee;
--ochre-line: #e2c887; --ochre-line: #e2c887;
/* --- Dark rail ----------------------------------------------------- */ /* --- Dark rail ----------------------------------------------------- */
--rail-top: #222b33; --rail: #222b33;
--rail-bot: #1a232b;
--rail-edge: #10171d; --rail-edge: #10171d;
--rail-ink: #d3dce4;
--rail-ink-soft: #9fb0bd;
--rail-ink-strong: #e8edf2;
/* --- Risk / signal semantics --------------------------------------- */ /* --- Risk / signal semantics --------------------------------------- */
--red: #b03a2e; --red-tint: #fceae7; --red-line: #e4aca3; --red: #b03a2e; --red-tint: #fceae7; --red-line: #e4aca3;
@ -57,6 +59,8 @@
--green: #25684a; --green-tint: #e7f3ec; --green-line: #a6d3b9; --green: #25684a; --green-tint: #e7f3ec; --green-line: #a6d3b9;
--violet: #564d75; --violet-tint: #efebf6;--violet-line: #c7bbd9; --violet: #564d75; --violet-tint: #efebf6;--violet-line: #c7bbd9;
--blue: #2c5a8c; --blue-tint: #e8f0fa; --blue-line: #b1c6e2; --blue: #2c5a8c; --blue-tint: #e8f0fa; --blue-line: #b1c6e2;
--forest: #215633; --forest-tint: #ecf7ef;--forest-line: #b1d5b9;
--slate: #4c5d6b; --slate-tint: #eef2f5; --slate-line: #c2cdd6;
/* --- Geometry & elevation ------------------------------------------ */ /* --- Geometry & elevation ------------------------------------------ */
--r-sm: 6px; --r-sm: 6px;
@ -68,18 +72,11 @@
--shadow: 0 1px 3px rgba(23, 32, 41, 0.05), 0 10px 24px -18px rgba(23, 32, 41, 0.25); --shadow: 0 1px 3px rgba(23, 32, 41, 0.05), 0 10px 24px -18px rgba(23, 32, 41, 0.25);
--shadow-lg: 0 18px 48px -20px rgba(16, 28, 38, 0.35), 0 3px 8px rgba(23, 32, 41, 0.05); --shadow-lg: 0 18px 48px -20px rgba(16, 28, 38, 0.35), 0 3px 8px rgba(23, 32, 41, 0.05);
--press: inset 0 1px 0 rgba(255, 255, 255, 0.55); --press: inset 0 1px 0 rgba(255, 255, 255, 0.55);
--inset-well: inset 0 1px 2px rgba(23, 32, 41, 0.04);
--bevel: var(--bevel);
--ease: cubic-bezier(0.22, 0.61, 0.36, 1); --ease: cubic-bezier(0.22, 0.61, 0.36, 1);
/* --- Back-compat aliases (legacy variable names still referenced) -- */
--surface: var(--paper);
--surface-strong: var(--card-raised);
--surface-muted: var(--paper-2);
--line: var(--hair);
--line-strong: var(--hair-strong);
--accent: var(--teal);
--accent-strong: var(--teal-deep);
--audit-object-width: 24%; --audit-object-width: 24%;
} }
@ -106,7 +103,7 @@ body {
} }
::selection { ::selection {
background: rgba(31, 111, 139, 0.22); background: color-mix(in srgb, var(--teal) 22%, transparent);
color: var(--ink); color: var(--ink);
} }
@ -155,10 +152,6 @@ textarea:disabled {
background-color: var(--ink-faint); background-color: var(--ink-faint);
} }
.asset-preload {
display: none;
}
.sr-only { .sr-only {
position: absolute; position: absolute;
width: 1px; width: 1px;
@ -188,8 +181,8 @@ textarea:disabled {
gap: 6px; gap: 6px;
height: 100vh; height: 100vh;
padding: 20px 14px 16px; padding: 20px 14px 16px;
background: var(--rail-top); background: var(--rail);
color: #e8edf2; color: var(--rail-ink-strong);
border-right: 1px solid var(--rail-edge); border-right: 1px solid var(--rail-edge);
box-shadow: inset -1px 0 0 rgba(255, 255, 255, 0.04); box-shadow: inset -1px 0 0 rgba(255, 255, 255, 0.04);
} }
@ -217,8 +210,7 @@ textarea:disabled {
.brand-block div span { .brand-block div span {
margin-top: 4px; margin-top: 4px;
color: #9fb0bd; color: var(--rail-ink-soft);
font-family: var(--font-stamp);
font-size: 11px; font-size: 11px;
letter-spacing: 0.12em; letter-spacing: 0.12em;
text-transform: uppercase; text-transform: uppercase;
@ -232,11 +224,10 @@ textarea:disabled {
border-radius: 10px; border-radius: 10px;
background: var(--teal); background: var(--teal);
color: #fff; color: #fff;
font-family: var(--font-stamp);
font-weight: 750; font-weight: 750;
font-size: 17px; font-size: 17px;
letter-spacing: 0.04em; letter-spacing: 0.04em;
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.18); box-shadow: var(--bevel);
} }
.nav-button { .nav-button {
@ -251,7 +242,7 @@ textarea:disabled {
border: 1px solid transparent; border: 1px solid transparent;
border-radius: var(--radius); border-radius: var(--radius);
background: transparent; background: transparent;
color: #d3dce4; color: var(--rail-ink);
text-align: left; text-align: left;
font-size: 14px; font-size: 14px;
transition: background 160ms var(--ease), color 160ms var(--ease), transform 120ms var(--ease); transition: background 160ms var(--ease), color 160ms var(--ease), transform 120ms var(--ease);
@ -287,7 +278,6 @@ textarea:disabled {
height: 28px; height: 28px;
border: 1px solid currentColor; border: 1px solid currentColor;
border-radius: 7px; border-radius: 7px;
font-family: var(--font-stamp);
font-size: 12px; font-size: 12px;
font-weight: 700; font-weight: 700;
opacity: 0.85; opacity: 0.85;
@ -313,8 +303,7 @@ textarea:disabled {
padding: 11px 12px; padding: 11px 12px;
border: 1px solid rgba(255, 255, 255, 0.14); border: 1px solid rgba(255, 255, 255, 0.14);
border-radius: var(--radius); border-radius: var(--radius);
color: #d3dce4; color: var(--rail-ink);
font-family: var(--font-stamp);
font-size: 12px; font-size: 12px;
letter-spacing: 0.06em; letter-spacing: 0.06em;
text-transform: uppercase; text-transform: uppercase;
@ -383,7 +372,7 @@ textarea:disabled {
border-radius: var(--r-sm); border-radius: var(--r-sm);
background: var(--card-raised); background: var(--card-raised);
color: var(--ink); color: var(--ink);
box-shadow: inset 0 1px 2px rgba(20, 30, 28, 0.04); box-shadow: var(--inset-well);
transition: border-color 140ms var(--ease), box-shadow 140ms var(--ease); transition: border-color 140ms var(--ease), box-shadow 140ms var(--ease);
} }
@ -415,7 +404,7 @@ textarea:disabled {
.memo-field textarea:focus { .memo-field textarea:focus {
outline: none; outline: none;
border-color: var(--teal); border-color: var(--teal);
box-shadow: inset 0 1px 2px rgba(20, 30, 28, 0.04), 0 0 0 3px rgba(34, 96, 114, 0.16); box-shadow: var(--inset-well), 0 0 0 3px color-mix(in srgb, var(--teal) 16%, transparent);
} }
.product-purpose { .product-purpose {
@ -456,12 +445,14 @@ textarea:disabled {
gap: 8px; gap: 8px;
min-height: 40px; min-height: 40px;
padding: 7px 11px; padding: 7px 11px;
overflow: hidden;
border: 1px solid var(--hair); border: 1px solid var(--hair);
border-radius: var(--r-sm); border-radius: var(--r-sm);
background: var(--card-raised); background: var(--card-raised);
color: var(--ink-soft); color: var(--ink-soft);
font-size: 13px; font-size: 13px;
white-space: nowrap; white-space: nowrap;
text-overflow: ellipsis;
box-shadow: var(--shadow-sm); box-shadow: var(--shadow-sm);
} }
@ -550,21 +541,18 @@ textarea:disabled {
.view.active { .view.active {
display: block; display: block;
/* fill-mode `backwards` (not `both`): the entrance animation must NOT persist /* Opacity-only on purpose: any transform here even one animating toward
its end-state transform. A retained identity transform on .view.active would `none` establishes a containing block for the whole animation and breaks
establish a containing block and break `position: fixed` on the floating `position: fixed` on the floating decision panel inside this view. */
decision panel, dropping it to the bottom of the page. */
animation: viewRise 320ms var(--ease) backwards; animation: viewRise 320ms var(--ease) backwards;
} }
@keyframes viewRise { @keyframes viewRise {
from { from {
opacity: 0; opacity: 0;
transform: translateY(9px);
} }
to { to {
opacity: 1; opacity: 1;
transform: none;
} }
} }
@ -673,7 +661,6 @@ textarea:disabled {
.queue-folder-input span, .queue-folder-input span,
.queue-file-input span:first-child { .queue-file-input span:first-child {
color: var(--ink-faint); color: var(--ink-faint);
font-family: var(--font-stamp);
font-size: 11px; font-size: 11px;
font-weight: 600; font-weight: 600;
letter-spacing: 0.07em; letter-spacing: 0.07em;
@ -687,7 +674,7 @@ textarea:disabled {
border-radius: var(--r-sm); border-radius: var(--r-sm);
background: var(--card-raised); background: var(--card-raised);
color: var(--ink); color: var(--ink);
box-shadow: inset 0 1px 2px rgba(20, 30, 28, 0.04); box-shadow: var(--inset-well);
} }
.queue-file-input span:last-of-type { .queue-file-input span:last-of-type {
@ -773,10 +760,9 @@ textarea:disabled {
border-radius: var(--r-pill); border-radius: var(--r-pill);
background: var(--teal); background: var(--teal);
color: #fff; color: #fff;
font-family: var(--font-stamp);
font-size: 13px; font-size: 13px;
font-weight: 700; font-weight: 700;
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.18); box-shadow: var(--bevel);
} }
.operator-workflow strong { .operator-workflow strong {
@ -797,7 +783,6 @@ textarea:disabled {
.eyebrow { .eyebrow {
margin: 0 0 6px; margin: 0 0 6px;
color: var(--teal); color: var(--teal);
font-family: var(--font-stamp);
font-size: 11px; font-size: 11px;
font-weight: 600; font-weight: 600;
letter-spacing: 0.16em; letter-spacing: 0.16em;
@ -836,7 +821,6 @@ textarea:disabled {
.knowledge-form label span, .knowledge-form label span,
.memo-field span { .memo-field span {
color: var(--ink-faint); color: var(--ink-faint);
font-family: var(--font-stamp);
font-size: 11px; font-size: 11px;
font-weight: 600; font-weight: 600;
letter-spacing: 0.07em; letter-spacing: 0.07em;
@ -893,7 +877,7 @@ textarea:disabled {
color: #fff; color: #fff;
font-size: 13px; font-size: 13px;
font-weight: 700; font-weight: 700;
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.18); box-shadow: var(--bevel);
} }
.knowledge-form .file-picker-button { .knowledge-form .file-picker-button {
@ -932,7 +916,7 @@ textarea:disabled {
border-radius: var(--r-sm); border-radius: var(--r-sm);
background: var(--card-raised); background: var(--card-raised);
overflow: hidden; overflow: hidden;
box-shadow: inset 0 1px 2px rgba(20, 30, 28, 0.04); box-shadow: var(--inset-well);
} }
.segmented button { .segmented button {
@ -1233,7 +1217,6 @@ th {
z-index: 1; z-index: 1;
background: var(--card-sunk); background: var(--card-sunk);
color: var(--ink-faint); color: var(--ink-faint);
font-family: var(--font-stamp);
font-size: 11px; font-size: 11px;
font-weight: 600; font-weight: 600;
letter-spacing: 0.08em; letter-spacing: 0.08em;
@ -1258,9 +1241,12 @@ tbody tr:hover {
background: var(--teal-tint); background: var(--teal-tint);
} }
tbody tr.selected-row { /* Selection must outrank the zebra stripe (.queue-grid tbody
.queue-row:nth-child(even), specificity 0,3,1) on even rows. */
tbody tr.selected-row,
.queue-grid tbody .queue-row.selected-row {
box-shadow: inset 4px 0 0 var(--teal), var(--shadow-sm); box-shadow: inset 4px 0 0 var(--teal), var(--shadow-sm);
background: #eef6f9; background: var(--teal-tint-soft);
} }
.thumb { .thumb {
@ -1384,9 +1370,9 @@ tbody tr.selected-row {
} }
.source-naver { .source-naver {
border-color: #b1d5b9; border-color: var(--forest-line);
background: #ecf7ef; background: var(--forest-tint);
color: #215633; color: var(--forest);
} }
.source-google { .source-google {
@ -1402,9 +1388,9 @@ tbody tr.selected-row {
} }
.source-internal { .source-internal {
border-color: #c2cdd6; border-color: var(--slate-line);
background: #eef2f5; background: var(--slate-tint);
color: #4c5d6b; color: var(--slate);
} }
.source-failure { .source-failure {
@ -1418,7 +1404,6 @@ tbody tr.selected-row {
border-color: var(--hair); border-color: var(--hair);
background: var(--card-sunk); background: var(--card-sunk);
color: var(--ink-soft); color: var(--ink-soft);
font-family: var(--font-stamp);
letter-spacing: 0.02em; letter-spacing: 0.02em;
} }
@ -1437,7 +1422,7 @@ tbody tr.selected-row {
.provider-chip.disabled, .provider-chip.disabled,
.provider-chip.skipped { .provider-chip.skipped {
border-color: var(--hair-strong); border-color: var(--hair-strong);
color: #66747f; color: var(--ink-soft);
} }
.reason-cell { .reason-cell {
@ -1551,7 +1536,6 @@ tbody tr.selected-row {
.fact-item span { .fact-item span {
display: block; display: block;
color: var(--ink-faint); color: var(--ink-faint);
font-family: var(--font-stamp);
font-size: 10px; font-size: 10px;
font-weight: 600; font-weight: 600;
letter-spacing: 0.07em; letter-spacing: 0.07em;
@ -1650,7 +1634,6 @@ tbody tr.selected-row {
.summary-stat span { .summary-stat span {
display: block; display: block;
color: var(--ink-faint); color: var(--ink-faint);
font-family: var(--font-stamp);
font-size: 10px; font-size: 10px;
font-weight: 600; font-weight: 600;
letter-spacing: 0.06em; letter-spacing: 0.06em;
@ -1726,7 +1709,7 @@ tbody tr.selected-row {
.evidence-group-strong { .evidence-group-strong {
border-color: var(--ochre-line); border-color: var(--ochre-line);
background: #fdf9ee; background: var(--ochre-tint-soft);
box-shadow: 0 0 0 1px rgba(176, 125, 24, 0.08); box-shadow: 0 0 0 1px rgba(176, 125, 24, 0.08);
} }
@ -1907,7 +1890,7 @@ tbody tr.selected-row {
.knowledge-row.watchlist { .knowledge-row.watchlist {
border-color: var(--ochre-line); border-color: var(--ochre-line);
background: #fdf9ee; background: var(--ochre-tint-soft);
} }
/* ===================== Decision pane / floating ==================== */ /* ===================== Decision pane / floating ==================== */
@ -1989,7 +1972,6 @@ tbody tr.selected-row {
.decision-secondary summary { .decision-secondary summary {
cursor: pointer; cursor: pointer;
color: var(--ink-soft); color: var(--ink-soft);
font-family: var(--font-stamp);
font-size: 11px; font-size: 11px;
font-weight: 600; font-weight: 600;
letter-spacing: 0.07em; letter-spacing: 0.07em;
@ -2319,7 +2301,7 @@ tbody tr.selected-row {
border-radius: var(--r-pill); border-radius: var(--r-pill);
background: var(--paper-2); background: var(--paper-2);
overflow: hidden; overflow: hidden;
box-shadow: inset 0 1px 2px rgba(20, 30, 28, 0.12); box-shadow: inset 0 1px 2px rgba(23, 32, 41, 0.12);
} }
.quota-meter span { .quota-meter span {
@ -2427,7 +2409,9 @@ tbody tr.selected-row {
} }
} }
@media (max-width: 1180px) { @media (max-width: 1380px) {
/* The 5-track toolbar needs ~1364px of viewport (track minimums + rail +
paddings); collapse it before that point, not at the generic 1180px step. */
.toolbar { .toolbar {
grid-template-columns: 1fr 1fr 1fr; grid-template-columns: 1fr 1fr 1fr;
} }
@ -2435,7 +2419,9 @@ tbody tr.selected-row {
.filter-search { .filter-search {
grid-column: span 2; grid-column: span 2;
} }
}
@media (max-width: 1180px) {
.case-layout { .case-layout {
grid-template-columns: minmax(280px, 0.9fr) minmax(360px, 1.1fr); grid-template-columns: minmax(280px, 0.9fr) minmax(360px, 1.1fr);
} }