provinans / frontend /src /index.css
reversely's picture
Upload folder using huggingface_hub
7b6e1ea verified
Raw
History Blame Contribute Delete
130 kB
/* Global design tokens (design-guideline section 2.1), light-default. The app
shell (section 3: left rail + top bar) and the pages that still use the
global element styles (Worklist, Cohort dashboard, Export) read these tokens;
the section 9 surfaces (Intake, Registry, Mapping, Case review) scope their
own copies of the same set. One blue accent carries chrome throughout, so the
composed top bar and every page surface point with the same hue. Legacy token
names are aliased to the guideline names below, so the global element styles
resolve unchanged. */
:root {
/* Clinical grey + navy blue. Emphasis surfaces opt into a deep-navy block with white text (see
--block-* below and the demo chrome), keyed to the Provinans mark (white line-art on deep navy). */
--bg: #f2f4f5;
--panel: #ffffff;
--surface: #ffffff;
--surface-2: #ecf0f3;
--ink: #1c2b36;
--ink-muted: #51626e;
--hairline: #dfe5e9;
--accent: #1b374a;
--accent-soft: rgba(27, 55, 74, 0.09);
--pos: #137a4c;
--warn: #b0851f;
--neg: #c0392b;
/* Deep-navy emphasis block: white text on a dark surface for contrast, with a periwinkle accent and a
hairline rule, keyed to the reference set (dark hero cards, white heading, muted body, blue accent).
Theme-independent: a block is dark in both light and dark mode. */
--block-bg: #101a2c;
--block-bg-2: #18233a;
--block-ink: #ffffff;
--block-ink-muted: rgba(255, 255, 255, 0.66);
--block-accent: #98a4e6;
--block-hairline: rgba(255, 255, 255, 0.12);
/* Shape (design-guideline section 2.3): two radii total. The section-9
surfaces alias these, so a radius change lives in one place. */
--r-card: 12px;
--r-ctrl: 8px;
/* Legacy aliases: the global element styles below still name these. */
--text: var(--ink);
--text-muted: var(--ink-muted);
--bg-alt: var(--panel);
--border: var(--hairline);
--confirmed: var(--pos);
--needs-review: var(--warn);
--flagged: var(--neg);
/* Type family (design-guideline section 2.2). Aeonik is the specified face
(CoType Foundry, commercial): the binaries are not redistributable, so they
stay out of git and load from /fonts/ at runtime (the @font-face rules
below; the files live in public/fonts/, gitignored). A clone or a deploy
without those files falls through the stack to a system grotesque, keeping
the weight-driven hierarchy. The bundled weights are Regular (400), Medium
(500), and Bold (700); the Medium face covers the 500-600 range so the
design's 600 headings render Medium rather than rounding up to Bold. */
--app-font: "Aeonik", "Inter", system-ui, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
font: 15px/1.5 var(--app-font);
color-scheme: light dark;
color: var(--ink);
background: var(--bg);
}
@media (prefers-color-scheme: dark) {
:root {
--bg: #0b1220;
--panel: #111a2e;
--surface: #16223a;
--surface-2: #1d2c49;
--ink: #f4f6fb;
--ink-muted: #9ba6bc;
--hairline: #243350;
--accent: #5e86a0;
--accent-soft: rgba(94, 134, 160, 0.16);
--pos: #3dcf8e;
--warn: #e0a93b;
--neg: #e5654b;
}
}
/* Aeonik (CoType Foundry, commercial). Files load from public/fonts/, which is
gitignored; a clone without them falls back through --app-font. The Medium
face covers 500-600 so 600 headings render Medium, not synthetic Bold. */
@font-face {
font-family: "Aeonik";
src: url("/fonts/Aeonik-Regular.woff2") format("woff2");
font-weight: 400;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "Aeonik";
src: url("/fonts/Aeonik-Medium.woff2") format("woff2");
font-weight: 500 600;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "Aeonik";
src: url("/fonts/Aeonik-Bold.woff2") format("woff2");
font-weight: 700;
font-style: normal;
font-display: swap;
}
* {
box-sizing: border-box;
}
body {
margin: 0;
}
/* Bare anchors take the signature colour (design-guideline section 6.1). Links
carrying an explicit colour through a class keep it by specificity. */
a {
color: var(--accent);
}
/* Inline SVG icons (icons.tsx) replace the former unicode glyph spans. Each icon
sizes to 1em, so it takes the font-size of the element it sits in, and aligns to
the middle of adjacent text. */
.icon {
display: inline-block;
vertical-align: middle;
flex: none;
}
/* App shell (design-guideline section 3): a left rail and a top bar, each one
tonal step from the canvas (--panel) and bounded by a 1px hairline. The rail
carries primary nav as icon+label rows, the active row on --accent-soft with a
2px accent left-edge, and collapses to icons. The top bar carries the view
title, the global context, and the reviewer affordance. One blue accent
throughout, matching the per-page surfaces. */
.app-shell {
display: flex;
align-items: stretch;
min-height: 100vh;
}
.rail {
flex: none;
width: 232px;
align-self: flex-start;
position: sticky;
top: 0;
height: 100vh;
overflow-y: auto;
display: flex;
flex-direction: column;
gap: 4px;
padding: 14px 12px;
background: var(--block-bg);
border-right: 1px solid var(--block-hairline);
}
.rail.collapsed {
width: 60px;
}
.rail-top {
display: flex;
align-items: center;
gap: 8px;
padding: 2px 4px 12px;
border-bottom: 1px solid var(--block-hairline);
}
.rail-brand {
display: flex;
align-items: center;
gap: 9px;
min-width: 0;
}
.rail-brand-mark {
flex: none;
width: 26px;
height: 26px;
display: block;
border-radius: 7px;
object-fit: cover;
}
.rail-brand-name {
font-size: 14px;
font-weight: 600;
line-height: 1.25;
color: var(--block-ink);
}
.rail-collapse {
margin-left: auto;
flex: none;
width: 28px;
height: 28px;
display: grid;
place-items: center;
padding: 0;
border: 1px solid var(--block-hairline);
border-radius: 6px;
background: transparent;
color: var(--block-ink-muted);
cursor: pointer;
line-height: 1;
}
.rail-collapse:hover {
border-color: var(--block-accent);
color: var(--block-ink);
}
.rail-nav {
display: flex;
flex-direction: column;
gap: 2px;
margin-top: 4px;
}
.rail-link {
display: flex;
align-items: center;
gap: 11px;
padding: 8px 10px;
border-radius: 6px;
color: var(--block-ink-muted);
text-decoration: none;
font-size: 14px;
font-weight: 500;
white-space: nowrap;
}
.rail-link:hover {
background: var(--block-bg-2);
color: var(--block-ink);
}
.rail-link.active {
background: var(--block-bg-2);
color: var(--block-ink);
box-shadow: inset 2px 0 0 var(--block-accent);
font-weight: 600;
}
.rail-link:focus-visible {
outline: 2px solid var(--block-accent);
outline-offset: -2px;
}
.rail-icon {
flex: none;
width: 18px;
height: 18px;
}
/* The three-layer spine: each rail group is a labelled band of links. */
.rail-group {
display: flex;
flex-direction: column;
gap: 2px;
}
.rail-group + .rail-group {
margin-top: 12px;
}
.rail-group-label {
padding: 2px 10px;
margin-bottom: 2px;
font-size: 11px;
font-weight: 600;
letter-spacing: 0.06em;
text-transform: uppercase;
color: var(--block-ink-muted);
}
/* The case-study link sits at the foot of the rail, held down by the flex column
and set off by a hairline so it reads as secondary to the primary nav. */
.rail-foot {
margin-top: auto;
padding-top: 8px;
border-top: 1px solid var(--block-hairline);
}
.rail.collapsed .rail-brand-name,
.rail.collapsed .rail-label {
display: none;
}
.rail.collapsed .rail-top {
justify-content: center;
}
.rail.collapsed .rail-link {
justify-content: center;
padding: 8px;
}
/* Collapsed: the group labels fall away, but the grouping still reads as spacing. */
.rail.collapsed .rail-group-label {
display: none;
}
.rail.collapsed .rail-group + .rail-group {
margin-top: 8px;
}
.app-body {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
}
.topbar {
position: sticky;
top: 0;
z-index: 5;
display: flex;
align-items: center;
gap: 16px;
padding: 12px 24px;
background: var(--panel);
border-bottom: 1px solid var(--hairline);
}
.topbar-title {
font-size: 16px;
font-weight: 600;
color: var(--ink);
}
/* The opened project inside the Provinans workbench; the rail brand is the workbench itself. */
.topbar-project {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 4px 10px 4px 5px;
border: 1px solid var(--hairline);
border-radius: 999px;
background: var(--surface-2);
}
.topbar-project-badge {
font-size: 10px;
font-weight: 600;
letter-spacing: 0.06em;
text-transform: uppercase;
color: var(--accent);
background: var(--accent-soft);
padding: 2px 7px;
border-radius: 999px;
}
.topbar-project-name {
font-size: 13px;
font-weight: 600;
color: var(--ink);
}
.topbar-project-standards {
font-size: 12px;
color: var(--ink-muted);
}
.topbar-right {
margin-left: auto;
display: flex;
align-items: center;
gap: 12px;
}
/* The project switcher: the topbar-project pill is now a button that opens a dropdown of projects. */
.project-switcher {
position: relative;
display: inline-flex;
}
button.topbar-project {
cursor: pointer;
font: inherit;
appearance: none;
}
button.topbar-project:hover {
border-color: var(--accent);
}
button.topbar-project:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
}
.project-switcher-caret {
font-size: 10px;
color: var(--ink-muted);
margin-left: 2px;
}
.project-menu {
position: absolute;
top: calc(100% + 6px);
left: 0;
z-index: 20;
min-width: 240px;
display: flex;
flex-direction: column;
padding: 4px;
background: var(--surface);
border: 1px solid var(--hairline);
border-radius: 10px;
box-shadow: 0 8px 24px rgba(14, 20, 32, 0.14);
}
.project-menu-item {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
width: 100%;
padding: 8px 10px;
border: none;
border-radius: 6px;
background: transparent;
color: var(--ink);
font: inherit;
text-align: left;
cursor: pointer;
}
.project-menu-item:hover {
background: var(--surface-2);
}
.project-menu-item.active {
background: var(--accent-soft);
color: var(--accent);
}
.project-menu-name {
font-weight: 500;
}
.project-menu-count {
font-size: 12px;
color: var(--ink-muted);
font-variant-numeric: tabular-nums;
}
.project-menu-item.active .project-menu-count {
color: var(--accent);
}
.project-menu-sep {
height: 1px;
margin: 4px 6px;
background: var(--hairline);
}
.project-menu-new {
width: 100%;
padding: 8px 10px;
border: none;
border-radius: 6px;
background: transparent;
color: var(--accent);
font: inherit;
font-weight: 500;
text-align: left;
cursor: pointer;
}
.project-menu-new:hover {
background: var(--accent-soft);
}
.project-menu-new:disabled {
color: var(--ink-muted);
cursor: default;
}
.project-menu-empty,
.project-menu-error {
padding: 8px 10px;
font-size: 13px;
color: var(--ink-muted);
}
.project-menu-error {
color: var(--neg);
}
.app-content {
flex: 1;
/* min-width: 0 lets this flex column shrink below its content's intrinsic width on a phone, so wide
content (tables, diagrams) reflows or scrolls in its own box instead of stretching the whole page. */
min-width: 0;
width: 100%;
max-width: 1320px;
margin: 0 auto;
padding: 24px;
}
/* Small screens: the rail holds icons only (its group labels drop too), and the project standards drop. */
@media (max-width: 760px) {
.rail,
.rail.collapsed {
width: 56px;
}
.rail-brand-name,
.rail-label,
.rail-collapse {
display: none;
}
.rail-top {
justify-content: center;
}
.rail-link {
justify-content: center;
padding: 8px;
}
.rail-group-label {
display: none;
}
.topbar-project-standards {
display: none;
}
/* Let the top bar wrap and the login fields flex, so it never forces the page wider than the phone. */
.topbar {
flex-wrap: wrap;
row-gap: 8px;
padding: 10px 16px;
}
.token-bar {
flex-wrap: wrap;
}
.token-bar input {
width: auto;
flex: 1 1 96px;
min-width: 84px;
}
/* Case review stacks its evidence and checklist columns instead of holding a 640px two-column minimum. */
.case-review-body {
grid-template-columns: 1fr;
}
/* Let the evidence panes shrink to the phone: the doc header wraps, and the panes and page image stop
forcing their content's intrinsic width. */
.cr .cr-document,
.cr .cr-record,
.cr .cr-band {
min-width: 0;
}
.cr .cr-doc-head {
flex-wrap: wrap;
}
.cr .cr-page-img {
min-width: 0;
max-width: 100%;
}
/* Dashboard field cards go single column instead of holding a 360px minimum per card, and their charts
cap to the card width. */
.db .db-field-grid {
grid-template-columns: 1fr;
}
.db .db-field-card {
min-width: 0;
}
.db svg {
max-width: 100%;
}
}
/* A horizontally-scrollable wrapper for wide data tables, so a table scrolls inside its own box instead
of stretching the page (which on iOS zooms the whole layout out). */
.table-scroll {
overflow-x: auto;
-webkit-overflow-scrolling: touch;
}
/* Reviewer token (issue #24): the shared bearer token the write endpoints
require on a public deployment. It sits in the top-bar right slot as the
reviewer affordance (design-guideline section 3). */
.token-bar {
display: flex;
align-items: center;
gap: 8px;
}
.token-bar input {
font: inherit;
padding: 4px 6px;
width: 150px;
border: 1px solid var(--border);
border-radius: 4px;
background: var(--bg);
color: var(--text);
}
.token-bar button {
padding: 4px 10px;
}
.token-status {
color: var(--confirmed);
font-size: 13px;
}
.token-error {
color: var(--error, #c0362c);
font-size: 13px;
}
h1 {
font-size: clamp(30px, 4vw, 36px);
font-weight: 500;
letter-spacing: -0.01em;
margin: 0 0 12px;
}
.worklist .eyebrow {
margin: 0 0 4px;
font-size: 12px;
font-weight: 600;
letter-spacing: 0.06em;
text-transform: uppercase;
color: var(--ink-muted);
}
.worklist .wl-lede {
max-width: 62ch;
color: var(--ink-muted);
margin: 4px 0 16px;
}
/* Two large links under the subheader: the source repo and the case study. */
.worklist .wl-cta {
display: flex;
flex-wrap: wrap;
gap: 12px;
margin: 0 0 26px;
}
.worklist .wl-cta-btn {
display: inline-flex;
align-items: center;
gap: 10px;
padding: 13px 22px;
font-size: 15px;
font-weight: 600;
border-radius: 10px;
border: 1px solid var(--accent);
text-decoration: none;
transition: transform 120ms ease, box-shadow 120ms ease, background 120ms ease,
border-color 120ms ease;
}
.worklist .wl-cta-btn:hover {
transform: translateY(-1px);
box-shadow: 0 6px 18px rgba(27, 55, 74, 0.18);
}
.worklist .wl-cta-primary {
background: var(--accent);
color: #ffffff;
}
.worklist .wl-cta-secondary {
background: var(--surface);
color: var(--ink);
border-color: var(--hairline);
}
.worklist .wl-cta-secondary:hover {
border-color: var(--accent);
}
.worklist .wl-cta-icon {
flex: none;
}
.filters {
display: flex;
flex-wrap: wrap;
align-items: end;
gap: 16px;
margin-bottom: 16px;
padding: 12px;
background: var(--bg-alt);
border-radius: 6px;
}
.filters label {
display: flex;
flex-direction: column;
gap: 4px;
font-size: 13px;
color: var(--text-muted);
}
.filters input,
.filters select {
font: inherit;
padding: 4px 6px;
}
button {
font: inherit;
padding: 6px 12px;
border: 1px solid var(--border);
border-radius: 4px;
background: var(--bg);
color: var(--text);
cursor: pointer;
}
button:hover:not(:disabled) {
border-color: var(--accent);
}
button:disabled {
opacity: 0.5;
cursor: default;
}
table {
width: 100%;
border-collapse: collapse;
}
th,
td {
text-align: left;
padding: 8px 10px;
border-bottom: 1px solid var(--border);
}
th {
cursor: pointer;
color: var(--text-muted);
font-weight: 600;
user-select: none;
}
/* The active sort column shows a small accent caret (design-guideline section 5.1); every other sortable
column shows a faint one so the affordance reads before a click (#198). */
.worklist th .wl-sort {
margin-left: 4px;
font-size: 12px;
color: var(--accent);
}
.worklist th .wl-sort.wl-sort-idle {
color: var(--ink-muted);
opacity: 0.5;
}
.error {
color: var(--flagged);
}
.action-error {
padding: 8px 12px;
border: 1px solid var(--flagged);
border-radius: 6px;
margin-bottom: 16px;
}
.action-error button {
margin-left: 8px;
}
.count {
color: var(--text-muted);
margin-top: 12px;
}
.intake-summary {
margin-top: 16px;
padding: 12px;
background: var(--bg-alt);
border-radius: 6px;
}
.intake-summary p {
margin: 0 0 8px;
}
/* Worklist visual hierarchy (#156). A section-5.4 metric row and a status
distribution bar sit above a section-5.1 table, all over the global tokens.
Confirmed/awaiting/flagged carry the semantic trio as case state, the one
sanctioned use of those colours here. */
.worklist .wl-metrics {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin: 6px 0 14px;
}
/* Each metric tile is a filter: clicking it narrows the table to that cohort bucket (#198). Reset button
chrome and give it a hover and a selected state so it reads as a control, not just a readout. */
.worklist .wl-metric {
display: flex;
flex-direction: column;
gap: 2px;
appearance: none;
text-align: left;
background: transparent;
border: 1px solid transparent;
border-radius: 10px;
padding: 8px 14px 8px 12px;
cursor: pointer;
transition: background 120ms ease-out, border-color 120ms ease-out;
}
.worklist .wl-metric:hover {
background: var(--surface-2);
}
.worklist .wl-metric.is-active {
background: var(--accent-soft);
border-color: var(--accent);
}
.worklist .wl-metric-value {
font-size: 32px;
font-weight: 500;
line-height: 1.1;
font-variant-numeric: tabular-nums;
color: var(--ink);
}
.worklist .wl-metric-label {
font-size: 12px;
letter-spacing: 0.04em;
color: var(--ink-muted);
}
.worklist .wl-statusbar {
display: flex;
height: 6px;
max-width: 640px;
margin: 0 0 8px;
border-radius: 999px;
overflow: hidden;
background: var(--surface-2);
}
.worklist .wl-statusbar-legend {
display: flex;
flex-wrap: wrap;
gap: 16px;
max-width: 640px;
margin: 0 0 22px;
font-size: 11px;
color: var(--ink-muted);
}
.worklist .wl-legend-item {
display: inline-flex;
align-items: center;
gap: 6px;
}
.worklist .wl-seg {
display: block;
height: 100%;
}
.worklist .wl-seg-confirmed {
background: var(--pos);
}
.worklist .wl-seg-awaiting {
background: var(--warn);
}
.worklist .wl-seg-flagged {
background: var(--neg);
}
/* Table (section 5.1): sticky header, hairline row separators, hover raise. */
.worklist .wl-table {
width: 100%;
border-collapse: collapse;
}
.worklist .wl-table thead th {
position: sticky;
top: 0;
z-index: 1;
padding: 9px 12px;
background: var(--panel);
border-bottom: 1px solid var(--hairline);
font-size: 12px;
font-weight: 600;
letter-spacing: 0.04em;
color: var(--ink-muted);
text-transform: none;
cursor: pointer;
user-select: none;
}
.worklist .wl-table tbody td {
padding: 11px 12px;
border-bottom: 1px solid var(--hairline);
font-size: 13px;
}
.worklist .wl-table tbody tr {
transition: background 120ms ease-out;
}
.worklist .wl-table tbody tr:hover {
background: var(--surface-2);
}
/* The whole row navigates to the case (#198), so it reads as clickable. */
.worklist .wl-table tbody tr.wl-row {
cursor: pointer;
}
/* The preset is a filter chip grouped with the controls; the reset is a quiet text button that appears
only when a filter is active, so the two no longer read as identical siblings (#198). */
.worklist .wl-preset {
appearance: none;
padding: 6px 12px;
font-size: 12px;
color: var(--ink);
background: var(--surface);
border: 1px solid var(--hairline);
border-radius: 999px;
cursor: pointer;
transition: background 120ms ease-out, border-color 120ms ease-out, color 120ms ease-out;
}
.worklist .wl-preset:hover {
border-color: var(--accent);
}
.worklist .wl-preset.is-active {
color: var(--accent);
background: var(--accent-soft);
border-color: var(--accent);
}
.worklist .wl-reset {
appearance: none;
border: none;
background: transparent;
color: var(--ink-muted);
font-size: 12px;
text-decoration: underline;
cursor: pointer;
}
.worklist .wl-reset:hover {
color: var(--ink);
}
.worklist .wl-case {
font-weight: 500;
font-variant-numeric: tabular-nums;
letter-spacing: 0.02em;
color: var(--accent);
text-decoration: none;
}
.worklist .wl-case:hover {
text-decoration: underline;
}
/* State chip (section 6.3): a semantic-trio dot plus a neutral label. */
.worklist .wl-chip {
display: inline-flex;
align-items: center;
gap: 7px;
font-size: 12px;
color: var(--ink);
}
.worklist .wl-dot {
width: 7px;
height: 7px;
border-radius: 999px;
background: var(--ink-muted);
}
.worklist .wl-chip-confirmed .wl-dot {
background: var(--pos);
}
.worklist .wl-chip-awaiting .wl-dot {
background: var(--warn);
}
.worklist .wl-chip-flagged .wl-dot {
background: var(--neg);
}
/* Loading skeleton at the true table layout (section 6.4). */
.worklist .wl-skel {
display: block;
height: 13px;
border-radius: var(--r-ctrl);
background: var(--surface-2);
animation: wl-pulse 1.2s ease-in-out infinite;
}
.worklist .wl-skel-chip {
width: 88px;
}
.worklist .wl-empty {
margin-top: 16px;
color: var(--ink-muted);
}
@keyframes wl-pulse {
0%,
100% {
opacity: 1;
}
50% {
opacity: 0.5;
}
}
@media (prefers-reduced-motion: reduce) {
.worklist .wl-table tbody tr,
.worklist .wl-skel {
transition: none;
animation: none;
}
}
/* Case review screen */
.case-review-header {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 16px;
}
.case-review-header h1 {
margin: 0;
}
.case-review-header .progress {
margin-left: auto;
color: var(--text-muted);
font-size: 13px;
}
.case-review-body {
display: grid;
grid-template-columns: minmax(280px, 1fr) minmax(360px, 1fr);
gap: 24px;
align-items: start;
}
.page-viewer {
position: sticky;
top: 16px;
border: 1px solid var(--border);
border-radius: 6px;
padding: 12px;
text-align: center;
}
.page-viewer img {
max-width: 100%;
border: 1px solid var(--border);
}
.page-viewer .no-pages {
color: var(--text-muted);
padding: 40px 0;
}
.page-nav {
display: flex;
align-items: center;
justify-content: center;
gap: 12px;
margin-top: 8px;
font-size: 13px;
color: var(--text-muted);
}
.field-list {
display: flex;
flex-direction: column;
gap: 10px;
}
.field-card {
border: 1px solid var(--border);
border-radius: 6px;
padding: 10px 12px;
cursor: pointer;
}
.field-card.focused {
border-color: var(--accent);
box-shadow: 0 0 0 1px var(--accent);
}
.field-card-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 6px;
}
.field-name {
font-weight: 600;
}
.field-value {
margin: 0 0 6px;
}
.field-value .confidence {
color: var(--text-muted);
font-size: 13px;
}
.evidence {
margin: 0 0 8px;
padding: 6px 10px;
border-left: 3px solid var(--accent);
background: var(--bg-alt);
font-style: italic;
font-size: 13px;
}
.evidence-fallback {
margin: 0 0 8px;
color: var(--text-muted);
font-size: 13px;
}
.not-applicable {
margin: 0;
color: var(--text-muted);
font-style: italic;
}
.run-extraction-banner {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
margin-bottom: 16px;
padding: 12px 16px;
border: 1px solid var(--accent);
border-radius: 6px;
background: var(--bg-alt);
}
.evidence-source {
display: inline-block;
margin-left: 8px;
padding: 1px 6px;
border-radius: 3px;
background: var(--bg);
border: 1px solid var(--border);
color: var(--text-muted);
font-size: 11px;
font-style: normal;
vertical-align: middle;
}
.visual-evidence {
margin: 0 0 8px;
font-size: 13px;
}
.evidence-page-link {
border: none;
background: none;
padding: 0;
color: var(--accent);
cursor: pointer;
font: inherit;
}
.field-actions,
.field-edit {
display: flex;
gap: 8px;
}
.field-edit input {
flex: 1;
font: inherit;
padding: 4px 6px;
}
.keyboard-hint {
margin-top: 20px;
color: var(--text-muted);
font-size: 12px;
}
.next-case {
margin-top: 12px;
}
/* Dashboard: the cohort screen has its own scoped surface (`.db`), at the end of this file with the
other section-9 surfaces. */
h2 {
font-size: 22px;
font-weight: 400;
letter-spacing: -0.01em;
margin: 24px 0 8px;
}
/* Export */
.export-summary {
margin-top: 16px;
padding: 12px;
background: var(--bg-alt);
border-radius: 6px;
}
.export-actions {
display: flex;
gap: 8px;
margin-top: 8px;
}
/* Intake (issue #90). Built to the design-guideline skill: light-default,
section 2.1 tokens scoped to this view, two radii total (8px cards, 6px
controls), depth by tonal layering and hairlines rather than resting shadow,
one accent in chrome, semantic color for state only. */
.intake {
--i-bg: var(--bg);
--i-panel: var(--panel);
--i-surface: var(--surface);
--i-surface-2: var(--surface-2);
--i-ink: var(--ink);
--i-ink-muted: var(--ink-muted);
--i-hairline: var(--hairline);
--i-accent: var(--accent);
--i-accent-soft: var(--accent-soft);
--i-pos: var(--pos);
--i-warn: var(--warn);
--i-neg: var(--neg);
--i-r-card: var(--r-card);
--i-r-ctrl: var(--r-ctrl);
color: var(--i-ink);
}
.intake-head {
margin-bottom: 20px;
}
.intake-lede {
max-width: 62ch;
color: var(--i-ink-muted);
margin: 4px 0 0;
}
.intake-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
gap: 16px;
align-items: start;
}
.intake .card {
background: var(--i-surface);
border: 1px solid var(--i-hairline);
border-radius: var(--i-r-card);
padding: 18px 20px;
}
.intake .eyebrow {
margin: 0 0 4px;
font-size: 12px;
font-weight: 600;
letter-spacing: 0.06em;
text-transform: uppercase;
color: var(--i-ink-muted);
}
.intake .card-h {
margin: 0 0 14px;
font-size: 16px;
font-weight: 600;
color: var(--i-ink);
}
/* Segmented control (section 6.3): accent marks the active segment, a hairline
bounds the group. */
.intake .segmented {
display: inline-flex;
border: 1px solid var(--i-hairline);
border-radius: var(--i-r-ctrl);
overflow: hidden;
margin-bottom: 12px;
}
.intake .seg {
border: none;
border-right: 1px solid var(--i-hairline);
border-radius: 0;
background: var(--i-surface);
color: var(--i-ink-muted);
padding: 7px 14px;
font: inherit;
font-size: 13px;
cursor: pointer;
}
.intake .seg:last-child {
border-right: none;
}
.intake .seg.active {
background: var(--i-accent-soft);
color: var(--i-accent);
font-weight: 500;
}
.intake .seg:focus-visible {
outline: 2px solid var(--i-accent);
outline-offset: -2px;
}
/* Inputs (section 6.2): surface fill, hairline, 6px radius, label above. */
.intake .field {
display: flex;
flex-direction: column;
gap: 4px;
margin-top: 12px;
}
.intake .field-label,
.intake .sub-label {
font-size: 12px;
color: var(--i-ink-muted);
font-weight: 500;
}
.intake .field-help {
font-size: 12px;
color: var(--i-ink-muted);
margin: 2px 0 0;
}
.intake .field-error {
font-size: 12px;
color: var(--i-neg);
}
.intake .field-note {
font-size: 12px;
color: var(--i-pos);
margin: 2px 0 0;
}
/* Dictionary upload: the accepted CSV/JSON format, stated before a file is chosen (#183). */
.intake .dict-format {
display: flex;
flex-direction: column;
gap: 6px;
margin-top: 6px;
}
.intake .dict-format-line {
margin: 0;
font-size: 12px;
line-height: 1.55;
color: var(--i-ink-muted);
}
.intake .dict-format-fmt {
display: inline-block;
margin-right: 6px;
font-size: 11px;
font-weight: 600;
letter-spacing: 0.04em;
color: var(--i-ink);
}
.intake .dict-format code {
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
font-size: 11px;
padding: 1px 4px;
background: var(--i-surface-2);
border-radius: 4px;
color: var(--i-ink);
}
.intake input[type="text"],
.intake select {
font: inherit;
font-size: 14px;
padding: 8px 10px;
border: 1px solid var(--i-hairline);
border-radius: var(--i-r-ctrl);
background: var(--i-surface);
color: var(--i-ink);
min-height: 38px;
}
.intake input[type="text"]:focus,
.intake select:focus {
outline: none;
border-color: var(--i-accent);
box-shadow: 0 0 0 2px var(--i-accent-soft);
}
.intake input[type="file"] {
font: inherit;
font-size: 13px;
color: var(--i-ink-muted);
}
.intake .subblock {
margin-top: 16px;
padding-top: 14px;
border-top: 1px solid var(--i-hairline);
}
/* Buttons (section 6.1): one primary per region, accent fill, flat. */
.intake .btn-primary {
background: var(--i-accent);
color: #ffffff;
border: 1px solid var(--i-accent);
border-radius: var(--i-r-ctrl);
padding: 9px 18px;
font: inherit;
font-weight: 500;
min-height: 38px;
cursor: pointer;
}
.intake .btn-primary:hover:not(:disabled) {
filter: brightness(0.94);
}
.intake .btn-primary:disabled {
opacity: 0.45;
cursor: default;
}
.intake .intake-actions {
display: flex;
align-items: center;
gap: 12px;
margin-top: 20px;
}
.intake .run-note,
.intake .run-detail .field-label {
color: var(--i-ink-muted);
font-size: 13px;
}
/* Dictionary preview: a table (section 5.1), hairline row separators, no
vertical rules. */
.intake .dict-preview {
margin-top: 14px;
}
.intake .dict-meta {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 8px;
margin-bottom: 10px;
}
.intake .dict-count {
font-size: 13px;
color: var(--i-ink-muted);
font-variant-numeric: tabular-nums;
}
.intake .table-scroll {
overflow-x: auto;
}
.intake .dict-table {
width: 100%;
border-collapse: collapse;
font-size: 13px;
}
.intake .dict-table th {
text-align: left;
font-size: 12px;
font-weight: 600;
color: var(--i-ink-muted);
padding: 6px 10px;
border-bottom: 1px solid var(--i-hairline);
cursor: default;
}
.intake .dict-table td {
padding: 8px 10px;
border-bottom: 1px solid var(--i-hairline);
vertical-align: top;
}
.intake .dict-table tr:last-child td {
border-bottom: none;
}
.intake .mono {
font-weight: 500;
letter-spacing: 0.02em;
}
.intake .value-tags {
display: flex;
flex-wrap: wrap;
gap: 4px;
}
.intake .open-vocab,
.intake .dash {
color: var(--i-ink-muted);
font-style: italic;
}
/* Dictionary edit (#162): remove a variable from an uploaded dictionary, add it back. */
.intake .dict-remove-col {
width: 32px;
text-align: right;
}
.intake .dict-remove {
display: inline-flex;
align-items: center;
justify-content: center;
width: 24px;
height: 24px;
padding: 0;
border: none;
border-radius: var(--r-ctrl);
background: transparent;
color: var(--i-ink-muted);
cursor: pointer;
font-size: 15px;
}
.intake .dict-remove:hover {
background: var(--i-surface-2);
color: var(--i-neg);
}
.intake .dict-removed {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 8px;
margin-top: 12px;
padding-top: 12px;
border-top: 1px solid var(--i-hairline);
}
.intake .dict-removed-label {
font-size: 12px;
color: var(--i-ink-muted);
}
.intake .dict-restore {
padding: 3px 10px;
border: 1px dashed var(--i-hairline);
border-radius: 999px;
background: var(--i-surface);
color: var(--i-ink-muted);
font: inherit;
font-size: 12px;
cursor: pointer;
}
.intake .dict-restore:hover {
border-style: solid;
border-color: var(--i-accent);
color: var(--i-ink);
}
/* Tags: neutral metadata pills (section 6.3), kept quieter than state chips. */
.intake .tag {
display: inline-block;
padding: 2px 8px;
border-radius: var(--i-r-ctrl);
background: var(--i-surface-2);
color: var(--i-ink-muted);
border: 1px solid var(--i-hairline);
font-size: 12px;
font-weight: 500;
}
.intake .tag-licence {
color: var(--i-ink);
}
.intake .tag.ticket {
font-variant-numeric: tabular-nums;
}
/* State chips: the semantic trio, state only. Enqueued reads as pending. */
.intake .chip {
display: inline-block;
padding: 2px 8px;
border-radius: var(--i-r-ctrl);
font-size: 12px;
font-weight: 500;
}
.intake .chip-pending {
background: var(--i-surface-2);
color: var(--i-warn);
}
/* Follow-up job progress: enqueued/running read as neutral progress, done/failed as the semantic trio. */
.intake .chip-running {
background: var(--i-surface-2);
color: var(--i-accent);
}
.intake .chip-ok {
background: var(--i-surface-2);
color: var(--i-pos);
}
.intake .chip-neg {
background: var(--i-surface-2);
color: var(--i-neg);
}
/* Metric readout (section 5.4): oversized tabular numeral, short label. */
.intake .metric-row {
display: flex;
flex-wrap: wrap;
gap: 28px;
margin: 4px 0 18px;
}
.intake .metric {
display: flex;
flex-direction: column;
}
.intake .metric-value {
font-size: 32px;
font-weight: 500;
font-variant-numeric: tabular-nums;
line-height: 1.1;
color: var(--i-ink);
}
.intake .metric-label {
font-size: 12px;
color: var(--i-ink-muted);
}
.intake .run-summary {
margin-top: 20px;
}
.intake .run-detail {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 16px;
}
.intake .enqueued {
border-top: 1px solid var(--i-hairline);
padding-top: 14px;
}
.intake .enqueued-row {
display: flex;
align-items: center;
gap: 12px;
padding: 8px 0;
border-bottom: 1px solid var(--i-hairline);
font-size: 13px;
}
.intake .enqueued-row:last-child {
border-bottom: none;
}
.intake .job-kind {
font-weight: 500;
color: var(--i-ink);
}
.intake .job-count {
margin-left: auto;
color: var(--i-ink-muted);
font-variant-numeric: tabular-nums;
}
/* States (section 6.4). */
.intake .empty-note {
color: var(--i-ink-muted);
font-size: 13px;
margin: 12px 0 0;
}
.intake .result-empty {
margin-top: 20px;
padding: 14px 16px;
background: var(--i-panel);
border: 1px solid var(--i-hairline);
border-radius: var(--i-r-card);
}
.intake .intake-inline-error {
margin-top: 16px;
padding: 10px 14px;
color: var(--i-neg);
border: 1px solid var(--i-neg);
border-radius: var(--i-r-card);
font-size: 13px;
}
/* Loading skeleton at the true layout (section 6.4). */
.intake .skel-card {
min-height: 120px;
}
.intake .skel {
background: var(--i-surface-2);
border-radius: var(--i-r-ctrl);
}
.intake .skel-line {
height: 12px;
margin-bottom: 10px;
animation: intake-pulse 1.2s ease-in-out infinite;
}
.intake .w40 {
width: 40%;
}
.intake .w60 {
width: 60%;
}
.intake .w70 {
width: 70%;
}
.intake .w80 {
width: 80%;
}
.intake .w90 {
width: 90%;
}
@keyframes intake-pulse {
0%,
100% {
opacity: 1;
}
50% {
opacity: 0.5;
}
}
@media (prefers-reduced-motion: reduce) {
.intake .skel-line {
animation: none;
}
}
/* Registry, the three-pane concept browser (issue #94). Built to the
design-guideline skill section 9.2: light-default, section 2.1 tokens scoped
to this view, two radii (8px cards, 6px controls), depth by tonal layering and
hairlines rather than resting shadow, one accent in chrome. The category hues
are the section 1 chromatic exception: a scoped categorical palette that lives
only on the rail dots and the concept eyebrow, keyed to the rail as its legend,
and never enters chrome. */
.registry {
--rg-bg: var(--bg);
--rg-panel: var(--panel);
--rg-surface: var(--surface);
--rg-surface-2: var(--surface-2);
--rg-ink: var(--ink);
--rg-ink-muted: var(--ink-muted);
--rg-hairline: var(--hairline);
--rg-accent: var(--accent);
--rg-accent-soft: var(--accent-soft);
--rg-warn: var(--warn);
--rg-warn-soft: rgba(176, 133, 31, 0.1);
--rg-neg: var(--neg);
--rg-neg-soft: rgba(192, 57, 43, 0.08);
--rg-r-card: var(--r-card);
--rg-r-ctrl: var(--r-ctrl);
/* histotype is a cyan-blue, deliberately offset from the chrome accent
(--rg-accent / --accent #1b374a) so a data category never reads as the
interactive/selection hue. Do not set it back to the accent value. */
--rg-cat-histotype: #0e8fc4;
--rg-cat-myo: #1f9e8a;
--rg-cat-lvsi: #7c5cbf;
--rg-cat-nodes: #c14f8f;
--rg-cat-molecular: #4f9e3f;
--rg-cat-stage: #b8960a;
color: var(--rg-ink);
}
@media (prefers-color-scheme: dark) {
.registry {
--rg-warn-soft: rgba(224, 169, 59, 0.14);
--rg-neg-soft: rgba(229, 101, 75, 0.14);
--rg-cat-histotype: #5cc4e8;
--rg-cat-myo: #5fc9b8;
--rg-cat-lvsi: #ab8fe0;
--rg-cat-nodes: #e08ab7;
--rg-cat-molecular: #86cf78;
--rg-cat-stage: #dcbb52;
}
}
.registry .rg-head {
margin-bottom: 20px;
}
.registry .rg-eyebrow {
display: flex;
align-items: center;
gap: 6px;
margin: 0 0 4px;
font-size: 12px;
font-weight: 600;
letter-spacing: 0.06em;
text-transform: uppercase;
color: var(--rg-ink-muted);
}
.registry h1 {
font-size: clamp(32px, 4.4vw, 40px);
font-weight: 300;
margin: 0 0 6px;
}
.registry .rg-lede {
max-width: 64ch;
margin: 0;
color: var(--rg-ink-muted);
}
/* Section 3 shell / section 1 spatial contrast: a dense rail beside a calm panel. */
.registry .rg-layout {
display: grid;
grid-template-columns: 300px minmax(0, 1fr);
gap: 24px;
align-items: start;
}
@media (max-width: 840px) {
.registry .rg-layout {
grid-template-columns: 1fr;
}
}
/* Left rail: category accordion. */
.registry .rg-rail {
display: flex;
flex-direction: column;
gap: 8px;
}
.registry .rg-cat {
background: var(--rg-surface);
border: 1px solid var(--rg-hairline);
border-radius: var(--rg-r-card);
overflow: hidden;
}
.registry .rg-cat-head {
display: flex;
align-items: center;
gap: 8px;
width: 100%;
padding: 11px 13px;
border: none;
border-radius: 0;
background: var(--rg-surface);
color: var(--rg-ink);
font: inherit;
cursor: pointer;
text-align: left;
}
.registry .rg-cat-head:hover {
background: var(--rg-surface-2);
}
.registry .rg-cat-head:focus-visible {
outline: 2px solid var(--rg-accent);
outline-offset: -2px;
}
.registry .rg-cat-dot {
width: 10px;
height: 10px;
border-radius: 3px;
flex: none;
}
.registry .rg-cat-name {
flex: 1;
font-size: 13.5px;
font-weight: 600;
}
.registry .rg-chip-open {
font-size: 11px;
font-weight: 500;
padding: 1px 8px;
border-radius: 999px;
background: var(--rg-surface-2);
color: var(--rg-warn);
font-variant-numeric: tabular-nums;
white-space: nowrap;
}
.registry .rg-chevron {
color: var(--rg-ink-muted);
font-size: 14px;
transition: transform 120ms ease-out;
flex: none;
}
.registry .rg-cat.open .rg-chevron {
transform: rotate(90deg);
}
@media (prefers-reduced-motion: reduce) {
.registry .rg-chevron {
transition: none;
}
}
.registry .rg-dim-list {
list-style: none;
margin: 0;
padding: 2px 8px 8px;
display: flex;
flex-direction: column;
gap: 2px;
border-top: 1px solid var(--rg-hairline);
}
.registry .rg-dim-row {
display: flex;
align-items: center;
gap: 8px;
width: 100%;
padding: 7px 8px;
border: none;
border-radius: var(--rg-r-ctrl);
background: transparent;
color: var(--rg-ink);
font: inherit;
font-size: 13px;
cursor: pointer;
text-align: left;
}
.registry .rg-dim-row:hover {
background: var(--rg-surface-2);
}
.registry .rg-dim-row:focus-visible {
outline: 2px solid var(--rg-accent);
outline-offset: -2px;
}
/* Selection: accent-soft fill with a 2px accent left-edge (section 5.1). */
.registry .rg-dim-row.selected {
background: var(--rg-accent-soft);
box-shadow: inset 2px 0 0 var(--rg-accent);
font-weight: 500;
}
.registry .rg-dim-name {
flex: 1;
}
.registry .rg-dim-open {
font-size: 11px;
font-weight: 600;
letter-spacing: 0.04em;
text-transform: uppercase;
color: var(--rg-warn);
}
.registry .rg-rail-note {
margin: 4px 2px 0;
font-size: 12px;
line-height: 1.4;
color: var(--rg-ink-muted);
}
/* Neutral tag (section 6.3): quieter than a state chip. */
.registry .rg-tag {
display: inline-block;
padding: 2px 8px;
border-radius: var(--rg-r-ctrl);
background: var(--rg-surface-2);
color: var(--rg-ink-muted);
border: 1px solid var(--rg-hairline);
font-size: 12px;
font-weight: 500;
white-space: nowrap;
}
.registry .rg-tag-kind {
font-size: 10.5px;
padding: 1px 6px;
letter-spacing: 0.02em;
}
.registry .rg-tag-standard {
color: var(--rg-accent);
background: var(--rg-accent-soft);
border-color: transparent;
font-weight: 600;
}
/* Right panel: header card, tab strip, facet body. */
.registry .rg-panel {
min-width: 0;
}
.registry .rg-header-card {
background: var(--rg-surface);
border: 1px solid var(--rg-hairline);
border-radius: var(--rg-r-card);
padding: 16px 18px;
margin-bottom: 14px;
}
.registry .rg-title {
font-size: 24px;
font-weight: 700;
margin: 0 0 4px;
}
.registry .rg-concept-id {
font-size: 12.5px;
font-weight: 500;
letter-spacing: 0.02em;
color: var(--rg-ink-muted);
}
.registry .rg-attr-chips {
display: flex;
flex-wrap: wrap;
gap: 6px;
margin-top: 12px;
}
/* Tab strip (section 9.2): fixed set, active tab accent + 2px underline. */
.registry .rg-tabs {
display: flex;
flex-wrap: wrap;
gap: 2px;
border-bottom: 1px solid var(--rg-hairline);
margin-bottom: 14px;
}
.registry .rg-tab {
border: none;
background: none;
padding: 9px 13px;
margin-bottom: -1px;
border-bottom: 2px solid transparent;
color: var(--rg-ink-muted);
font: inherit;
font-size: 13px;
font-weight: 500;
cursor: pointer;
}
.registry .rg-tab:hover {
color: var(--rg-ink);
}
.registry .rg-tab.active {
color: var(--rg-accent);
border-bottom-color: var(--rg-accent);
}
.registry .rg-tab:focus-visible {
outline: 2px solid var(--rg-accent);
outline-offset: -2px;
}
/* Facet body: one card of labeled sub-blocks (section 9.2). */
.registry .rg-facet {
background: var(--rg-surface);
border: 1px solid var(--rg-hairline);
border-radius: var(--rg-r-card);
padding: 16px 18px;
}
.registry .rg-facet p {
margin: 0 0 8px;
font-size: 14px;
line-height: 1.5;
}
.registry .rg-facet p:last-child {
margin-bottom: 0;
}
.registry .rg-block {
padding: 12px 0;
border-top: 1px solid var(--rg-hairline);
}
.registry .rg-block:first-child {
padding-top: 0;
border-top: none;
}
.registry .rg-block-label,
.registry .rg-sub-label {
margin: 0 0 6px;
font-size: 11.5px;
font-weight: 600;
letter-spacing: 0.05em;
text-transform: uppercase;
color: var(--rg-ink-muted);
}
.registry .rg-sub-label {
margin-top: 10px;
}
.registry .rg-muted {
color: var(--rg-ink-muted);
}
.registry .rg-pointer {
margin-top: 12px;
font-size: 12.5px;
}
/* The "how it is decided (not extracted)" note: a distinct footer block, one
tonal step down (section 9.2). */
.registry .rg-decided-note {
margin-top: 12px;
padding: 12px 14px;
background: var(--rg-surface-2);
border-radius: var(--rg-r-ctrl);
}
.registry .rg-decided-note p:last-child {
color: var(--rg-ink-muted);
}
.registry .rg-rule-line {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 4px;
}
/* Concept cross-references: tertiary text buttons in accent (section 6.1). */
.registry .rg-ref-list {
list-style: none;
margin: 0;
padding: 0;
display: flex;
flex-wrap: wrap;
gap: 6px;
}
.registry .rg-ref {
border: 1px solid var(--rg-hairline);
background: var(--rg-accent-soft);
color: var(--rg-accent);
border-radius: 999px;
padding: 4px 11px;
font: inherit;
font-size: 12.5px;
font-weight: 500;
cursor: pointer;
}
.registry .rg-ref:hover {
text-decoration: underline;
}
.registry .rg-ref:focus-visible {
outline: 2px solid var(--rg-accent);
outline-offset: 1px;
}
.registry .rg-link {
color: var(--rg-accent);
}
/* Evidence bubble (section 7): recessed, a 2px accent left-edge marking cited
material rather than authored prose. */
.registry .rg-source-title {
font-weight: 500;
}
.registry .rg-quote {
margin: 0 0 8px;
padding: 10px 12px;
background: var(--rg-surface-2);
border-left: 2px solid var(--rg-accent);
border-radius: var(--rg-r-ctrl);
font-size: 13.5px;
}
.registry .rg-provenance {
margin: 0 0 6px;
}
/* Decision card. */
.registry .rg-decision {
border: 1px solid var(--rg-hairline);
border-radius: var(--rg-r-ctrl);
padding: 11px 13px;
margin-bottom: 10px;
}
.registry .rg-decision:last-child {
margin-bottom: 0;
}
.registry .rg-decision-q {
font-weight: 600;
font-size: 13.5px;
}
.registry .rg-decision-meta {
font-size: 12px;
color: var(--rg-ink-muted);
font-variant-numeric: tabular-nums;
}
/* Crosswalk facet (#59): the cross-standard edges for a concept's clinical
dimension. Neutral rows carry agreement, narrowing, and broadening; a conflict
row takes the negative tone (soft fill, a 3px left edge, a red kind pill) so a
disagreement between two standards is the first thing the eye lands on. */
.registry .rg-xw-list {
display: flex;
flex-direction: column;
gap: 8px;
}
.registry .rg-xw {
border: 1px solid var(--rg-hairline);
border-radius: var(--rg-r-ctrl);
padding: 10px 12px;
background: var(--rg-surface);
}
.registry .rg-xw-conflict {
border-color: var(--rg-neg);
background: var(--rg-neg-soft);
box-shadow: inset 3px 0 0 var(--rg-neg);
}
.registry .rg-xw-flow {
display: flex;
flex-wrap: wrap;
align-items: baseline;
gap: 6px 10px;
}
.registry .rg-xw-side {
display: inline-flex;
align-items: baseline;
gap: 6px;
min-width: 0;
}
.registry .rg-xw-std {
font-size: 11px;
font-weight: 700;
letter-spacing: 0.03em;
text-transform: uppercase;
color: var(--rg-ink-muted);
white-space: nowrap;
}
.registry .rg-xw-val {
font-size: 13.5px;
color: var(--rg-ink);
}
.registry .rg-xw-arrow {
color: var(--rg-ink-muted);
font-size: 14px;
}
.registry .rg-xw-kind {
display: inline-block;
padding: 1px 8px;
border-radius: 999px;
background: var(--rg-surface-2);
color: var(--rg-ink-muted);
border: 1px solid var(--rg-hairline);
font-size: 11px;
font-weight: 600;
white-space: nowrap;
}
.registry .rg-xw-kind.conflict {
color: var(--rg-neg);
background: var(--rg-neg-soft);
border-color: var(--rg-neg);
}
.registry .rg-xw-uncertain {
display: inline-block;
padding: 1px 7px;
border-radius: 999px;
border: 1px dashed var(--rg-hairline);
color: var(--rg-warn);
font-size: 10.5px;
font-weight: 600;
white-space: nowrap;
}
.registry .rg-xw-rationale {
margin: 8px 0 0;
font-size: 13px;
line-height: 1.5;
color: var(--rg-ink);
}
/* States (section 6.4). */
.registry .rg-empty-note,
.registry .rg-inline-error {
font-size: 13.5px;
}
.registry .rg-inline-error {
padding: 10px 14px;
color: var(--rg-neg);
border: 1px solid var(--rg-neg);
border-radius: var(--rg-r-card);
}
.registry .rg-empty-note {
color: var(--rg-ink-muted);
}
/* Loading skeleton at the true three-pane layout (section 6.4). */
.registry .rg-skel {
background: var(--rg-surface-2);
border-radius: var(--rg-r-ctrl);
animation: rg-pulse 1.2s ease-in-out infinite;
}
.registry .rg-skel-row {
height: 42px;
margin-bottom: 8px;
}
.registry .rg-skel-line {
height: 12px;
margin-bottom: 10px;
}
.registry .rg-skel-line.w40 {
width: 40%;
}
.registry .rg-skel-line.w60 {
width: 60%;
}
.registry .rg-skel-line.w70 {
width: 70%;
}
.registry .rg-skel-line.w80 {
width: 80%;
}
.registry .rg-skel-line.w90 {
width: 90%;
}
@keyframes rg-pulse {
0%,
100% {
opacity: 1;
}
50% {
opacity: 0.5;
}
}
@media (prefers-reduced-motion: reduce) {
.registry .rg-skel {
animation: none;
}
}
/* ---------------------------------------------------------------------------
* Crosswalk mapping-review surface (#95, #103; design-guideline sections 9.1,
* 3; clinical-review-interface skill). Light-default; section 2.1 tokens scoped
* to this view. The categorical palette is scoped to the connectors, the concept
* dots, and the top legend; chrome keeps the single accent. Semantic color marks
* state only. Two radii, depth by tonal layering and hairlines.
*
* Layout: aligned source/concept pairs with a straight connector between, an
* unresolved lane, and a calm inspector beside the dense diagram (section 3
* two-pane). Connectors are drawn once and only opacity and stroke-weight change
* on selection; prefers-reduced-motion drops the transitions.
* ------------------------------------------------------------------------- */
.mapping {
--mp-bg: var(--bg);
--mp-panel: var(--panel);
--mp-surface: var(--surface);
--mp-surface-2: var(--surface-2);
--mp-ink: var(--ink);
--mp-ink-muted: var(--ink-muted);
--mp-hairline: var(--hairline);
--mp-accent: var(--accent);
--mp-accent-soft: var(--accent-soft);
--mp-pos: var(--pos);
--mp-pos-soft: rgba(19, 122, 76, 0.1);
--mp-warn: var(--warn);
--mp-warn-soft: rgba(176, 133, 31, 0.12);
--mp-neg: var(--neg);
--mp-neg-soft: rgba(192, 57, 43, 0.09);
--mp-r-card: var(--r-card);
--mp-r-ctrl: var(--r-ctrl);
--mp-gutter: 92px;
/* histotype is a cyan-blue, deliberately offset from the chrome accent
(--mp-accent / --accent #1b374a) so a connector or concept dot never reads
as the interactive/selection hue. Do not set it back to the accent value. */
--mp-cat-histotype: #0e8fc4;
--mp-cat-myo: #1f9e8a;
--mp-cat-lvsi: #7c5cbf;
--mp-cat-nodes: #c14f8f;
--mp-cat-molecular: #4f9e3f;
--mp-cat-stage: #b8960a;
color: var(--mp-ink);
}
@media (prefers-color-scheme: dark) {
.mapping {
--mp-pos-soft: rgba(61, 207, 142, 0.14);
--mp-warn-soft: rgba(224, 169, 59, 0.16);
--mp-neg-soft: rgba(229, 101, 75, 0.16);
--mp-cat-histotype: #5cc4e8;
--mp-cat-myo: #5fc9b8;
--mp-cat-lvsi: #ab8fe0;
--mp-cat-nodes: #e08ab7;
--mp-cat-molecular: #86cf78;
--mp-cat-stage: #dcbb52;
}
}
.mapping .mp-head {
margin-bottom: 14px;
}
.mapping .mp-eyebrow {
margin: 0 0 4px;
font-size: 12px;
font-weight: 600;
letter-spacing: 0.06em;
text-transform: uppercase;
color: var(--mp-ink-muted);
}
.mapping h1 {
font-size: clamp(32px, 4.4vw, 40px);
font-weight: 300;
margin: 0 0 6px;
}
.mapping .mp-lede {
max-width: 72ch;
margin: 0;
color: var(--mp-ink-muted);
line-height: 1.5;
}
/* Session identity: from the per-user login, shown read-only as "Confirming as <name> (<role>)". */
.mapping .mp-dict-bar {
display: flex;
align-items: center;
gap: 10px;
flex-wrap: wrap;
margin: 14px 0;
padding: 8px 14px;
border: 1px solid var(--mp-hairline);
border-radius: var(--mp-r-card);
background: var(--mp-panel);
font-size: 13px;
}
.mapping .mp-dict-label {
font-size: 12px;
color: var(--mp-ink-muted);
}
.mapping .mp-dict-note {
flex-basis: 100%;
font-size: 12px;
color: var(--mp-ink-muted);
}
.mapping .mp-identity-strip {
display: flex;
align-items: center;
gap: 10px;
flex-wrap: wrap;
margin: 14px 0;
padding: 8px 14px;
border: 1px solid var(--mp-hairline);
border-radius: var(--mp-r-card);
background: var(--mp-panel);
font-size: 13px;
}
.mapping .mp-identity-label {
font-size: 12px;
color: var(--mp-ink-muted);
}
.mapping .mp-identity-who {
display: flex;
align-items: center;
gap: 8px;
font-weight: 600;
}
.mapping .mp-identity-note {
flex-basis: 100%;
font-size: 12px;
color: var(--mp-ink-muted);
}
/* Status band. */
.mapping .mp-banner {
display: flex;
align-items: center;
gap: 10px;
flex-wrap: wrap;
padding: 10px 14px;
margin: 12px 0;
border: 1px solid var(--mp-hairline);
border-radius: var(--mp-r-card);
background: var(--mp-panel);
font-size: 13px;
color: var(--mp-ink-muted);
}
.mapping .mp-banner-ok {
border-color: var(--mp-pos);
background: var(--mp-pos-soft);
}
/* Top legend: the categorical palette keyed to a visible legend, plus the two
* attention states. */
.mapping .mp-legend {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 8px 16px;
margin-bottom: 16px;
}
.mapping .mp-legend-item {
display: flex;
align-items: center;
gap: 6px;
font-size: 12px;
color: var(--mp-ink-muted);
}
.mapping .mp-legend-swatch {
width: 10px;
height: 10px;
border-radius: 3px;
flex: none;
}
.mapping .mp-legend-sep {
width: 1px;
height: 18px;
background: var(--mp-hairline);
}
/* The review-state filter bar: where the work is, and a way to focus the frame. Neutral UI colours only,
so it never competes with the category (connector) colours. */
.mapping .mp-filter {
display: flex;
gap: 6px;
flex-wrap: wrap;
margin-bottom: 16px;
}
.mapping .mp-filter-btn {
display: inline-flex;
align-items: center;
gap: 7px;
padding: 5px 12px;
font-size: 12.5px;
font-weight: 600;
color: var(--mp-ink-muted);
background: var(--mp-surface);
border: 1px solid var(--mp-hairline);
border-radius: 999px;
cursor: pointer;
transition: border-color 120ms ease-out, background 120ms ease-out;
}
.mapping .mp-filter-btn:hover {
border-color: color-mix(in srgb, var(--mp-ink) 22%, transparent);
}
.mapping .mp-filter-btn.active {
color: var(--mp-ink);
background: var(--mp-surface-2);
border-color: color-mix(in srgb, var(--mp-ink) 32%, transparent);
}
.mapping .mp-filter-n {
font-size: 11px;
font-weight: 700;
color: var(--mp-ink-muted);
background: var(--mp-surface-2);
padding: 0 6px;
border-radius: 999px;
min-width: 18px;
text-align: center;
}
.mapping .mp-filter-btn.active .mp-filter-n {
color: var(--mp-surface);
background: var(--mp-ink);
}
.mapping .mp-filter-neg .mp-filter-n {
color: var(--mp-neg);
background: color-mix(in srgb, var(--mp-neg) 14%, var(--mp-surface-2));
}
/* The collapsed "N more confirmed" row, so a long inventory reads as a short frame. */
/* The filter bar and the running "fields available" count share one row. */
.mapping .mp-filter-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
flex-wrap: wrap;
margin-bottom: 16px;
}
.mapping .mp-filter-row .mp-filter {
margin-bottom: 0;
}
/* Fields available: the primitive (extractable) columns the confirmed mapping yields. It flashes and shows
before then after when a decision raises it, so recovering fields reads as a gain, not a silent change. */
.mapping .mp-avail {
display: inline-flex;
align-items: baseline;
gap: 8px;
padding: 5px 13px;
border-radius: 999px;
border: 1px solid var(--mp-hairline);
background: var(--mp-surface);
white-space: nowrap;
transition: border-color 200ms ease-out;
}
.mapping .mp-avail-label {
font-size: 12px;
font-weight: 600;
color: var(--mp-ink-muted);
}
.mapping .mp-avail-count {
display: inline-flex;
align-items: baseline;
gap: 6px;
font-size: 15px;
font-weight: 700;
color: var(--mp-ink);
font-variant-numeric: tabular-nums;
}
.mapping .mp-avail-from {
color: var(--mp-ink-muted);
text-decoration: line-through;
text-decoration-color: color-mix(in srgb, var(--mp-ink-muted) 55%, transparent);
}
.mapping .mp-avail-arrow {
color: #0f766e;
font-size: 12px;
font-weight: 700;
}
.mapping .mp-avail-to {
color: #0f766e;
}
.mapping .mp-avail.bumped {
border-color: color-mix(in srgb, #0f766e 55%, transparent);
animation: mp-avail-flash 2.8s ease-out;
}
@keyframes mp-avail-flash {
0% {
background: color-mix(in srgb, #0f766e 28%, var(--mp-surface));
}
70% {
background: color-mix(in srgb, #0f766e 18%, var(--mp-surface));
}
100% {
background: var(--mp-surface);
}
}
/* A confirmed split shows its recovered primitive fields under the concept card. */
.mapping .mp-pair-components {
grid-column: 3;
list-style: none;
margin: 7px 0 0;
padding: 2px 0 2px 14px;
border-left: 2px solid color-mix(in srgb, #0f766e 42%, transparent);
display: flex;
flex-direction: column;
gap: 6px;
}
.mapping .mp-pair-components li {
display: flex;
align-items: center;
gap: 8px;
font-size: 12.5px;
color: var(--mp-ink);
}
.mapping .mp-pair-comp-label {
font-weight: 600;
}
/* The two-pane workspace: dense diagram left, calm inspector right (section 3). */
.mapping .mp-workspace {
display: flex;
gap: 20px;
align-items: flex-start;
}
.mapping .mp-diagram {
flex: 1;
min-width: 0;
}
/* The aligned pairs: each concept sits directly across from its source field so
* the connector runs straight. */
.mapping .mp-pairs {
position: relative;
display: flex;
flex-direction: column;
gap: 10px;
}
.mapping .mp-pairs-head {
display: grid;
grid-template-columns: 1fr var(--mp-gutter) 1fr;
align-items: end;
margin-bottom: 2px;
}
.mapping .mp-col-label {
margin: 0;
font-size: 11px;
font-weight: 700;
letter-spacing: 0.07em;
text-transform: uppercase;
color: var(--mp-ink-muted);
}
.mapping .mp-col-label-right {
grid-column: 3;
}
.mapping .mp-col-sub {
font-weight: 500;
letter-spacing: 0;
text-transform: none;
}
.mapping .mp-connectors {
position: absolute;
inset: 0;
z-index: 0;
overflow: visible;
pointer-events: none;
}
.mapping .mp-connector {
fill: none;
stroke-width: 1.5;
opacity: 0.7;
transition: opacity 120ms ease-out, stroke-width 120ms ease-out;
}
.mapping .mp-arrowhead {
opacity: 0.7;
transition: opacity 120ms ease-out;
}
.mapping .mp-conn.selected .mp-connector {
opacity: 1;
stroke-width: 2.4;
}
.mapping .mp-conn.selected .mp-arrowhead {
opacity: 1;
}
.mapping .mp-pair {
display: grid;
grid-template-columns: 1fr var(--mp-gutter) 1fr;
align-items: center;
position: relative;
z-index: 1;
}
/* Cards are buttons: click an edge to edit it. Reset the button chrome. */
.mapping .mp-card {
display: block;
width: 100%;
text-align: left;
font: inherit;
color: var(--mp-ink);
position: relative;
z-index: 1;
background: var(--mp-surface);
border: 1px solid var(--mp-hairline);
border-radius: var(--mp-r-card);
padding: 11px 13px;
cursor: pointer;
transition: border-color 120ms ease-out, background 120ms ease-out;
}
.mapping .mp-source-card {
grid-column: 1;
}
.mapping .mp-concept-card {
grid-column: 3;
}
.mapping .mp-card:hover {
border-color: var(--mp-accent);
box-shadow: 0 3px 12px rgba(16, 24, 40, 0.09);
}
.mapping .mp-card.selected {
border-color: var(--mp-accent);
background: var(--mp-accent-soft);
box-shadow: inset 3px 0 0 0 var(--mp-accent);
}
/* A "Review" affordance that fades in on the clickable source card, so it reads as an action, not just
information. It sits bottom-right, clear of the relation tag on the left. */
.mapping .mp-source-card::after {
content: "Review";
position: absolute;
bottom: 11px;
right: 13px;
font-size: 10px;
font-weight: 700;
letter-spacing: 0.06em;
text-transform: uppercase;
color: var(--mp-accent);
opacity: 0;
transition: opacity 120ms ease-out;
pointer-events: none;
}
.mapping .mp-source-card:hover::after,
.mapping .mp-source-card.selected::after {
opacity: 1;
}
.mapping .mp-source-card.selected::after {
content: "Reviewing";
}
.mapping .mp-card-row {
display: flex;
align-items: center;
gap: 8px;
}
.mapping .mp-card-title {
flex: 1;
font-size: 13.5px;
font-weight: 600;
}
.mapping .mp-card-meta {
display: flex;
align-items: center;
gap: 8px;
margin: 7px 0 0;
font-size: 12px;
flex-wrap: wrap;
}
.mapping .mp-card-note {
margin: 7px 0 0;
font-size: 12px;
line-height: 1.45;
color: var(--mp-ink-muted);
}
.mapping .mp-cat-dot {
width: 10px;
height: 10px;
border-radius: 3px;
flex: none;
}
.mapping .mp-concept-card .mp-code {
display: inline-block;
margin-top: 7px;
}
/* The unresolved lane: genuinely unresolved edges, separated. */
.mapping .mp-lane {
margin-top: 22px;
padding: 14px 16px;
border: 1px solid var(--mp-hairline);
border-radius: var(--mp-r-card);
background: var(--mp-panel);
}
.mapping .mp-lane-head {
display: flex;
align-items: center;
gap: 8px;
margin: 0 0 4px;
font-size: 12px;
font-weight: 700;
letter-spacing: 0.05em;
text-transform: uppercase;
color: var(--mp-ink-muted);
}
.mapping .mp-lane-note {
margin: 0 0 12px;
font-size: 12px;
color: var(--mp-ink-muted);
}
.mapping .mp-lane-cards {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
gap: 10px;
}
.mapping .mp-unresolved-card {
border-left: 3px solid var(--mp-neg);
}
/* Tags and semantic state chips. */
.mapping .mp-tag {
display: inline-block;
font-size: 11px;
font-weight: 500;
padding: 1px 8px;
border-radius: var(--mp-r-ctrl);
background: var(--mp-surface-2);
color: var(--mp-ink-muted);
white-space: nowrap;
}
.mapping .mp-state-chip {
font-size: 11px;
font-weight: 650;
padding: 2px 9px;
border-radius: 999px;
white-space: nowrap;
border: 1px solid transparent;
}
/* Stronger fills and darker text than the soft tokens, so the states stay legible in a recorded and
compressed demo. */
.mapping .mp-chip-pos {
background: color-mix(in srgb, var(--mp-pos) 16%, var(--mp-surface));
border-color: color-mix(in srgb, var(--mp-pos) 32%, transparent);
color: color-mix(in srgb, var(--mp-pos) 82%, black);
}
.mapping .mp-chip-warn {
background: color-mix(in srgb, var(--mp-warn) 18%, var(--mp-surface));
border-color: color-mix(in srgb, var(--mp-warn) 34%, transparent);
color: color-mix(in srgb, var(--mp-warn) 78%, black);
}
.mapping .mp-chip-neg {
background: color-mix(in srgb, var(--mp-neg) 16%, var(--mp-surface));
border-color: color-mix(in srgb, var(--mp-neg) 32%, transparent);
color: color-mix(in srgb, var(--mp-neg) 82%, black);
}
.mapping .mp-chip-neutral {
background: var(--mp-surface-2);
border-color: var(--mp-hairline, rgba(20, 24, 34, 0.12));
color: var(--mp-ink-muted, #6b7280);
}
/* "confirmed" is a settled state, not a category: a filled neutral ink pill, so it never reads as one of
the category hues on the connectors. */
.mapping .mp-chip-done {
background: color-mix(in srgb, var(--mp-ink) 86%, transparent);
border-color: transparent;
color: var(--mp-surface, #ffffff);
}
.mapping .mp-count-chip {
font-size: 11px;
font-weight: 600;
padding: 1px 8px;
border-radius: 999px;
background: var(--mp-neg);
color: #fff;
font-variant-numeric: tabular-nums;
}
.mapping .mp-code {
font-family: ui-monospace, "SF Mono", Menlo, monospace;
font-size: 12px;
color: var(--mp-ink);
overflow-wrap: anywhere;
min-width: 0;
}
.mapping .mp-muted {
color: var(--mp-ink-muted);
}
/* Buttons: one primary accent per region, a secondary ghost, a tertiary text. */
.mapping .mp-btn,
.mapping .mp-btn-secondary,
.mapping .mp-btn-ghost {
font: inherit;
font-size: 13px;
font-weight: 600;
border-radius: var(--mp-r-ctrl);
cursor: pointer;
}
.mapping .mp-btn {
height: 34px;
padding: 0 16px;
border: none;
background: var(--mp-accent);
color: #fff;
}
.mapping .mp-btn:hover {
filter: brightness(1.06);
}
.mapping .mp-btn-secondary {
height: 32px;
padding: 0 12px;
border: 1px solid var(--mp-hairline);
background: var(--mp-surface);
color: var(--mp-ink);
}
.mapping .mp-btn-secondary:hover {
border-color: var(--mp-accent);
color: var(--mp-accent);
}
.mapping .mp-btn-ghost {
height: 30px;
padding: 0 8px;
border: none;
background: none;
color: var(--mp-accent);
}
.mapping .mp-btn-ghost:hover {
text-decoration: underline;
}
.mapping .mp-btn:disabled,
.mapping .mp-btn-secondary:disabled,
.mapping .mp-btn-ghost:disabled {
opacity: 0.5;
cursor: default;
filter: none;
}
/* Inputs. */
.mapping .mp-input {
height: 36px;
padding: 0 10px;
border: 1px solid var(--mp-hairline);
border-radius: var(--mp-r-ctrl);
background: var(--mp-surface);
color: var(--mp-ink);
font: inherit;
font-size: 13px;
font-weight: 500;
}
.mapping .mp-input:focus {
outline: none;
border-color: var(--mp-accent);
box-shadow: 0 0 0 2px var(--mp-accent-soft);
}
.mapping .mp-select {
padding-right: 6px;
}
.mapping .mp-textarea {
height: auto;
padding: 8px 10px;
line-height: 1.4;
resize: vertical;
}
/* The inspector: edit any edge, and chat with the engine about it. */
.mapping .mp-inspector {
flex: none;
width: 380px;
position: sticky;
top: 16px;
max-height: calc(100vh - 32px);
overflow-y: auto;
border: 1px solid var(--mp-hairline);
border-radius: var(--mp-r-card);
background: var(--mp-surface);
padding: 16px;
}
.mapping .mp-inspector-empty {
background: var(--mp-panel);
}
.mapping .mp-inspector-empty .mp-empty-note {
margin: 0;
}
.mapping .mp-inspector-head {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 10px;
margin-bottom: 12px;
}
.mapping .mp-inspector-title {
margin: 0;
font-size: 16px;
font-weight: 600;
}
.mapping .mp-registry-link {
display: inline-block;
margin-top: 5px;
font-size: 12px;
font-weight: 500;
color: var(--mp-accent);
text-decoration: none;
}
.mapping .mp-registry-link:hover {
text-decoration: underline;
}
.mapping .mp-drafted-note {
margin: 0 0 12px;
padding: 10px 12px;
border-radius: var(--mp-r-ctrl);
background: var(--mp-surface-2);
border-left: 2px solid var(--mp-accent);
font-size: 12.5px;
line-height: 1.45;
color: var(--mp-ink-muted);
}
.mapping .mp-sub-label {
margin: 0 0 8px;
font-size: 11px;
font-weight: 700;
letter-spacing: 0.05em;
text-transform: uppercase;
color: var(--mp-ink-muted);
}
.mapping .mp-suggestions {
margin-bottom: 16px;
}
.mapping .mp-candidate {
display: flex;
align-items: center;
gap: 10px;
padding: 10px;
border: 1px solid var(--mp-hairline);
border-radius: var(--mp-r-ctrl);
background: var(--mp-surface-2);
margin-bottom: 8px;
}
.mapping .mp-candidate-body {
flex: 1;
min-width: 0;
}
.mapping .mp-candidate-label {
margin: 0 0 4px;
font-size: 13px;
font-weight: 600;
}
.mapping .mp-candidate-tradeoff {
margin: 0;
font-size: 12px;
line-height: 1.4;
color: var(--mp-ink-muted);
}
.mapping .mp-editor-fields {
display: flex;
flex-direction: column;
gap: 10px;
padding-top: 14px;
border-top: 1px solid var(--mp-hairline);
}
.mapping .mp-field {
display: flex;
flex-direction: column;
gap: 4px;
}
.mapping .mp-field-label {
font-size: 12px;
color: var(--mp-ink-muted);
}
.mapping .mp-editor-hint {
margin: 10px 0 0;
font-size: 12px;
color: var(--mp-warn);
}
.mapping .mp-editor-actions {
display: flex;
gap: 8px;
margin-top: 14px;
flex-wrap: wrap;
}
.mapping .mp-provenance {
margin: 12px 0 0;
font-size: 12px;
font-weight: 500;
color: var(--mp-ink-muted);
font-variant-numeric: tabular-nums;
}
/* The edge chat co-pilot: a dark Claude block with white bubbles, raised in the visual hierarchy. */
.mapping .mp-chat {
margin-top: 20px;
padding: 16px 16px 14px;
border-radius: 14px;
background: var(--block-bg);
border: 1px solid var(--block-hairline);
color: var(--block-ink);
min-width: 0;
}
.mapping .mp-chat .mp-sub-label {
margin: 0 0 12px;
font-size: 15px;
font-weight: 600;
letter-spacing: 0;
text-transform: none;
color: var(--block-ink);
}
.mapping .mp-chat-thread {
display: flex;
flex-direction: column;
gap: 12px;
margin-bottom: 12px;
/* The thread scrolls within itself so a long exchange never overflows the panel or pushes the input
out of reach; the input below stays pinned. overscroll-behavior keeps a thread scroll from chaining
into the inspector scroll behind it. */
max-height: 42vh;
overflow-y: auto;
overscroll-behavior: contain;
}
.mapping .mp-chat-hint {
margin: 0;
font-size: 13.5px;
color: var(--block-ink-muted);
line-height: 1.5;
}
.mapping .mp-chat-user {
align-self: flex-end;
max-width: 90%;
margin: 0;
padding: 10px 14px;
border-radius: 14px 14px 4px 14px;
background: #ffffff;
color: #1c2b36;
font-size: 15px;
line-height: 1.5;
overflow-wrap: anywhere;
}
.mapping .mp-chat-answer {
align-self: flex-start;
max-width: 94%;
padding: 12px 14px;
border: none;
border-radius: 14px 14px 14px 4px;
background: #ffffff;
color: #1c2b36;
min-width: 0;
}
.mapping .mp-chat-text {
margin: 0;
font-size: 15px;
line-height: 1.55;
color: #1c2b36;
overflow-wrap: anywhere;
}
.mapping .mp-chat-suggestion {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 8px;
margin-top: 10px;
padding-top: 10px;
border-top: 1px solid var(--mp-hairline);
}
.mapping .mp-chat-proposal-summary {
margin: 0;
font-size: 12px;
font-weight: 600;
line-height: 1.45;
color: var(--mp-ink);
}
.mapping .mp-chat-proposal-target {
display: flex;
align-items: center;
gap: 8px;
flex-wrap: wrap;
}
.mapping .mp-chat-components {
margin: 0;
padding: 0 0 0 10px;
list-style: none;
display: flex;
flex-direction: column;
gap: 6px;
border-left: 2px solid var(--mp-hairline);
}
.mapping .mp-chat-components li {
display: flex;
align-items: center;
gap: 8px;
flex-wrap: wrap;
}
.mapping .mp-split-components {
margin-top: 12px;
}
.mapping .mp-chat-form {
display: flex;
gap: 8px;
/* The input grows with its content; keep the Ask button pinned to the bottom edge as it does. */
align-items: flex-end;
}
.mapping .mp-chat-form .mp-input {
flex: 1;
min-width: 0;
background: #ffffff;
color: #1c2b36;
border: 1px solid var(--block-hairline);
}
/* An auto-growing textarea: one line to start, grows with the message (see the useLayoutEffect that sets
its height from scrollHeight), then scrolls past a cap so it never runs off the panel. */
.mapping .mp-chat-form textarea.mp-input {
height: auto;
min-height: 36px;
max-height: 132px;
padding: 8px 10px;
line-height: 1.4;
resize: none;
overflow-y: auto;
box-sizing: border-box;
}
.mapping .mp-chat-form .mp-input::placeholder {
color: #8a97a3;
}
.mapping .mp-chat-form .mp-btn-secondary {
background: var(--block-accent);
color: var(--block-bg);
border: none;
font-weight: 600;
}
/* Decision ledger + confirmed field set tables. */
.mapping .mp-ledger,
.mapping .mp-fieldset {
margin-top: 28px;
}
.mapping .mp-section-title {
font-size: 18px;
font-weight: 600;
margin: 0 0 6px;
}
.mapping .mp-ledger .mp-lede {
margin-bottom: 12px;
}
.mapping .mp-answer-cell {
display: inline-flex;
align-items: center;
gap: 6px;
}
.mapping .mp-ledger-role {
color: var(--mp-ink-muted);
}
.mapping .mp-table-wrap {
overflow-x: auto;
border: 1px solid var(--mp-hairline);
border-radius: var(--mp-r-card);
}
.mapping .mp-table {
width: 100%;
border-collapse: collapse;
font-size: 13px;
}
.mapping .mp-table th {
text-align: left;
padding: 9px 12px;
font-size: 12px;
font-weight: 600;
color: var(--mp-ink-muted);
background: var(--mp-panel);
border-bottom: 1px solid var(--mp-hairline);
white-space: nowrap;
}
.mapping .mp-table td {
padding: 9px 12px;
border-bottom: 1px solid var(--mp-hairline);
vertical-align: middle;
}
.mapping .mp-table tr:last-child td {
border-bottom: none;
}
.mapping .mp-inline-error {
margin: 10px 0 0;
padding: 8px 12px;
border: 1px solid var(--mp-neg);
border-radius: var(--mp-r-ctrl);
background: var(--mp-neg-soft);
color: var(--mp-neg);
font-size: 12.5px;
}
/* The chat's inline error sits on the dark block panel, where the soft-red light-surface fill above
disappears. Give it a white surface so the message reads, and balanced vertical margins so it is not
cramped between the thread and the input. */
.mapping .mp-chat .mp-inline-error {
margin: 12px 0;
background: #ffffff;
}
.mapping .mp-empty-note {
padding: 14px 16px;
border: 1px dashed var(--mp-hairline);
border-radius: var(--mp-r-card);
color: var(--mp-ink-muted);
font-size: 13px;
}
/* State: loading skeleton at the true aligned-pairs layout. */
.mapping .mp-skel {
background: var(--mp-surface-2);
border-radius: var(--mp-r-card);
animation: mp-pulse 1.4s ease-in-out infinite;
}
.mapping .mp-skel-card {
height: 62px;
}
.mapping .mp-pair .mp-skel-card:first-child {
grid-column: 1;
}
.mapping .mp-pair .mp-skel-card:last-child {
grid-column: 3;
}
@keyframes mp-pulse {
0%,
100% {
opacity: 1;
}
50% {
opacity: 0.5;
}
}
@media (max-width: 1040px) {
.mapping .mp-workspace {
flex-direction: column;
}
.mapping .mp-inspector {
width: 100%;
position: static;
max-height: none;
}
}
@media (max-width: 720px) {
.mapping .mp-pair {
grid-template-columns: 1fr;
gap: 8px;
}
.mapping .mp-source-card,
.mapping .mp-concept-card,
.mapping .mp-pair-components {
grid-column: 1;
}
.mapping .mp-pairs-head,
.mapping .mp-connectors {
display: none;
}
}
@media (prefers-reduced-motion: reduce) {
.mapping .mp-connector,
.mapping .mp-arrowhead,
.mapping .mp-card {
transition: none;
}
.mapping .mp-skel {
animation: none;
}
}
/* ============================================================================
Case review: the section 9.3 document-and-record confirmation surface (#98).
Light-default, design-guideline section 2.1 tokens; dark reserved for the
auxiliary polarity. Built on the same reviewer-confirmation pattern as the
crosswalk (#103): identity strip, editable cards, value chat, decision ledger.
============================================================================ */
.case-review.cr {
--cr-bg: #f2f4f5;
--cr-panel: #ffffff;
--cr-surface: #ffffff;
--cr-surface-2: #ecf0f3;
--cr-ink: #1c2b36;
--cr-ink-muted: #51626e;
--cr-hairline: #dfe5e9;
--cr-accent: #1b374a;
--cr-accent-soft: rgba(27, 55, 74, 0.09);
--cr-accent-line: rgba(27, 55, 74, 0.9);
--cr-pos: #137a4c;
--cr-pos-soft: rgba(19, 122, 76, 0.1);
--cr-warn: #b0851f;
--cr-warn-soft: rgba(176, 133, 31, 0.12);
--cr-neg: #c0392b;
--cr-neg-soft: rgba(192, 57, 43, 0.09);
--cr-r-card: var(--r-card);
--cr-r-ctrl: var(--r-ctrl);
color: var(--cr-ink);
max-width: 1320px;
}
/* The confirmation view holds a scanned document, so it renders light in both OS color
schemes. A scanned page reads truest on white (design-guideline: the document pane always
renders light, one polarity per view). This surface has no dark override. */
.cr .cr-back {
margin: 0 0 12px;
font-size: 13px;
}
.cr .cr-eyebrow {
margin: 0 0 4px;
font-size: 12px;
font-weight: 600;
letter-spacing: 0.06em;
text-transform: uppercase;
color: var(--cr-ink-muted);
}
/* --- Identity strip (from the session) --- */
.cr .cr-identity-strip {
display: flex;
align-items: center;
gap: 10px;
flex-wrap: wrap;
padding: 8px 12px;
margin-bottom: 12px;
background: var(--cr-panel);
border: 1px solid var(--cr-hairline);
border-radius: var(--cr-r-ctrl);
font-size: 13px;
}
.cr .cr-identity-label {
font-size: 12px;
letter-spacing: 0.04em;
text-transform: uppercase;
color: var(--cr-ink-muted);
}
.cr .cr-identity-who {
font-weight: 500;
}
.cr .cr-identity-note {
flex-basis: 100%;
font-size: 12px;
color: var(--cr-ink-muted);
}
/* --- Header --- */
.cr .cr-head {
display: flex;
align-items: flex-end;
justify-content: space-between;
gap: 16px;
margin-bottom: 12px;
}
.cr .cr-head h1 {
margin: 0;
font-size: clamp(32px, 4.4vw, 40px);
font-weight: 300;
font-variant-numeric: tabular-nums;
}
.cr .cr-head-meta {
display: flex;
align-items: center;
gap: 12px;
}
.cr .cr-progress {
font-size: 13px;
color: var(--cr-ink-muted);
font-variant-numeric: tabular-nums;
}
.cr .cr-progress strong {
color: var(--cr-ink);
font-weight: 600;
}
/* --- Banners, errors, empties --- */
.cr .cr-banner {
display: flex;
align-items: center;
gap: 10px;
flex-wrap: wrap;
padding: 10px 14px;
margin-bottom: 14px;
background: var(--cr-warn-soft);
border: 1px solid var(--cr-hairline);
border-radius: var(--cr-r-ctrl);
font-size: 13px;
}
.cr .cr-inline-error {
padding: 8px 12px;
margin-bottom: 12px;
color: var(--cr-neg);
background: var(--cr-neg-soft);
border: 1px solid var(--cr-neg);
border-radius: var(--cr-r-ctrl);
font-size: 13px;
}
.cr .cr-empty-note,
.cr .cr-chat-hint {
color: var(--cr-ink-muted);
font-size: 13px;
}
/* --- Two-pane workspace --- */
.cr .cr-workspace {
display: grid;
grid-template-columns: minmax(360px, 1fr) minmax(420px, 1.15fr);
gap: 18px;
align-items: start;
}
@media (max-width: 900px) {
.cr .cr-workspace {
grid-template-columns: 1fr;
}
}
/* --- Left pane: the document --- */
.cr .cr-document {
position: sticky;
top: 12px;
background: var(--cr-surface);
border: 1px solid var(--cr-hairline);
border-radius: var(--cr-r-card);
padding: 12px;
}
.cr .cr-doc-head {
display: flex;
align-items: baseline;
justify-content: space-between;
gap: 8px;
margin-bottom: 8px;
}
.cr .cr-doc-title {
font-size: 12px;
font-weight: 600;
letter-spacing: 0.06em;
text-transform: uppercase;
color: var(--cr-ink-muted);
}
.cr .cr-doc-active {
font-size: 12px;
color: var(--cr-ink-muted);
}
.cr .cr-page-frame {
position: relative;
background: var(--cr-surface-2);
border: 1px solid var(--cr-hairline);
border-radius: var(--cr-r-ctrl);
overflow: hidden;
min-height: 320px;
display: flex;
align-items: center;
justify-content: center;
}
.cr .cr-page-img {
display: block;
width: 100%;
height: auto;
}
.cr .cr-page-empty {
padding: 40px 24px;
}
.cr .cr-page-box {
position: absolute;
background: var(--cr-accent-soft);
border: 2px solid var(--cr-accent-line);
border-radius: 3px;
pointer-events: none;
}
.cr .cr-page-fallback {
position: absolute;
left: 8px;
bottom: 8px;
max-width: calc(100% - 16px);
margin: 0;
padding: 6px 10px;
font-size: 12px;
color: var(--cr-ink-muted);
background: var(--cr-surface);
border: 1px dashed var(--cr-hairline);
border-radius: var(--cr-r-ctrl);
}
.cr .cr-page-empty .cr-page-fallback {
position: static;
border-style: solid;
text-align: center;
}
.cr .cr-page-nav {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
margin-top: 10px;
}
.cr .cr-page-count {
font-size: 12px;
color: var(--cr-ink-muted);
font-variant-numeric: tabular-nums;
}
/* --- Right pane: the record --- */
.cr .cr-record {
display: flex;
flex-direction: column;
gap: 14px;
}
.cr .cr-card {
background: var(--cr-surface);
border: 1px solid var(--cr-hairline);
border-radius: var(--cr-r-card);
padding: 14px 16px;
}
.cr .cr-card-head {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 10px;
}
.cr .cr-card-sub,
.cr .cr-lede {
margin: 2px 0 0;
font-size: 12px;
color: var(--cr-ink-muted);
}
/* --- Synoptic checklist card --- */
.cr .cr-checklist-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 6px 14px;
margin-top: 12px;
}
@media (max-width: 560px) {
.cr .cr-checklist-grid {
grid-template-columns: 1fr;
}
}
.cr .cr-check-item {
display: flex;
flex-direction: column;
gap: 2px;
text-align: left;
padding: 6px 8px;
background: transparent;
border: 1px solid transparent;
border-radius: var(--cr-r-ctrl);
cursor: pointer;
color: inherit;
/* Keep the grid column at 1fr so a long evidence quote truncates instead of widening the box. */
min-width: 0;
}
.cr .cr-check-item:hover {
background: var(--cr-surface-2);
}
.cr .cr-check-row {
display: flex;
align-items: center;
gap: 8px;
}
.cr .cr-check-box {
display: inline-flex;
align-items: center;
justify-content: center;
width: 16px;
height: 16px;
flex: 0 0 16px;
border: 1.5px solid var(--cr-ink-muted);
border-radius: 4px;
font-size: 11px;
line-height: 1;
color: #fff;
}
.cr .cr-check-box.ticked {
background: var(--cr-pos);
border-color: var(--cr-pos);
}
.cr .cr-check-label {
font-size: 13px;
font-weight: 500;
}
.cr .cr-check-quote {
margin-left: 24px;
font-size: 12px;
color: var(--cr-ink);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.cr .cr-checklist-foot {
margin: 12px 0 0;
font-size: 12px;
color: var(--cr-ink-muted);
font-variant-numeric: tabular-nums;
}
/* --- Section bands --- */
.cr .cr-band {
background: var(--cr-surface);
border: 1px solid var(--cr-hairline);
border-radius: var(--cr-r-card);
overflow: hidden;
}
.cr .cr-band-head {
display: flex;
align-items: center;
gap: 8px;
width: 100%;
padding: 10px 14px;
background: var(--cr-panel);
border: none;
border-bottom: 1px solid var(--cr-hairline);
cursor: pointer;
color: inherit;
}
.cr .cr-band-caret {
color: var(--cr-ink-muted);
font-size: 11px;
}
.cr .cr-band-title {
font-size: 12px;
font-weight: 600;
letter-spacing: 0.06em;
text-transform: uppercase;
}
.cr .cr-band-count {
margin-left: auto;
font-size: 12px;
color: var(--cr-ink-muted);
font-variant-numeric: tabular-nums;
}
.cr .cr-band-body {
display: flex;
flex-direction: column;
gap: 10px;
padding: 12px;
}
/* --- Field cards --- */
.cr .cr-field-card {
cursor: pointer;
transition: border-color 120ms ease-out, background 120ms ease-out;
}
.cr .cr-field-card.active {
border-color: var(--cr-accent);
box-shadow: inset 2px 0 0 0 var(--cr-accent);
}
.cr .cr-card-derived.active {
background: var(--cr-surface);
}
.cr .cr-card-not_applicable {
background: var(--cr-panel);
}
.cr .cr-card-title-wrap {
display: flex;
flex-direction: column;
gap: 1px;
}
.cr .cr-card-title {
font-size: 15px;
font-weight: 600;
}
.cr .cr-card-chips {
display: flex;
align-items: center;
gap: 6px;
flex-shrink: 0;
}
.cr .cr-value-row {
display: flex;
align-items: baseline;
gap: 10px;
flex-wrap: wrap;
margin: 8px 0;
}
.cr .cr-value {
font-size: 15px;
font-weight: 500;
font-variant-numeric: tabular-nums;
}
.cr .cr-not-applicable {
display: flex;
align-items: center;
gap: 8px;
flex-wrap: wrap;
margin: 8px 0 0;
font-size: 13px;
color: var(--cr-ink-muted);
}
/* --- Evidence bubble (section 7): recessed, cited, accent left-edge --- */
.cr .cr-evidence {
margin: 8px 0 0;
padding: 8px 12px;
background: var(--cr-accent-soft);
border-left: 2px solid var(--cr-accent);
border-radius: 0 var(--cr-r-ctrl) var(--cr-r-ctrl) 0;
display: flex;
align-items: baseline;
justify-content: space-between;
gap: 8px;
flex-wrap: wrap;
}
.cr .cr-evidence-quote {
font-size: 13px;
}
.cr .cr-evidence-thin {
background: var(--cr-surface-2);
border-left-color: var(--cr-hairline);
}
/* --- Derivation chip --- */
.cr .cr-derivation {
margin: 8px 0 0;
padding: 8px 12px;
background: var(--cr-surface-2);
border: 1px solid var(--cr-hairline);
border-radius: var(--cr-r-ctrl);
}
.cr .cr-derivation-inputs {
display: flex;
align-items: baseline;
gap: 8px;
margin: 0 0 4px;
font-size: 12px;
}
.cr .cr-derivation-chip {
display: flex;
align-items: center;
gap: 8px;
margin: 0;
font-size: 13px;
font-variant-numeric: tabular-nums;
}
.cr .cr-derivation-result strong {
color: var(--cr-accent);
font-weight: 600;
}
/* --- Validation block --- */
.cr .cr-validation {
margin: 10px 0 0;
padding: 10px 12px;
background: var(--cr-panel);
border: 1px solid var(--cr-hairline);
border-radius: var(--cr-r-ctrl);
}
.cr .cr-validation-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
margin-bottom: 8px;
}
.cr .cr-checks {
display: flex;
flex-wrap: wrap;
gap: 6px;
}
.cr .cr-check {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 2px 8px;
font-size: 12px;
border-radius: var(--cr-r-ctrl);
border: 1px solid var(--cr-hairline);
background: var(--cr-surface);
color: var(--cr-ink-muted);
}
.cr .cr-check-pass .cr-check-mark {
color: var(--cr-pos);
}
.cr .cr-check-fail {
border-color: var(--cr-neg);
color: var(--cr-neg);
}
.cr .cr-check-fail .cr-check-mark {
color: var(--cr-neg);
}
.cr .cr-reasons {
margin: 8px 0 0;
padding-left: 18px;
font-size: 12px;
color: var(--cr-ink-muted);
}
.cr .cr-reasons li {
margin: 2px 0;
}
.cr .cr-sub-label {
margin: 0;
font-size: 11px;
font-weight: 600;
letter-spacing: 0.05em;
text-transform: uppercase;
color: var(--cr-ink-muted);
}
/* --- Actions --- */
.cr .cr-actions {
display: flex;
align-items: center;
gap: 8px;
flex-wrap: wrap;
margin-top: 10px;
}
.cr .cr-locate-result {
font-size: 12px;
color: var(--cr-ink-muted);
font-variant-numeric: tabular-nums;
}
.cr .cr-edit {
display: flex;
align-items: center;
gap: 8px;
flex-wrap: wrap;
margin: 8px 0 0;
}
/* --- Value chat --- */
.cr .cr-chat {
margin-top: 12px;
padding-top: 10px;
border-top: 1px solid var(--cr-hairline);
}
.cr .cr-chat-thread {
display: flex;
flex-direction: column;
gap: 8px;
margin: 8px 0;
/* Scroll a long exchange within the thread rather than growing the card unboundedly; the input stays
reachable below. */
max-height: 320px;
overflow-y: auto;
overscroll-behavior: contain;
}
.cr .cr-chat-user {
align-self: flex-end;
max-width: 85%;
margin: 0;
padding: 6px 10px;
font-size: 13px;
background: var(--cr-accent-soft);
border-radius: var(--cr-r-ctrl);
}
.cr .cr-chat-answer {
padding: 8px 12px;
background: var(--cr-surface-2);
border: 1px solid var(--cr-hairline);
border-radius: var(--cr-r-ctrl);
}
.cr .cr-chat-text {
margin: 0;
font-size: 13px;
}
.cr .cr-chat-reasoning {
margin: 6px 0 0;
font-size: 12px;
color: var(--cr-ink-muted);
}
.cr .cr-chat-evidence {
margin: 8px 0 0;
padding-left: 16px;
font-size: 12px;
}
.cr .cr-chat-evidence li {
margin: 2px 0;
display: flex;
gap: 6px;
align-items: baseline;
}
.cr .cr-chat-suggestion {
display: flex;
align-items: center;
gap: 8px;
flex-wrap: wrap;
margin-top: 8px;
}
.cr .cr-chat-form {
display: flex;
gap: 8px;
/* Keep the Ask button at the bottom edge as the input grows with its content. */
align-items: flex-end;
}
.cr .cr-chat-form .cr-input {
flex: 1;
}
/* An auto-growing textarea (see the useLayoutEffect setting its height from scrollHeight): one line to
start, grows with the message, then scrolls past a cap. */
.cr .cr-chat-form textarea.cr-input {
height: auto;
min-height: 32px;
max-height: 120px;
line-height: 1.4;
resize: none;
overflow-y: auto;
box-sizing: border-box;
}
/* --- Decision ledger --- */
.cr .cr-ledger {
margin-top: 20px;
}
.cr .cr-section-title {
margin: 0 0 4px;
font-size: 18px;
font-weight: 600;
}
.cr .cr-table-wrap {
margin-top: 10px;
overflow-x: auto;
border: 1px solid var(--cr-hairline);
border-radius: var(--cr-r-card);
}
.cr .cr-table {
width: 100%;
border-collapse: collapse;
font-size: 13px;
}
.cr .cr-table thead th {
text-align: left;
padding: 8px 12px;
font-size: 12px;
font-weight: 600;
color: var(--cr-ink-muted);
background: var(--cr-panel);
border-bottom: 1px solid var(--cr-hairline);
white-space: nowrap;
}
.cr .cr-table tbody td {
padding: 8px 12px;
border-bottom: 1px solid var(--cr-hairline);
vertical-align: middle;
}
.cr .cr-table tbody tr:last-child td {
border-bottom: none;
}
.cr .cr-ledger-role {
color: var(--cr-ink-muted);
}
/* --- Chips, tags, buttons, inputs --- */
.cr .cr-state-chip {
display: inline-flex;
align-items: center;
gap: 5px;
padding: 2px 8px;
font-size: 12px;
font-weight: 500;
border-radius: var(--cr-r-ctrl);
white-space: nowrap;
}
.cr .cr-state-chip::before {
content: "";
width: 6px;
height: 6px;
border-radius: 50%;
background: currentColor;
opacity: 0.85;
}
.cr .cr-chip-pos {
color: var(--cr-pos);
background: var(--cr-pos-soft);
}
.cr .cr-chip-warn {
color: var(--cr-warn);
background: var(--cr-warn-soft);
}
.cr .cr-chip-neg {
color: var(--cr-neg);
background: var(--cr-neg-soft);
}
.cr .cr-status-queued,
.cr .cr-status-ready_for_review {
color: var(--cr-warn);
background: var(--cr-warn-soft);
}
.cr .cr-status-in_review,
.cr .cr-status-processing {
color: var(--cr-accent);
background: var(--cr-accent-soft);
}
.cr .cr-status-confirmed,
.cr .cr-status-exported {
color: var(--cr-pos);
background: var(--cr-pos-soft);
}
.cr .cr-status-flagged {
color: var(--cr-neg);
background: var(--cr-neg-soft);
}
.cr .cr-tag {
display: inline-flex;
align-items: center;
padding: 1px 7px;
font-size: 12px;
color: var(--cr-ink-muted);
background: var(--cr-surface-2);
border-radius: var(--cr-r-ctrl);
}
.cr .cr-muted {
color: var(--cr-ink-muted);
}
.cr .cr-code {
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
font-size: 12px;
padding: 1px 5px;
background: var(--cr-surface-2);
border-radius: 4px;
}
.cr .cr-btn,
.cr .cr-btn-secondary,
.cr .cr-btn-ghost {
font: inherit;
font-size: 13px;
padding: 6px 12px;
border-radius: var(--cr-r-ctrl);
cursor: pointer;
border: 1px solid transparent;
}
.cr .cr-btn {
background: var(--cr-accent);
color: #fff;
border-color: var(--cr-accent);
}
.cr .cr-btn-secondary {
background: var(--cr-surface);
color: var(--cr-ink);
border-color: var(--cr-hairline);
}
.cr .cr-btn-ghost {
background: transparent;
color: var(--cr-accent);
padding: 4px 8px;
}
.cr .cr-btn:disabled,
.cr .cr-btn-secondary:disabled,
.cr .cr-btn-ghost:disabled {
opacity: 0.55;
cursor: default;
}
.cr .cr-next {
margin-top: 14px;
}
.cr .cr-input {
font: inherit;
font-size: 13px;
padding: 6px 10px;
color: var(--cr-ink);
background: var(--cr-surface);
border: 1px solid var(--cr-hairline);
border-radius: var(--cr-r-ctrl);
}
.cr .cr-input:focus {
outline: none;
border-color: var(--cr-accent);
box-shadow: 0 0 0 2px var(--cr-accent-soft);
}
.cr .cr-keyboard-hint {
margin-top: 14px;
font-size: 12px;
color: var(--cr-ink-muted);
}
/* --- Loading skeleton at the true layout --- */
.cr .cr-skel {
background: linear-gradient(90deg, var(--cr-surface-2) 25%, var(--cr-panel) 50%, var(--cr-surface-2) 75%);
background-size: 400% 100%;
border-radius: var(--cr-r-card);
animation: cr-shimmer 1.4s ease-in-out infinite;
}
.cr .cr-skel-page {
height: 480px;
}
.cr .cr-skel-card {
height: 110px;
}
@keyframes cr-shimmer {
0% {
background-position: 100% 0;
}
100% {
background-position: 0 0;
}
}
@media (prefers-reduced-motion: reduce) {
.cr .cr-skel {
animation: none;
}
.cr .cr-field-card {
transition: none;
}
}
/* ==========================================================================
Export screen (#101, prd.md sections 3, 8.1; design-guideline sections 5.1,
5.4, 4, 6.4). No document in view, so it ships both polarities: light default
plus a dark override for a reviewer working without a scanned page.
========================================================================== */
.export.ex {
--ex-bg: var(--bg);
--ex-panel: var(--panel);
--ex-surface: var(--surface);
--ex-surface-2: var(--surface-2);
--ex-ink: var(--ink);
--ex-ink-muted: var(--ink-muted);
--ex-hairline: var(--hairline);
--ex-accent: var(--accent);
--ex-accent-soft: var(--accent-soft);
--ex-neg: var(--neg);
--ex-neg-soft: rgba(192, 57, 43, 0.09);
--ex-r-card: var(--r-card);
--ex-r-ctrl: var(--r-ctrl);
color: var(--ex-ink);
max-width: 1320px;
}
@media (prefers-color-scheme: dark) {
.export.ex {
--ex-neg-soft: rgba(229, 101, 75, 0.16);
}
}
.ex .ex-head {
margin-bottom: 20px;
}
.ex .ex-eyebrow {
margin: 0 0 4px;
font-size: 12px;
font-weight: 600;
letter-spacing: 0.06em;
text-transform: uppercase;
color: var(--ex-ink-muted);
}
.ex h1 {
margin: 0 0 8px;
font-size: clamp(32px, 4.4vw, 40px);
font-weight: 300;
letter-spacing: -0.01em;
}
.ex .ex-lede {
margin: 0;
max-width: 68ch;
font-size: 14px;
line-height: 1.5;
color: var(--ex-ink-muted);
}
/* --- Error banner (section 6.4) --- */
.ex .ex-banner-error {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
margin-bottom: 16px;
padding: 10px 14px;
font-size: 13px;
color: var(--ex-neg);
background: var(--ex-neg-soft);
border: 1px solid var(--ex-neg);
border-radius: var(--ex-r-ctrl);
}
/* --- Empty state (section 6.4) --- */
.ex .ex-empty {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 12px;
padding: 40px;
background: var(--ex-surface);
border: 1px solid var(--ex-hairline);
border-radius: var(--ex-r-card);
}
.ex .ex-empty p {
margin: 0;
font-size: 15px;
color: var(--ex-ink-muted);
}
/* --- Metric readouts (section 5.4) --- */
.ex .ex-metrics {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 12px;
margin-bottom: 16px;
}
.ex .ex-metric {
display: flex;
flex-direction: column;
gap: 4px;
padding: 16px 18px;
background: var(--ex-surface);
border: 1px solid var(--ex-hairline);
border-radius: var(--ex-r-card);
}
.ex .ex-metric-value {
font-size: 32px;
font-weight: 500;
font-variant-numeric: tabular-nums;
line-height: 1.1;
}
.ex .ex-metric-accent {
color: var(--ex-accent);
}
.ex .ex-metric-label {
font-size: 12px;
color: var(--ex-ink-muted);
}
/* --- Buttons (section 6.1) --- */
.ex .ex-btn {
display: inline-flex;
align-items: center;
height: 36px;
padding: 0 16px;
font-size: 13px;
font-weight: 500;
border-radius: var(--ex-r-ctrl);
border: 1px solid transparent;
cursor: pointer;
text-decoration: none;
transition: background 120ms ease-out, border-color 120ms ease-out;
}
.ex .ex-btn-primary {
color: #ffffff;
background: var(--ex-accent);
}
.ex .ex-btn-primary:hover {
filter: brightness(1.06);
}
.ex .ex-btn-ghost {
color: var(--ex-ink);
background: var(--ex-surface);
border-color: var(--ex-hairline);
}
.ex .ex-btn-ghost:hover {
border-color: var(--ex-accent);
color: var(--ex-accent);
}
.ex .ex-btn-text {
color: var(--ex-accent);
background: transparent;
padding: 0 8px;
}
.ex .ex-btn:focus-visible {
outline: 2px solid var(--ex-accent);
outline-offset: 2px;
}
/* --- Downloads --- */
.ex .ex-downloads {
margin-bottom: 16px;
}
.ex .ex-downloads-row {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 8px;
}
.ex .ex-downloads-note {
margin: 8px 0 0;
font-size: 12px;
color: var(--ex-ink-muted);
}
/* --- Cards (section 4) --- */
.ex .ex-bundle,
.ex .ex-table-card {
margin-bottom: 16px;
background: var(--ex-surface);
border: 1px solid var(--ex-hairline);
border-radius: var(--ex-r-card);
}
.ex .ex-card-head {
display: flex;
align-items: baseline;
justify-content: space-between;
gap: 12px;
padding: 14px 18px;
border-bottom: 1px solid var(--ex-hairline);
}
.ex .ex-card-title {
font-size: 16px;
font-weight: 600;
}
.ex .ex-card-meta {
font-size: 12px;
color: var(--ex-ink-muted);
}
.ex .ex-bundle-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 14px 24px;
padding: 18px;
}
.ex .ex-field {
display: flex;
flex-direction: column;
gap: 3px;
min-width: 0;
}
.ex .ex-field-label {
font-size: 12px;
color: var(--ex-ink-muted);
}
.ex .ex-field-value {
font-size: 14px;
font-weight: 500;
word-break: break-word;
}
.ex .ex-mono {
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
font-size: 12px;
font-variant-numeric: tabular-nums;
}
.ex .ex-bundle-note {
margin: 0;
padding: 0 18px 18px;
font-size: 13px;
line-height: 1.5;
color: var(--ex-ink-muted);
}
/* --- Table (section 5.1): wide, horizontal scroll, sticky header and first column --- */
.ex .ex-table-scroll {
overflow-x: auto;
border-radius: 0 0 var(--ex-r-card) var(--ex-r-card);
}
.ex .ex-table {
border-collapse: collapse;
width: max-content;
min-width: 100%;
font-size: 13px;
}
.ex .ex-table th,
.ex .ex-table td {
padding: 8px 12px;
text-align: left;
white-space: nowrap;
border-bottom: 1px solid var(--ex-hairline);
}
.ex .ex-table thead th {
position: sticky;
top: 0;
z-index: 1;
font-size: 12px;
font-weight: 600;
color: var(--ex-ink-muted);
background: var(--ex-panel);
}
.ex .ex-th-derived {
display: block;
font-size: 10px;
font-weight: 500;
letter-spacing: 0.04em;
text-transform: uppercase;
color: var(--ex-accent);
}
/* The join tag marks an outcome column joined from the GDC clinical record (#176): a neutral ink label,
distinct from the gold `derived` accent, so source and derivation read as two different facts. */
.ex .ex-th-join {
display: block;
font-size: 10px;
font-weight: 500;
letter-spacing: 0.04em;
text-transform: uppercase;
color: var(--ex-ink-muted);
}
.ex .ex-th-sticky,
.ex .ex-td-sticky {
position: sticky;
left: 0;
z-index: 2;
background: var(--ex-surface);
font-weight: 500;
font-variant-numeric: tabular-nums;
}
.ex .ex-table thead .ex-th-sticky {
z-index: 3;
background: var(--ex-panel);
}
.ex .ex-table tbody tr:hover td {
background: var(--ex-surface-2);
}
.ex .ex-table tbody tr:hover .ex-td-sticky {
background: var(--ex-surface-2);
}
.ex .ex-cell-value {
font-weight: 500;
font-variant-numeric: tabular-nums;
}
.ex .ex-reason-tag {
display: inline-flex;
align-items: center;
padding: 1px 8px;
font-size: 12px;
color: var(--ex-ink-muted);
background: var(--ex-surface-2);
border-radius: var(--ex-r-ctrl);
}
/* --- Target-schema selector and the FIGO stage verdict (#40) --- */
.ex .ex-controls {
display: flex;
align-items: center;
gap: 12px;
flex-wrap: wrap;
margin: 4px 0 18px;
padding: 10px 14px;
border: 1px solid var(--ex-hairline);
border-radius: var(--ex-r-card);
background: var(--ex-panel);
}
.ex .ex-control {
display: flex;
align-items: center;
gap: 8px;
}
.ex .ex-control-label {
font-size: 12px;
color: var(--ex-ink-muted);
}
.ex .ex-select {
height: 32px;
padding: 0 10px;
font-size: 13px;
color: var(--ex-ink);
background: var(--ex-surface);
border: 1px solid var(--ex-hairline);
border-radius: var(--ex-r-ctrl);
}
.ex .ex-select:focus {
outline: none;
border-color: var(--ex-accent);
}
.ex .ex-control-note {
font-size: 12px;
color: var(--ex-ink-muted);
}
.ex .ex-stage-determined {
font-weight: 500;
font-variant-numeric: tabular-nums;
}
.ex .ex-stage-constrained {
display: inline-flex;
align-items: center;
gap: 6px;
font-weight: 500;
font-variant-numeric: tabular-nums;
}
.ex .ex-stage-flag {
padding: 1px 7px;
font-size: 11px;
font-weight: 500;
letter-spacing: 0.04em;
border-radius: var(--ex-r-ctrl);
color: var(--ex-accent);
background: var(--ex-accent-soft);
}
.ex .ex-stage-indeterminate {
color: var(--ex-ink-muted);
background: var(--ex-surface-2);
}
/* --- Loading skeleton at the true layout (section 6.4) --- */
.ex .ex-skel {
display: block;
background: linear-gradient(
100deg,
var(--ex-surface-2) 30%,
var(--ex-panel) 50%,
var(--ex-surface-2) 70%
);
background-size: 200% 100%;
border-radius: var(--ex-r-ctrl);
animation: ex-shimmer 1.4s ease-in-out infinite;
}
.ex .ex-skel-metric {
width: 56px;
height: 32px;
margin-bottom: 6px;
}
.ex .ex-skel-line {
width: 84px;
height: 12px;
}
.ex .ex-skel-block {
height: 180px;
margin-bottom: 16px;
}
.ex .ex-skel-table {
height: 320px;
}
@keyframes ex-shimmer {
0% {
background-position: 100% 0;
}
100% {
background-position: 0 0;
}
}
@media (prefers-reduced-motion: reduce) {
.ex .ex-skel {
animation: none;
}
.ex .ex-btn {
transition: none;
}
}
/* ==========================================================================
Cohort dashboard (#100, prd.md sections 8.1, 4.3, 4.5, 4.7; design-guideline
sections 5.4, 5.1, 4, 6.4). No document in view, so it ships both polarities.
One accent carries chrome and the confirmed-value bars; coded absences read as
quiet neutral tags, and the semantic trio is reserved for state.
========================================================================== */
.dashboard.db {
--db-bg: var(--bg);
--db-panel: var(--panel);
--db-surface: var(--surface);
--db-surface-2: var(--surface-2);
--db-ink: var(--ink);
--db-ink-muted: var(--ink-muted);
--db-hairline: var(--hairline);
--db-accent: var(--accent);
--db-accent-soft: var(--accent-soft);
--db-pos: var(--pos);
--db-warn: var(--warn);
--db-neg: var(--neg);
--db-neg-soft: rgba(192, 57, 43, 0.09);
--db-r-card: var(--r-card);
--db-r-ctrl: var(--r-ctrl);
color: var(--db-ink);
max-width: 1320px;
}
@media (prefers-color-scheme: dark) {
.dashboard.db {
--db-neg-soft: rgba(229, 101, 75, 0.16);
}
}
.db .db-head {
margin-bottom: 20px;
}
.db .db-eyebrow {
margin: 0 0 4px;
font-size: 12px;
font-weight: 600;
letter-spacing: 0.06em;
text-transform: uppercase;
color: var(--db-ink-muted);
}
.db h1 {
margin: 0 0 8px;
font-size: clamp(32px, 4.4vw, 40px);
font-weight: 300;
letter-spacing: -0.01em;
}
.db .db-lede {
margin: 0;
max-width: 74ch;
font-size: 14px;
line-height: 1.5;
color: var(--db-ink-muted);
}
/* --- Error banner (section 6.4) --- */
.db .db-banner-error {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
margin-bottom: 16px;
padding: 10px 14px;
font-size: 13px;
color: var(--db-neg);
background: var(--db-neg-soft);
border: 1px solid var(--db-neg);
border-radius: var(--db-r-ctrl);
}
/* --- Empty state (section 6.4) --- */
.db .db-empty {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 12px;
padding: 40px;
background: var(--db-surface);
border: 1px solid var(--db-hairline);
border-radius: var(--db-r-card);
}
.db .db-empty p {
margin: 0;
font-size: 15px;
color: var(--db-ink-muted);
}
/* --- Buttons (section 6.1) --- */
.db .db-btn {
display: inline-flex;
align-items: center;
height: 36px;
padding: 0 16px;
font-size: 13px;
font-weight: 500;
border-radius: var(--db-r-ctrl);
border: 1px solid transparent;
cursor: pointer;
text-decoration: none;
transition: background 120ms ease-out, border-color 120ms ease-out;
}
.db .db-btn-primary {
color: #ffffff;
background: var(--db-accent);
}
.db .db-btn-primary:hover {
filter: brightness(1.06);
}
.db .db-btn-ghost {
color: var(--db-ink);
background: var(--db-surface);
border-color: var(--db-hairline);
}
.db .db-btn-ghost:hover {
border-color: var(--db-accent);
color: var(--db-accent);
}
.db .db-btn:focus-visible {
outline: 2px solid var(--db-accent);
outline-offset: 2px;
}
/* --- Metric readouts (section 5.4) --- */
.db .db-metrics {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 12px;
margin-bottom: 20px;
}
.db .db-metric {
display: flex;
flex-direction: column;
gap: 4px;
padding: 16px 18px;
background: var(--db-surface);
border: 1px solid var(--db-hairline);
border-radius: var(--db-r-card);
}
.db .db-metric-value {
font-size: 32px;
font-weight: 500;
font-variant-numeric: tabular-nums;
line-height: 1.1;
}
.db .db-metric-accent {
color: var(--db-accent);
}
/* Cohort findings: per field with confirmed data, its most common value and share (#231). */
.db .db-findings {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
gap: 12px;
}
.db .db-finding {
display: flex;
align-items: baseline;
gap: 12px;
padding: 12px 14px;
background: var(--db-surface);
border: 1px solid var(--db-hairline);
border-radius: var(--db-r-card);
}
.db .db-finding-share {
font-size: 26px;
font-weight: 500;
font-variant-numeric: tabular-nums;
line-height: 1;
}
.db .db-finding-body {
display: flex;
flex-direction: column;
gap: 2px;
min-width: 0;
}
.db .db-finding-value {
font-size: 14px;
font-weight: 600;
overflow-wrap: anywhere;
}
.db .db-finding-field {
font-size: 11px;
color: var(--db-ink-muted);
}
.db .db-metric-label {
font-size: 12px;
color: var(--db-ink-muted);
}
/* --- Legend (completion-by-field status trio) --- */
.db .db-legend {
display: flex;
flex-wrap: wrap;
gap: 16px;
margin-bottom: 10px;
}
.db .db-legend-item {
display: inline-flex;
align-items: center;
gap: 6px;
font-size: 12px;
color: var(--db-ink-muted);
}
.db .db-swatch {
width: 10px;
height: 10px;
border-radius: 3px;
}
/* --- Completion by field: one stacked status bar per variable --- */
.db .db-completion {
display: flex;
flex-direction: column;
gap: 8px;
padding: 16px 18px;
background: var(--db-surface);
border: 1px solid var(--db-hairline);
border-radius: var(--db-r-card);
}
.db .db-comp-row {
display: grid;
grid-template-columns: 190px 1fr 44px;
align-items: center;
gap: 12px;
}
.db .db-comp-label {
font-size: 13px;
font-weight: 500;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.db .db-comp-track {
display: flex;
height: 16px;
border-radius: var(--db-r-ctrl);
overflow: hidden;
background: var(--db-surface-2);
}
.db .db-comp-seg {
height: 100%;
}
/* A 2px surface gap between adjacent status segments (dataviz marks-and-anatomy). */
.db .db-comp-seg + .db-comp-seg {
border-left: 2px solid var(--db-surface);
}
.db .db-comp-pct {
font-size: 13px;
font-weight: 500;
font-variant-numeric: tabular-nums;
text-align: right;
color: var(--db-ink);
}
/* The section-4.3 status trio; the legend swatches reuse the same fills. */
.db .db-seg-confirmed {
background: var(--db-pos);
}
.db .db-seg-pending {
background: var(--db-warn);
}
.db .db-seg-absent {
background: var(--db-ink-muted);
opacity: 0.45;
}
/* --- Sections --- */
.db .db-section {
margin-bottom: 24px;
}
.db .db-section-head {
margin-bottom: 12px;
}
.db .db-section h2 {
margin: 0 0 2px;
font-size: 18px;
font-weight: 600;
}
.db .db-section-note {
margin: 0;
font-size: 12px;
color: var(--db-ink-muted);
}
/* --- Collapsible section headers (progressive disclosure) --- */
.db .db-collapsible {
border-top: 1px solid var(--db-hairline);
padding-top: 14px;
margin-bottom: 18px;
}
.db .db-section-toggle {
display: flex;
align-items: center;
gap: 8px;
width: 100%;
padding: 0;
border: none;
background: none;
cursor: pointer;
text-align: left;
color: var(--db-ink);
}
.db .db-section-toggle h2 {
margin: 0;
font-size: 18px;
font-weight: 600;
}
.db .db-chevron {
flex: none;
width: 18px;
height: 18px;
color: var(--db-ink-muted);
transition: transform 120ms ease-out;
}
.db .db-chevron-open {
transform: rotate(90deg);
}
.db .db-section-count {
font-size: 12px;
font-variant-numeric: tabular-nums;
color: var(--db-ink-muted);
background: var(--db-surface-2);
border-radius: var(--db-r-ctrl);
padding: 1px 8px;
}
.db .db-section-body {
margin-top: 12px;
}
@media (prefers-reduced-motion: reduce) {
.db .db-chevron {
transition: none;
}
}
/* --- Missingness-by-grade module (the section-4.5 finding, stated once) --- */
.db .db-missing {
display: flex;
flex-direction: column;
gap: 16px;
}
.db .db-missing-field {
display: flex;
flex-direction: column;
gap: 8px;
padding: 14px 16px;
background: var(--db-surface);
border: 1px solid var(--db-hairline);
border-radius: var(--db-r-card);
}
.db .db-missing-head {
display: flex;
align-items: baseline;
gap: 10px;
}
.db .db-missing-detail {
margin: 0;
font-size: 12px;
line-height: 1.4;
color: var(--db-ink-muted);
}
/* --- Per-field distribution cards (section 4) --- */
.db .db-field-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(360px, 1fr));
gap: 12px;
}
.db .db-field-card {
display: flex;
flex-direction: column;
gap: 12px;
padding: 16px 18px;
background: var(--db-surface);
border: 1px solid var(--db-hairline);
border-radius: var(--db-r-card);
}
.db .db-field-head {
display: flex;
align-items: baseline;
justify-content: space-between;
gap: 12px;
}
.db .db-field-title {
display: flex;
flex-direction: column;
gap: 2px;
min-width: 0;
}
.db .db-field-name {
font-size: 16px;
font-weight: 600;
}
.db .db-field-counts {
font-size: 12px;
color: var(--db-ink-muted);
font-variant-numeric: tabular-nums;
}
.db .db-field-conf {
flex: none;
font-size: 12px;
font-weight: 500;
font-variant-numeric: tabular-nums;
color: var(--db-ink-muted);
}
.db .db-series {
display: flex;
flex-direction: column;
gap: 6px;
}
.db .db-series-label {
font-size: 11px;
font-weight: 600;
letter-spacing: 0.05em;
text-transform: uppercase;
color: var(--db-ink-muted);
}
.db .db-series-empty {
margin: 0;
font-size: 13px;
color: var(--db-ink-muted);
}
/* --- Categorical distribution: ranked top-N bars, tail folded into Other --- */
/* --- Reported-stage T/N/M sub-distributions (separated tabs) --- */
.db .db-components {
display: flex;
flex-direction: column;
gap: 12px;
}
.db .db-component {
display: flex;
flex-direction: column;
gap: 5px;
padding-left: 10px;
border-left: 2px solid var(--db-hairline);
}
.db .db-component-dim {
font-size: 12px;
font-weight: 500;
color: var(--db-ink-muted);
}
.db .db-catbars {
display: flex;
flex-direction: column;
gap: 4px;
}
.db .db-catbar-row {
display: grid;
grid-template-columns: 136px 1fr 32px;
align-items: center;
gap: 8px;
}
.db .db-catbar-key {
font-size: 12px;
font-weight: 500;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.db .db-catbar-track {
height: 12px;
border-radius: 999px;
background: var(--db-surface-2);
overflow: hidden;
}
.db .db-catbar-fill {
display: block;
height: 100%;
border-radius: 999px;
background: var(--db-accent);
}
.db .db-catbar-count {
font-size: 12px;
font-variant-numeric: tabular-nums;
text-align: right;
color: var(--db-ink-muted);
}
.db .db-catbar-other .db-catbar-key {
color: var(--db-ink-muted);
font-style: italic;
}
.db .db-catbar-other .db-catbar-fill {
background: var(--db-ink-muted);
opacity: 0.45;
}
/* --- Numeric distribution: vertical histogram over fixed-width bins --- */
.db .db-hist {
display: flex;
flex-direction: column;
gap: 5px;
}
.db .db-hist-plot {
display: flex;
align-items: flex-end;
gap: 3px;
height: 96px;
padding-top: 14px;
}
.db .db-hist-col {
position: relative;
flex: 1;
display: flex;
flex-direction: column;
justify-content: flex-end;
align-items: center;
height: 100%;
}
.db .db-hist-bar {
width: 100%;
border-radius: 4px 4px 0 0;
background: var(--db-accent);
}
.db .db-hist-count {
position: absolute;
top: -14px;
font-size: 10px;
font-variant-numeric: tabular-nums;
color: var(--db-ink-muted);
}
.db .db-hist-axis {
display: flex;
gap: 3px;
font-size: 10px;
font-variant-numeric: tabular-nums;
color: var(--db-ink-muted);
}
.db .db-hist-tick {
flex: 1;
text-align: left;
}
.db .db-hist-tick-end {
flex: 0 0 auto;
text-align: right;
white-space: nowrap;
}
/* --- Coded-absence tags: categorical metadata, quiet neutral --- */
.db .db-abs-tags {
display: flex;
flex-wrap: wrap;
gap: 6px;
}
.db .db-abs-tag {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 2px 8px;
font-size: 12px;
color: var(--db-ink-muted);
background: var(--db-surface-2);
border-radius: var(--db-r-ctrl);
}
.db .db-abs-tag-n {
font-weight: 600;
font-variant-numeric: tabular-nums;
color: var(--db-ink);
}
/* --- Absence-versus-grade association --- */
.db .db-assoc {
display: flex;
flex-direction: column;
gap: 6px;
padding-top: 10px;
border-top: 1px solid var(--db-hairline);
}
.db .db-assoc-strata {
display: flex;
flex-direction: column;
gap: 4px;
}
.db .db-stratum {
display: grid;
grid-template-columns: 96px 1fr 78px;
align-items: center;
gap: 8px;
}
.db .db-stratum-label {
font-size: 12px;
color: var(--db-ink-muted);
}
.db .db-stratum-track {
height: 8px;
border-radius: 999px;
background: var(--db-surface-2);
overflow: hidden;
}
.db .db-stratum-fill {
display: block;
height: 100%;
border-radius: 999px;
background: var(--db-accent);
opacity: 0.65;
}
.db .db-stratum-rate {
font-size: 12px;
font-variant-numeric: tabular-nums;
text-align: right;
color: var(--db-ink);
}
.db .db-stratum-n {
color: var(--db-ink-muted);
}
.db .db-assoc-signal {
display: flex;
align-items: baseline;
gap: 8px;
margin: 2px 0 0;
padding: 8px 10px;
font-size: 12px;
line-height: 1.4;
color: var(--db-ink);
background: var(--db-accent-soft);
border-left: 2px solid var(--db-accent);
border-radius: 0 var(--db-r-ctrl) var(--db-r-ctrl) 0;
}
.db .db-assoc-flag {
flex: none;
font-size: 11px;
font-weight: 600;
letter-spacing: 0.04em;
text-transform: uppercase;
color: var(--db-accent);
}
/* --- Requires-slides table (section 5.1) --- */
.db .db-table-card {
background: var(--db-surface);
border: 1px solid var(--db-hairline);
border-radius: var(--db-r-card);
}
.db .db-table-scroll {
overflow-x: auto;
border-radius: var(--db-r-card);
}
.db .db-table {
border-collapse: collapse;
width: max-content;
min-width: 100%;
font-size: 13px;
}
.db .db-table th,
.db .db-table td {
padding: 10px 14px;
text-align: left;
vertical-align: top;
border-bottom: 1px solid var(--db-hairline);
}
.db .db-table tbody tr:last-child td {
border-bottom: none;
}
.db .db-table thead th {
position: sticky;
top: 0;
z-index: 1;
font-size: 12px;
font-weight: 600;
color: var(--db-ink-muted);
white-space: nowrap;
background: var(--db-panel);
}
.db .db-th-sticky,
.db .db-td-sticky {
position: sticky;
left: 0;
z-index: 2;
background: var(--db-surface);
font-weight: 500;
font-variant-numeric: tabular-nums;
white-space: nowrap;
}
.db .db-table thead .db-th-sticky {
z-index: 3;
background: var(--db-panel);
}
.db .db-table tbody tr:hover td {
background: var(--db-surface-2);
}
.db .db-table tbody tr:hover .db-td-sticky {
background: var(--db-surface-2);
}
.db .db-tag-row {
display: flex;
flex-wrap: wrap;
gap: 6px;
}
.db .db-slide-tag {
display: inline-flex;
align-items: center;
padding: 1px 8px;
font-size: 12px;
color: var(--db-accent);
background: var(--db-accent-soft);
border-radius: var(--db-r-ctrl);
}
.db .db-reason-tag,
.db .db-verdict-tag {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 1px 8px;
font-size: 12px;
color: var(--db-ink-muted);
background: var(--db-surface-2);
border-radius: var(--db-r-ctrl);
}
/* A verdict projected from values a reviewer has not confirmed reads provisional, not established: a warn
wash and an explicit UNREVIEWED chip (a text label, so the signal survives without colour), distinct from
a verdict resting on confirmed values (#224). */
.db .db-verdict-tag.is-provisional {
color: var(--warn, #b0851f);
background: color-mix(in srgb, var(--warn, #b0851f) 12%, var(--db-surface-2));
}
.db .db-verdict-unreviewed {
display: inline-flex;
align-items: center;
padding: 0 5px;
font-size: 9.5px;
font-weight: 700;
letter-spacing: 0.07em;
text-transform: uppercase;
color: var(--warn, #b0851f);
border: 1px solid color-mix(in srgb, var(--warn, #b0851f) 45%, transparent);
border-radius: 999px;
line-height: 1.6;
}
.db .db-provisional-note {
margin: 0 0 12px;
padding: 8px 12px;
font-size: 12px;
line-height: 1.5;
color: var(--db-ink-muted);
background: color-mix(in srgb, var(--warn, #b0851f) 8%, var(--db-surface-2));
border-radius: var(--db-r-ctrl);
}
.db .db-provisional-note .db-verdict-unreviewed {
vertical-align: middle;
}
.db .db-muted {
color: var(--db-ink-muted);
}
/* --- Loading skeleton at the true layout (section 6.4) --- */
.db .db-skel {
display: block;
background: linear-gradient(
100deg,
var(--db-surface-2) 30%,
var(--db-panel) 50%,
var(--db-surface-2) 70%
);
background-size: 200% 100%;
border-radius: var(--db-r-ctrl);
animation: db-shimmer 1.4s ease-in-out infinite;
}
.db .db-skel-metric {
width: 56px;
height: 32px;
margin-bottom: 6px;
}
.db .db-skel-line {
width: 96px;
height: 12px;
}
.db .db-skel-card {
height: 240px;
border-radius: var(--db-r-card);
}
@keyframes db-shimmer {
0% {
background-position: 100% 0;
}
100% {
background-position: 0 0;
}
}
@media (prefers-reduced-motion: reduce) {
.db .db-skel {
animation: none;
}
.db .db-btn {
transition: none;
}
}
/* ===================================================================== */
/* Traceability affordance (#148): from a cohort value to its evidence */
/* region and its governing definition, each hop a labelled link. Shared */
/* layout here; the accent colour is scoped to each host view's token. */
/* ===================================================================== */
.trace-links {
display: inline-flex;
align-items: center;
gap: 6px;
white-space: nowrap;
}
.trace-link {
display: inline-flex;
align-items: center;
gap: 3px;
font-size: 11px;
font-weight: 600;
letter-spacing: 0.01em;
text-decoration: none;
}
.trace-link:hover {
text-decoration: underline;
}
.trace-link:focus-visible {
outline: 2px solid currentColor;
outline-offset: 2px;
border-radius: 3px;
}
.trace-ic {
font-size: 13px;
line-height: 1;
}
/* Export: the pair rides as a hover/focus-revealed overlay pinned to the cell's right edge, so it never
widens the dense table or shifts its layout at rest. */
.ex .ex-cell {
position: relative;
}
.ex .trace-link {
color: var(--ex-accent);
}
.ex .trace-sep {
color: var(--ex-ink-muted);
font-size: 11px;
}
.ex .ex-cell .trace-links {
position: absolute;
top: 50%;
right: 6px;
transform: translateY(-50%);
padding: 2px 8px;
background: var(--ex-surface);
border: 1px solid var(--ex-hairline);
border-radius: var(--ex-r-ctrl);
opacity: 0;
pointer-events: none;
transition: opacity 0.12s ease;
}
.ex .ex-table tbody tr:hover .ex-cell .trace-links,
.ex .ex-cell:focus-within .trace-links {
opacity: 1;
pointer-events: auto;
}
/* Dashboard: the field card header carries only the Definition hop, always shown, since a distribution is
a cohort aggregate with no single evidence region. */
.db .trace-link {
color: var(--db-accent);
}
.db .trace-sep {
color: var(--db-ink-muted);
font-size: 11px;
}
.db .db-field-head-actions {
flex: none;
display: inline-flex;
align-items: baseline;
gap: 10px;
}
.db .db-case-link {
color: var(--db-accent);
text-decoration: none;
font-weight: 500;
}
.db .db-case-link:hover {
text-decoration: underline;
}
@media (prefers-reduced-motion: reduce) {
.ex .ex-cell .trace-links {
transition: none;
}
}
/* Guided demo tour (DemoTour.tsx): a launch button, a spotlight that dims the page
except the target element, and a callout carrying the script line. Overlay only,
so it changes no existing surface. */
.tour-launch {
position: fixed;
right: 20px;
bottom: 20px;
z-index: 800;
padding: 9px 16px;
border-radius: var(--r-ctrl);
border: 1px solid var(--accent);
background: var(--accent);
color: #fff;
font: inherit;
font-weight: 500;
box-shadow: 0 4px 16px rgba(14, 20, 32, 0.16);
cursor: pointer;
}
.tour-launch:hover {
filter: brightness(1.08);
}
.tour {
position: fixed;
inset: 0;
z-index: 900;
}
/* Transparent; the spotlight box-shadow supplies the dim, this only blocks page clicks. */
.tour-blocker {
position: fixed;
inset: 0;
z-index: 900;
}
.tour-spot {
position: fixed;
z-index: 901;
border-radius: var(--r-ctrl);
box-shadow: 0 0 0 9999px rgba(12, 18, 32, 0.55);
outline: 2px solid var(--accent);
pointer-events: none;
transition: top 160ms ease-out, left 160ms ease-out, width 160ms ease-out, height 160ms ease-out;
}
.tour-callout {
position: fixed;
z-index: 902;
width: 340px;
padding: 16px 18px;
background: var(--surface);
border: 1px solid var(--hairline);
border-radius: var(--r-card);
box-shadow: 0 8px 32px rgba(14, 20, 32, 0.18);
}
.tour-count {
margin: 0 0 6px;
font-size: 12px;
letter-spacing: 0.04em;
color: var(--ink-muted);
}
.tour-title {
margin: 0 0 6px;
font-size: 16px;
font-weight: 600;
color: var(--ink);
}
.tour-body {
margin: 0 0 14px;
font-size: 13px;
line-height: 1.5;
color: var(--ink);
}
.tour-actions {
display: flex;
align-items: center;
justify-content: space-between;
}
.tour-nav {
display: flex;
gap: 8px;
}
.tour-callout button {
padding: 6px 14px;
border-radius: var(--r-ctrl);
border: 1px solid var(--hairline);
background: var(--surface);
color: var(--ink);
font: inherit;
cursor: pointer;
}
.tour-callout button:hover:not(:disabled) {
border-color: var(--accent);
}
.tour-callout button:disabled {
opacity: 0.45;
cursor: default;
}
.tour-callout .tour-next {
border-color: var(--tour-accent, var(--accent));
background: var(--tour-accent, var(--accent));
color: #fff;
}
.tour-exit {
color: var(--ink-muted);
}
@media (prefers-reduced-motion: reduce) {
.tour-spot {
transition: none;
}
}
/* Interactive tour step: a "Your turn" pill and a pulsing spotlight that invites the viewer to act. */
.tour-turn {
display: inline-block;
margin-right: 8px;
padding: 1px 8px;
border-radius: 999px;
background: var(--accent-soft);
color: var(--tour-accent, var(--accent));
font-weight: 600;
letter-spacing: 0.02em;
text-transform: none;
}
/* Per-persona colour: a side stripe on the callout and the role name, keyed to the picker legend. */
.tour-callout-story {
border-left: 4px solid var(--tour-accent, var(--accent));
}
.tour-role {
color: var(--tour-accent, var(--accent));
font-weight: 600;
text-transform: none;
}
/* Story picker: choose which role's walkthrough to run. */
/* The story picker is a deep-navy emphasis block: white heading, muted body, role cards that lift on
hover. It matches the dark demo chrome and the brand's dark hero cards. */
.tour-picker {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 902;
width: 440px;
max-width: calc(100vw - 32px);
padding: 30px 32px;
background: var(--block-bg);
border-radius: 20px;
box-shadow: 0 30px 80px rgba(6, 9, 14, 0.5);
}
.tour-picker .tour-count {
color: var(--block-ink-muted);
font-size: 13px;
}
.tour-picker .tour-title {
color: var(--block-ink);
font-size: 22px;
font-weight: 500;
letter-spacing: -0.01em;
}
.tour-picker .tour-body {
color: var(--block-ink-muted);
font-size: 15px;
}
.tour-picker .tour-exit {
background: transparent;
border: none;
padding: 8px 14px;
border-radius: 999px;
color: rgba(255, 255, 255, 0.6);
cursor: pointer;
transition: color 140ms ease, background 140ms ease;
}
.tour-picker .tour-exit:hover {
color: #ffffff;
background: rgba(255, 255, 255, 0.08);
}
.tour-story-list {
display: flex;
flex-direction: column;
gap: 10px;
margin: 18px 0 4px;
}
.tour-story {
display: flex;
align-items: center;
gap: 14px;
padding: 16px 18px;
text-align: left;
border: none;
border-radius: 15px;
background: var(--block-bg-2);
cursor: pointer;
transition: background 160ms ease, transform 160ms ease, box-shadow 160ms ease;
}
.tour-story:hover {
background: color-mix(in srgb, var(--block-accent) 14%, var(--block-bg-2));
transform: translateY(-1px);
box-shadow: 0 10px 26px rgba(0, 0, 0, 0.32);
}
.tour-story-dot {
width: 32px;
height: 32px;
border-radius: 9px;
background: var(--tour-accent, var(--accent));
flex: none;
}
.tour-story-text {
display: flex;
flex-direction: column;
gap: 3px;
}
.tour-story-label {
font-size: 15px;
font-weight: 600;
color: var(--block-ink);
}
.tour-story-blurb {
font-size: 13px;
line-height: 1.45;
color: var(--block-ink-muted);
}
.tour-spot-live {
animation: tour-pulse 1.6s ease-in-out infinite;
}
@keyframes tour-pulse {
0%,
100% {
box-shadow: 0 0 0 9999px rgba(12, 18, 32, 0.45), 0 0 0 3px var(--tour-accent, var(--accent));
}
50% {
box-shadow: 0 0 0 9999px rgba(12, 18, 32, 0.45), 0 0 0 7px var(--accent-soft);
}
}
@media (prefers-reduced-motion: reduce) {
.tour-spot-live {
animation: none;
box-shadow: 0 0 0 9999px rgba(12, 18, 32, 0.45), 0 0 0 3px var(--accent);
}
}
/* Escalation queue (#161): the pathologist's cross-case list of escalated fields, section-5.1 table. */
.escalations .eyebrow {
margin: 0 0 4px;
font-size: 12px;
font-weight: 600;
letter-spacing: 0.06em;
text-transform: uppercase;
color: var(--ink-muted);
}
.escalations .esc-lede {
max-width: 68ch;
color: var(--ink-muted);
margin: 4px 0 18px;
}
.escalations .esc-loading,
.escalations .esc-empty {
color: var(--ink-muted);
}
.escalations .esc-table {
width: 100%;
border-collapse: collapse;
}
.escalations .esc-table thead th {
position: sticky;
top: 0;
z-index: 1;
padding: 9px 12px;
background: var(--panel);
border-bottom: 1px solid var(--hairline);
font-size: 12px;
font-weight: 600;
letter-spacing: 0.04em;
color: var(--ink-muted);
text-align: left;
}
.escalations .esc-table tbody td {
padding: 11px 12px;
border-bottom: 1px solid var(--hairline);
font-size: 13px;
vertical-align: top;
}
.escalations .esc-table tbody tr {
transition: background 120ms ease-out;
}
.escalations .esc-table tbody tr:hover {
background: var(--surface-2);
}
.escalations .esc-case {
font-weight: 500;
font-variant-numeric: tabular-nums;
letter-spacing: 0.02em;
color: var(--accent);
text-decoration: none;
}
.escalations .esc-case:hover {
text-decoration: underline;
}
.escalations .esc-value {
font-weight: 500;
}
.escalations .esc-nodraft {
color: var(--ink-muted);
font-weight: 400;
font-style: italic;
}
.escalations .esc-note {
color: var(--ink-muted);
max-width: 42ch;
}
@media (prefers-reduced-motion: reduce) {
.escalations .esc-table tbody tr {
transition: none;
}
}
/* Scripted-playback cursor: a dot that glides to each action target so the viewer follows the process. */
.tour-cursor {
position: fixed;
z-index: 903;
width: 18px;
height: 18px;
margin: -9px 0 0 -9px;
border-radius: 999px;
background: var(--tour-accent, var(--accent));
border: 2px solid #fff;
box-shadow: 0 2px 8px rgba(14, 20, 32, 0.35);
pointer-events: none;
transition: left 500ms ease-in-out, top 500ms ease-in-out;
}
@media (prefers-reduced-motion: reduce) {
.tour-cursor {
transition: none;
}
}
/* Pagination footer: shared by the worklist and the dashboard slide-order table (global house tokens). */
.pager {
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
gap: 12px;
margin-top: 12px;
padding-top: 10px;
font-size: 13px;
color: var(--ink-muted);
}
.pager-range {
font-variant-numeric: tabular-nums;
}
.pager-controls {
display: flex;
align-items: center;
gap: 10px;
}
.pager-page {
font-variant-numeric: tabular-nums;
color: var(--ink-muted);
}
.pager-btn {
font: inherit;
font-size: 13px;
font-weight: 500;
height: 32px;
padding: 0 12px;
color: var(--ink);
background: var(--surface);
border: 1px solid var(--hairline);
border-radius: var(--r-ctrl);
cursor: pointer;
}
.pager-btn:hover:not(:disabled) {
border-color: var(--accent);
color: var(--accent);
}
.pager-btn:disabled {
opacity: 0.45;
cursor: default;
}
/* Mobile grid overrides placed after the base grid rules above, so they win the cascade (a media query
adds no specificity, so an earlier override would otherwise be shadowed by a later base rule). */
@media (max-width: 760px) {
.db .db-field-grid {
grid-template-columns: 1fr;
}
.case-review-body,
.cr .cr-workspace {
grid-template-columns: 1fr;
}
}
/* The live model-call trace (#221): the transparency panel over GET /api/trace. */
.trace .trace-lede {
max-width: 68ch;
margin: 0 0 18px;
color: var(--ink-muted);
font-size: 14px;
line-height: 1.5;
}
.trace .trace-summary {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 10px 20px;
padding: 12px 16px;
margin-bottom: 16px;
background: var(--surface-2);
border: 1px solid var(--hairline);
border-radius: 10px;
font-size: 13px;
color: var(--ink-muted);
}
.trace .trace-stat b {
color: var(--ink);
font-weight: 600;
}
.trace .trace-live {
margin-left: auto;
display: inline-flex;
align-items: center;
gap: 6px;
cursor: pointer;
}
.trace .trace-table {
width: 100%;
border-collapse: collapse;
}
.trace .trace-table thead th {
position: sticky;
top: 0;
z-index: 1;
padding: 9px 12px;
background: var(--panel);
border-bottom: 1px solid var(--hairline);
font-size: 12px;
font-weight: 600;
letter-spacing: 0.04em;
color: var(--ink-muted);
text-align: left;
white-space: nowrap;
}
.trace .trace-table tbody td {
padding: 10px 12px;
border-bottom: 1px solid var(--hairline);
font-size: 13px;
vertical-align: top;
white-space: nowrap;
}
.trace .trace-table tbody tr:hover {
background: var(--surface-2);
}
.trace .trace-time {
color: var(--ink-muted);
font-variant-numeric: tabular-nums;
}
.trace .trace-ctx,
.trace .trace-reqid code {
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
font-size: 12px;
color: var(--ink-muted);
}
.trace .trace-resolved {
font-weight: 600;
}
.trace .trace-tokens,
.trace .trace-cache {
font-variant-numeric: tabular-nums;
}
.trace .trace-empty {
padding: 24px 0;
color: var(--ink-muted);
font-size: 14px;
}
/* Not-found catch-all (#233): the client-drawn 404 for a path no route matches. */
.not-found .eyebrow {
margin: 0 0 4px;
font-size: 12px;
font-weight: 600;
letter-spacing: 0.06em;
text-transform: uppercase;
color: var(--ink-muted);
}
.not-found .nf-lede {
max-width: 68ch;
color: var(--ink-muted);
margin: 4px 0 18px;
}
.not-found .nf-path {
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
font-size: 14px;
color: var(--ink);
}