/* =============================================
   Framework
   ---------------------------------------------
   Component + utility layer. Depends on a theme
   stylesheet (e.g. theme-01-powder-blue.css)
   for color tokens. Theme MUST be linked first.
   ============================================= */


/* =============================================
   Non-color tokens
   ============================================= */
:root {
    --font-body: "Geist", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
    --font-mono: "Geist Mono", ui-monospace, "SFMono-Regular", Menlo, Monaco, monospace;
    --radius:         8px;   /* cards, panels, decorative icons */
    --radius-control: 4px;   /* inputs, buttons, form controls */
}


/* =============================================
   Reset + Base
   ============================================= */
*,
*::before,
*::after {
    box-sizing: border-box;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
}

html, body {
    margin: 0;
    padding: 0;
}

/* The HTML `hidden` attribute is the standards-compliant way to hide an
   element, but our component CSS sets explicit `display:` values that win
   over the user-agent default. Restore the intended semantics globally so
   `<button hidden>` actually hides — otherwise every component that uses
   the attribute has to ship its own toggle class. */
[hidden] { display: none !important; }

/* Reusable spin animation — used on inline loaders (e.g. while an AJAX
   button's request is in flight). Pair with the Lucide `icon-loader`
   symbol: <svg class="is-spinning"><use href="#icon-loader"/></svg>. */
@keyframes spin {
    to { transform: rotate(360deg); }
}
.is-spinning {
    animation: spin 800ms linear infinite;
    transform-origin: center;
}

body {
    font-family: var(--font-body);
    font-size: 14px;
    line-height: 1.55;        /* Tailwind-docs-style breathing room */
    color: var(--color-on-background);
    background: #FFFFFF;
}

input, textarea, select, button {
    font-family: inherit;
    font-size: inherit;
    color: inherit;
}

th { font-weight: 600; }


/* =============================================
   Page chrome
   ============================================= */
.page {
    min-height: 100vh;
    display: flex;
    flex-direction: column;
}

.page-header,
.page-footer {
    padding: 16px 32px;
    display: flex;
    align-items: center;
}
.page-header { justify-content: flex-end; }
.page-footer { justify-content: space-between; }

.flex-center {
    flex: 1;
    display: flex;
    align-items: center;
    justify-content: center;
}


/* =============================================
   Typography utilities
   ---------------------------------------------
   Single typographic scale used by ALL pages.
   Don't override font-size/weight in templates —
   reach for one of these classes instead.

     Heading scale (all weight 700):
       .text-display  30 px  — hero / marketing
       .text-h1       26 px  — section hero
       .page-title    22 px  — in-app page titles
       .text-h2       20 px  — secondary headings
       .section-title 16 px  — group label (weight 600)
       .text-eyebrow  11 px  — small uppercase chip (weight 700)

     Body scale (weight 400):
       .text-body-lg  15 px
       .text-body     14 px  (default)
       .text-body-sm  13 px
       .text-caption  12 px

     Weights (use the .text-* utilities, never <strong>):
       400  body, sidebar items inactive
       500  hover / active states, semi-emphasis  → .text-medium
       600  badges, buttons, section labels,
            user names, page-btn                  → .text-semibold
       700  headings, eyebrows, table headers, brand → .text-bold

     Color tiers:
       .text-primary  — accent
       .text-muted    — subtitles, descriptions
       (default)      — body, headings
       on-surface-muted — small/eyebrow text only
   ============================================= */
.text-display {
    font-size: 26px;
    font-weight: 600;
    line-height: 1.15;
    letter-spacing: -0.025em;
}
.text-h1 {
    font-size: 22px;
    font-weight: 600;
    line-height: 1.2;
    letter-spacing: -0.02em;      /* tighter tracking — Tailwind-style */
}
.text-h2 {
    font-size: 18px;
    font-weight: 600;
    line-height: 1.3;
    letter-spacing: -0.015em;
}
.text-body-lg { font-size: 15px; }
.text-body    { font-size: 14px; }
.text-body-sm { font-size: 13px; }
.text-caption { font-size: 12px; }

.text-primary  { color: var(--color-primary); }
.text-muted    { color: var(--color-on-surface-variant); }
.text-warning-tone { color: var(--color-warning-600); }   /* accent for counts (e.g. "2 open") */
.text-italic   { font-style: italic; }

.text-medium   { font-weight: 500; }
.text-semibold { font-weight: 600; }
.text-bold     { font-weight: 700; }


/* =============================================
   Prose - for content-heavy pages (docs, articles, help)
   ---------------------------------------------
   Use on a wrapper element. Bumps body to 16px and
   line-height to 1.7 for relaxed reading flow,
   constrains line length to 65ch (optimal reading).
   Inspired by tailwindcss.com/docs body typography.
   ============================================= */
.prose {
    max-width: 65ch;
    font-size: 16px;
    line-height: 1.7;
    color: var(--color-on-surface);
}
.prose h1 { font-size: 28px; font-weight: 600; line-height: 1.2; letter-spacing: -0.025em; margin: 0 0 16px 0; }
.prose h2 { font-size: 22px; font-weight: 600; line-height: 1.25; letter-spacing: -0.02em; margin: 32px 0 12px 0; }
.prose h3 { font-size: 17px; font-weight: 600; line-height: 1.3; letter-spacing: -0.01em; margin: 24px 0 8px 0; }
.prose p { margin: 0 0 16px 0; }
.prose code {
    font-family: var(--font-mono);
    font-size: 0.9em;
    padding: 2px 6px;
    background: var(--color-neutral-75);
    border-radius: var(--radius-control);
}
.prose a {
    color: var(--color-primary);
    text-decoration: none;
}
.prose a:hover {
    text-decoration: underline;
    text-underline-offset: 2px;
}

.text-eyebrow {
    font-size: 11px;
    font-weight: 700;
    color: var(--color-on-surface-muted);
    text-transform: uppercase;
    letter-spacing: 0.08em;
}


/* =============================================
   Links
   - Tailwind-docs-style: no underline by default,
     underline on hover. Color always = primary.
   ============================================= */
.link,
.link-strong,
.link-eyebrow {
    color: var(--color-primary);
    text-decoration: none;
    transition: text-decoration-color 120ms;
}
.link:hover,
.link-strong:hover,
.link-eyebrow:hover {
    text-decoration: underline;
    text-underline-offset: 2px;
}
.link-strong {
    font-weight: 700;
}
.link-eyebrow {
    font-size: 10px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.08em;
}


/* =============================================
   Form fields
   ============================================= */
.field {
    margin-bottom: 14px;
}

.field-label {
    display: block;
    margin-bottom: 8px;
    font-size: 10px;
    font-weight: 700;
    color: var(--color-on-surface-muted);
    text-transform: uppercase;
    letter-spacing: 0.08em;
}

.field-row {
    display: flex;
    justify-content: space-between;
    align-items: flex-end;
    margin-bottom: 8px;
}
.field-row .field-label { margin-bottom: 0; }

.input {
    width: 100%;
    height: 40px;
    padding: 0 16px;
    font-size: 14px;
    color: var(--color-on-surface);
    background: var(--color-surface-container-low);
    border: 1px solid var(--color-outline-variant);
    border-radius: var(--radius-control);
    outline: none;
    transition: border-color 120ms;
}
.input::placeholder { color: var(--color-on-surface-muted); }
.input:focus        { border-color: var(--color-primary); }
.input--mono {
    font-family: var(--font-mono);
    letter-spacing: 2px;
}

.checkbox-row {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-bottom: 14px;
    font-size: 13px;
    font-weight: 500;
    color: var(--color-on-surface-muted);
    cursor: pointer;
}
.checkbox-row input[type="checkbox"] {
    appearance: none;
    -webkit-appearance: none;
    flex-shrink: 0;
    position: relative;
    width: 16px;
    height: 16px;
    margin: 0;
    background: var(--color-surface);
    border: 1px solid var(--color-outline);
    border-radius: var(--radius-control);
    cursor: pointer;
    transition: background-color 120ms, border-color 120ms;
}
.checkbox-row input[type="checkbox"]:hover {
    border-color: var(--color-primary);
}
.checkbox-row input[type="checkbox"]:checked {
    background: var(--color-primary);
    border-color: var(--color-primary);
}
.checkbox-row input[type="checkbox"]:checked::after {
    content: "";
    position: absolute;
    top: 50%;
    left: 50%;
    width: 4px;
    height: 8px;
    border: solid var(--color-on-primary);
    border-width: 0 2px 2px 0;
    transform: translate(-50%, -60%) rotate(45deg);
}
.checkbox-row input[type="checkbox"]:focus-visible {
    outline: 2px solid var(--color-primary);
    outline-offset: 2px;
}


/* =============================================
   Buttons

   Sizing system (pick ONE size per row so heights line up):
     .btn           → 32px medium — toolbars, table actions, wizard footers, filter rows
     .btn--lg       → 40px large  — sits next to .input / .select (which are 40px)

   Tone modifiers:
     .btn--primary  → solid primary CTA
     .btn--outline  → quiet secondary action
     .btn--ghost    → tertiary/icon-adjacent action (transparent)

   For external-data pulls, use one of: .url-fetcher (full panel + integrated CTA) or
   .icon-btn--fetch (compact, icon-only). Both share the violet/tertiary tint.

   Width:
     default is full-width; add .btn--auto to size to content.
   ============================================= */
.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    width: 100%;
    height: 32px;
    padding: 0 12px;
    border: none;
    border-radius: var(--radius-control);
    font-family: inherit;
    font-size: 13px;
    font-weight: 500;
    text-decoration: none;       /* prevent <a class="btn"> picking up browser underline */
    cursor: pointer;
    transition: filter 120ms;
}
.btn:hover {
    text-decoration: none;       /* and on hover (link-style :hover underline rule shouldn't kick in) */
    filter: brightness(0.96);
}

.btn--primary {
    background: var(--color-primary);
    color: var(--color-on-primary);
}
.btn--outline {
    background: var(--color-surface);
    color: var(--color-on-surface-variant);   /* medium-emphasis — secondary action, won't out-weigh primary CTA */
    border: 1px solid var(--color-outline-variant);
}
.btn--outline:hover {
    background: var(--color-neutral-50);
    color: var(--color-on-surface);            /* lift to full emphasis on hover */
    filter: none;
}
/* Danger: solid red CTA for destructive confirmations. */
.btn--danger {
    background: var(--color-error-600);
    color: #fff;
}
.btn--danger:hover {
    background: var(--color-error-700);
    filter: none;
}

/* Tonal: clearly coloured (always-available action) but not as loud as
   filled primary. Light tint bg + darker primary text. */
.btn--tonal {
    background: var(--color-primary-75);
    color: var(--color-primary-700);
}
.btn--tonal:hover {
    background: var(--color-primary-100);
    filter: none;
}




/* =============================================
   Alerts
   ============================================= */
.alert {
    padding: 10px 14px;
    border-radius: var(--radius);
    font-size: 13px;
    margin-bottom: 14px;
}
.alert--error   { background: var(--color-error-container);   color: var(--color-on-error-container); }
.alert--success { background: var(--color-success-container); color: var(--color-on-success-container); }
.alert--warning { background: var(--color-warning-container); color: var(--color-on-warning-container); }
.alert--info    { background: var(--color-info-container);    color: var(--color-on-info-container); }


/* =============================================
   Divider
   ============================================= */
.divider {
    display: flex;
    align-items: center;
    margin: 20px 0;
}
.divider::before,
.divider::after {
    content: "";
    flex: 1;
    height: 1px;
    background: var(--color-outline-variant);
}
.divider-text {
    padding: 0 16px;
    font-size: 11px;
    font-weight: 700;
    color: var(--color-on-surface-muted);
    text-transform: uppercase;
    letter-spacing: 0.08em;
}


/* =============================================
   Brand block
   ============================================= */
.brand {
    display: flex;
    align-items: center;
    gap: 14px;
}
.brand-icon {
    width: 44px;
    height: 44px;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 4px;
    background: transparent;
    overflow: hidden;
}
.brand-icon > svg,
.brand-icon > img {
    width: 100%;
    height: 100%;
    object-fit: contain;
    display: block;
}
.brand-text {
    font-size: 28px;
    font-weight: 700;
    color: var(--color-on-surface);
}


/* =============================================
   Feature list (checkmark items)
   ============================================= */
.feature-list {
    list-style: none;
    padding: 0;
    margin: 0;
    display: flex;
    flex-direction: column;
    gap: 10px;
}
.feature-item {
    display: flex;
    align-items: center;
    gap: 12px;
    font-size: 13px;
    font-weight: 500;
    color: var(--color-on-surface);
}
.feature-icon {
    flex: 0 0 auto;
    width: 20px;
    height: 20px;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: var(--radius);
    background: var(--color-primary);
    color: var(--color-on-primary);
}
.feature-icon > svg {
    width: 12px;
    height: 12px;
}


/* =============================================
   Table scrollbar
   ============================================= */
.table-scrollbar::-webkit-scrollbar { width: 6px; }
.table-scrollbar::-webkit-scrollbar-track {
    background: var(--color-surface-container-low);
    border-radius: 10px;
}
.table-scrollbar::-webkit-scrollbar-thumb {
    background: var(--color-outline);
    border-radius: 10px;
}


/* =============================================
   App shell (sidebar + main)
   ============================================= */
.app-shell {
    flex: 1;
    display: flex;
    min-height: 0;
}


/* =============================================
   Sidebar (overlay-on-hover pattern)
   - 64px collapsed (icons only); 240px on :hover
   - position:fixed so expanded state overlays
     the main content rather than reflowing it
   ============================================= */
.sidebar {
    position: fixed;
    top: 0;
    left: 0;
    width: 64px;
    height: 100vh;
    background: var(--color-surface);
    border-right: 1px solid var(--color-outline-variant);
    display: flex;
    flex-direction: column;
    overflow: visible;             /* allow hover tooltips to escape the right edge */
    transition: width 200ms ease, box-shadow 200ms ease;
    z-index: 10;
}
.sidebar[data-expanded] {
    width: 240px;
    box-shadow: 4px 0 24px rgba(0, 0, 0, 0.06);
}

.sidebar-header {
    flex: 0 0 auto;
    height: 72px;
    padding: 16px;
    display: flex;
    align-items: center;
    gap: 12px;
}
.sidebar:not([data-expanded]) .sidebar-header {
    justify-content: center;
    padding: 16px 0;
}
.sidebar:not([data-expanded]) .sidebar-header .brand-text { display: none; }
.sidebar-header .brand-icon {
    flex-shrink: 0;
    width: 32px;
    height: 32px;
}
.sidebar-header .brand-icon > svg,
.sidebar-header .brand-icon > img {
    width: 100%;
    height: 100%;
    object-fit: contain;
    display: block;
}
.sidebar-header .brand-text {
    flex: 1;
    min-width: 0;
    font-size: 18px;
}

.sidebar-nav {
    flex: 1;
    min-height: 0;
    padding: 8px 12px;
    display: flex;
    flex-direction: column;
    gap: 2px;
    overflow: visible;             /* allow hover tooltips to escape the right edge */
}

.sidebar-section-title {
    margin-top: 16px;
    padding: 8px 12px;
    font-size: 10px;
    font-weight: 700;
    color: var(--color-on-surface-muted);
    text-transform: uppercase;
    letter-spacing: 0.08em;
}

.sidebar-item {
    position: relative;            /* anchor for the hover tooltip ::after */
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    gap: 12px;
    height: 40px;
    padding: 0 10px;
    border-radius: var(--radius-control);
    color: var(--color-on-surface-variant);
    font-size: 14px;
    font-weight: 400;
    text-decoration: none;
    transition: background-color 120ms, color 120ms, font-weight 120ms;
}
/* Log Out posts a form; display:contents lets its <button> sit in the nav's
   flex flow as if the form weren't there, and the button is reset to mirror
   an <a class="sidebar-item">. */
.sidebar-logout-form { display: contents; }
button.sidebar-item {
    width: 100%;
    background: none;
    border: none;
    font-family: inherit;
    text-align: left;
    cursor: pointer;
}

/* Hover popover — only when the sidebar is collapsed (labels are hidden).
   Reads the item's name from data-tooltip="…". Small dark pill that sits to
   the right of the icon, with a tiny ::before arrow pointing back at it. */
.sidebar:not([data-expanded]) .sidebar-item[data-tooltip]:hover::after {
    content: attr(data-tooltip);
    position: absolute;
    left: calc(100% + 14px);
    top: 50%;
    transform: translateY(-50%);
    padding: 5px 9px;
    background: var(--color-on-surface);
    color: var(--color-surface);
    border-radius: 6px;
    font-size: 12px;
    font-weight: 500;
    line-height: 1;
    white-space: nowrap;
    pointer-events: none;
    z-index: 100;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12);
}
.sidebar:not([data-expanded]) .sidebar-item[data-tooltip]:hover::before {
    content: "";
    position: absolute;
    left: calc(100% + 10px);
    top: 50%;
    width: 8px;
    height: 8px;
    background: var(--color-on-surface);
    transform: translateY(-50%) rotate(45deg);
    z-index: 99;
    pointer-events: none;
}
.sidebar-item:hover {
    background: var(--color-neutral-100);
    color: var(--color-on-surface);
    font-weight: 500;
}
.sidebar-item--active,
.sidebar-item--active:hover {
    background: var(--color-primary-75);
    color: var(--color-primary-600);
    font-weight: 500;
}
.sidebar-item__icon {
    flex: 0 0 auto;
    width: 18px;
    height: 18px;
    stroke-width: 1.25;       /* matches inactive nav text weight */
    transition: stroke-width 120ms;
}
.sidebar-item:hover .sidebar-item__icon,
.sidebar-item--active .sidebar-item__icon {
    stroke-width: 1.75;       /* thicker stroke matches text weight bump */
}

.sidebar-user {
    flex: 0 0 auto;
    border-top: 1px solid var(--color-outline-variant);
    padding: 12px;
    display: flex;
    align-items: center;
    gap: 12px;
    color: inherit;
}
.sidebar-user__info {
    flex: 1;
    min-width: 0;
}
.sidebar-user__name {
    font-size: 13px;
    font-weight: 600;
    color: var(--color-on-surface);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.sidebar-user__role {
    font-size: 12px;
    line-height: 1.3;
    color: var(--color-on-surface-muted);
}
/* Items that should fade in/out as the sidebar expands/collapses */
.sidebar-label,
.sidebar-section-title,
.sidebar-user__info {
    opacity: 0;
    transition: opacity 100ms ease;
    white-space: nowrap;
    pointer-events: none;
}
.sidebar[data-expanded] .sidebar-label,
.sidebar[data-expanded] .sidebar-section-title,
.sidebar[data-expanded] .sidebar-user__info {
    opacity: 1;
    pointer-events: auto;
}


/* =============================================
   App main content
   - margin-left reserves the collapsed sidebar
   - expanded sidebar OVERLAYS this content
   ============================================= */
.app-main {
    flex: 1;
    min-width: 0;
    margin-left: 64px;
    padding: 16px 40px 32px;
}


/* =============================================
   Page / section titles
   ============================================= */
.page-title {
    margin: 0 0 28px 0;
    font-size: 18px;
    font-weight: 600;
    letter-spacing: -0.015em;
    color: var(--color-on-surface);
}
.section-title {
    margin: 0 0 14px 0;
    font-size: 16px;
    font-weight: 500;
    line-height: 1.3;
    letter-spacing: -0.005em;
    color: var(--color-on-surface);
}


/* =============================================
   Breadcrumbs
   - Place above .page-title to show the current UI path.
   - Last segment uses .breadcrumb--current (no <a>).
   - Separator is the chevron-right icon from the sprite.
   ============================================= */
.breadcrumbs {
    display: flex;
    align-items: center;
    gap: 8px;
    margin: 0 0 8px 0;
    font-size: 13px;
}
.breadcrumb {
    color: var(--color-on-surface-muted);
    text-decoration: none;
    transition: color 120ms;
}
.breadcrumb:hover {
    color: var(--color-on-surface-variant);
}
.breadcrumb--current {
    color: var(--color-on-surface-variant);
    font-weight: 500;
}
.breadcrumb-separator {
    flex-shrink: 0;
    width: 14px;
    height: 14px;
    color: var(--color-on-surface-muted);
}


/* =============================================
   Card (generic surface container)
   ============================================= */
.card {
    background: var(--color-surface);
    border: 1px solid var(--color-outline-variant);
    border-radius: var(--radius);
    overflow: hidden;
}
/* When a row-menu inside the card is open, lift the card's overflow
   clipping so the dropdown can extend past the card's bottom edge.
   Without this, kebab menus on the last few rows of a short table
   (e.g. the surveyfeed admin) render with their lower items hidden
   under the card boundary. Closes back to ``hidden`` when the menu
   closes, preserving the rounded-corner clipping for normal content. */
.card:has(.row-menu--open) {
    overflow: visible;
}


/* =============================================
   Metric Card (rich version)
   - 3-row layout: top (icon + label + trend pill),
     main (value + sparkline), bottom (callout / context / pace)
   - Each card is hover-affordable; click navigation
     can be wired separately on the .metric-card--clickable
   ============================================= */
.metric-grid {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: 16px;
    margin-bottom: 32px;
}
.metric-card {
    display: flex;
    flex-direction: column;
    gap: 10px;
    padding: 16px;
    border-radius: var(--radius);
    border: 1px solid var(--color-outline-variant);
    background: var(--color-surface);
    transition: border-color 120ms;
}
.metric-card--clickable {
    cursor: pointer;
}
.metric-card--clickable:hover {
    border-color: var(--color-outline);
}

/* top row: icon + label + trend pill */
.metric-card__top {
    display: flex;
    align-items: center;
    gap: 8px;
}
.metric-card__icon {
    flex: 0 0 auto;
    width: 28px;
    height: 28px;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: var(--radius-control);
}
.metric-card__icon > svg {
    width: 14px;
    height: 14px;
}
.metric-card__icon--info    { background: var(--color-info-75);    color: var(--color-info-600); }
.metric-card__icon--error   { background: var(--color-error-75);   color: var(--color-error-600); }
.metric-card__icon--warning { background: var(--color-warning-75); color: var(--color-warning-600); }
.metric-card__icon--success { background: var(--color-success-75); color: var(--color-success-600); }

.metric-card__label {
    flex: 1;
    font-size: 11px;
    font-weight: 600;
    color: var(--color-on-surface-muted);
    text-transform: uppercase;
    letter-spacing: 0.05em;
}

/* main row: value + sparkline */
.metric-card__main {
    display: flex;
    align-items: end;
    justify-content: space-between;
    gap: 12px;
}
.metric-card__values {
    display: flex;
    align-items: baseline;
    gap: 4px;
    flex-wrap: wrap;
}
.metric-card__value {
    font-size: 22px;
    font-weight: 700;
    line-height: 1;
    color: var(--color-on-surface);
    letter-spacing: -0.01em;
}
.metric-card__unit {
    font-size: 13px;
    font-weight: 500;
    color: var(--color-on-surface-variant);
    margin-left: 1px;
    margin-right: 2px;
}
.metric-card__denominator {
    font-size: 14px;
    font-weight: 500;
    color: var(--color-on-surface-muted);
}

/* bottom row: callout / context (no border, sits close to value) */
.metric-card__bottom {
    margin-top: auto;
}

.metric-card__callout {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    color: var(--color-primary);
    font-size: 12px;
    font-weight: 500;
    text-decoration: none;
}
.metric-card__callout > svg {
    flex: 0 0 auto;
    width: 13px;
    height: 13px;
    stroke-width: 2;          /* arrow-right symbol no longer carries one — set here to keep the callout's weight */
}
.metric-card__callout > svg:last-child {
    transition: transform 120ms;
}
.metric-card__callout:hover > svg:last-child {
    transform: translateX(2px);
}

.metric-card__context {
    margin: 0;
    font-size: 12px;
    line-height: 1.4;
    color: var(--color-on-surface-muted);
}


/* =============================================
   Trend Pill
   ============================================= */
.trend-pill {
    display: inline-flex;
    align-items: center;
    gap: 3px;
    padding: 2px 8px;
    border-radius: 999px;
    font-size: 11px;
    font-weight: 600;
    line-height: 1.2;
    white-space: nowrap;
}
.trend-pill > svg {
    flex: 0 0 auto;
    width: 12px;
    height: 12px;
}
.trend-pill--success { background: var(--color-success-75); color: var(--color-success-600); }
.trend-pill--error   { background: var(--color-error-75);   color: var(--color-error-600); }
.trend-pill--warning { background: var(--color-warning-75); color: var(--color-warning-600); }
.trend-pill--info    { background: var(--color-info-75);    color: var(--color-info-600); }


/* =============================================
   Sparkline
   - Inline SVG; color comes from `color:` so the
     stroke + filled area share the metric's tone.
   ============================================= */
.sparkline {
    flex: 0 0 auto;
    width: 56px;
    height: 28px;
}
.sparkline--info    { color: var(--color-info-500); }
.sparkline--error   { color: var(--color-error-500); }
.sparkline--warning { color: var(--color-warning-500); }
.sparkline--success { color: var(--color-success-500); }


/* =============================================
   Tabs (underline style)
   ============================================= */
.tabs {
    display: flex;
    gap: 32px;
    padding: 0 24px;
    border-bottom: 1px solid var(--color-outline-variant);
}
.tab {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    height: 48px;
    padding: 0 4px;
    background: none;
    border: none;
    border-bottom: 2px solid transparent;
    color: var(--color-on-surface-variant);
    font-family: inherit;
    font-size: 14px;
    font-weight: 600;
    text-decoration: none;
    cursor: pointer;
    transition: color 120ms, border-color 120ms;
}
.tab:hover { color: var(--color-on-surface); }
.tab--active {
    color: var(--color-primary);
    border-bottom-color: var(--color-primary);
}
.tab-count {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 20px;
    height: 20px;
    padding: 0 6px;
    border-radius: 10px;
    background: var(--color-neutral-200);
    color: var(--color-on-surface-variant);
    font-size: 11px;
    font-weight: 700;
}
.tab--active .tab-count {
    background: var(--color-primary);
    color: var(--color-on-primary);
}


/* =============================================
   Data Table
   ============================================= */
.data-table {
    width: 100%;
    border-collapse: collapse;
}
/* Content-width variant. The default ``.data-table`` fills its
   container (``width: 100%``), so any leftover after the shrink-to-fit
   cells get their content width is split between the remaining
   flexible cells — fine when there are several wide text columns, but
   it leaves uncomfortable gaps when most columns are tight chips +
   counts. ``.data-table--fit`` opts that table into shrink-to-content
   so it sits compactly on the left and the container holds any
   surplus space (rather than the cells). Selector deliberately
   doubled up (``.data-table.data-table--fit``) so it outweighs the
   base ``.data-table { width: 100% }`` regardless of source order. */
.data-table.data-table--fit {
    width: auto;
}
.data-table th,
.data-table td {
    padding: 11px 12px;
    text-align: left;
    vertical-align: middle;
    border-bottom: 1px solid var(--color-outline-variant);
}
/* Compact variant — tighter rows (used on the projects screen). Only the
   vertical padding changes; horizontal padding + edge padding are untouched. */
.data-table--compact th,
.data-table--compact td {
    padding-top: 5px;
    padding-bottom: 5px;
}
.data-table th {
    font-size: 11px;
    font-weight: 600;
    color: var(--color-on-surface-muted);
    text-transform: uppercase;
    letter-spacing: 0.08em;
}
.data-table td {
    font-size: 13px;          /* aligned with .cell-stack__primary — single 13px tier for data cells */
    line-height: 1.4;
    color: var(--color-on-surface);
}
.data-table tbody tr:last-child td { border-bottom: none; }
.data-table th:first-child,
.data-table td:first-child {
    padding-left: 24px;
}
.data-table--priority th:first-child,
.data-table--priority td:first-child {
    width: 56px;
}
/* Center every cell except the one marked .col-primary (the "primary identifier"
   column — e.g. Project / Name — which stays left-aligned) and the last column
   (action menu, kept right-aligned by base data-table rules). Used on tables where
   most cells are short data (numbers, badges, chips). */
.data-table--center-cells thead th:not(.col-primary):not(:last-child),
.data-table--center-cells tbody td:not(.col-primary):not(:last-child) {
    text-align: center;
}
/* The primary identifier column reads as the row's anchor — slightly heavier
   weight (500) lifts it above sibling cells without the boldness of a heading.
   Matches .cell-stack__primary used inside more elaborate col-primary cells. */
.data-table td.col-primary { font-weight: 500; }

/* Cap the col-primary column at 400px so unusually long project
   titles can't stretch it. Under ``table-layout: auto`` (which the
   data-table uses) browsers treat ``max-width`` on cells as a hint
   that's frequently ignored — explicit ``width`` is read more
   reliably as a "this is how big the column should be" signal.
   Setting both, with ``!important`` to overcome any column-sizing
   slack the browser might still want to add, plus a hard-capped
   inline-block on the inner anchor (which is what feeds the column's
   preferred-width calculation), makes the cap stick across every
   browser. .cell-stack content is intentionally untouched so
   multi-line stacks (home / settings) keep wrapping. */
.data-table th.col-primary,
.data-table td.col-primary {
    width: 400px !important;
    max-width: 400px !important;
}
.data-table td.col-primary > a {
    display: block;
    width: 400px;
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    box-sizing: border-box;
}


/* Muted row — dims every cell (including the primary/label column) for
   disabled / inactive items. Toggle and action controls keep their normal
   color so they remain readable and clearly interactive. */
/* Muted row — every cell dims uniformly so badges, pills, progress bars,
   avatars, etc. all read as "kept but inactive". Opacity sits on each
   direct <td> (not the <tr>) so a single cell can opt back out — used
   for the status cell so its chip stays at full intensity and reads
   identical to the same chip in a non-muted row (Draft vs Archived). */
.data-table tr.row--muted > td              { opacity: 0.55; }
.data-table tr.row--muted > td.col-status   { opacity: 1; }

/* Linked-to row — when arriving via /users#u-<id> (e.g. clicking a reviewer
   avatar elsewhere), briefly tint the targeted user row so it's easy to spot. */
.data-table tr:target > td { background: var(--color-primary-75); }

/* Removing row — fade out before being detached from the DOM. */
.data-table tr {
    transition: opacity 200ms ease, transform 200ms ease;
}
.data-table tr.row--removing {
    opacity: 0;
    transform: translateX(-12px);
    pointer-events: none;
}


/* =============================================
   Toggle switch (binary on/off, e.g. user Active/Inactive)
   ============================================= */
.toggle {
    display: inline-flex;
    align-items: center;
    gap: 10px;
    cursor: pointer;
    user-select: none;
}
.toggle__input {
    position: absolute;
    opacity: 0;
    pointer-events: none;
    width: 0;
    height: 0;
}
.toggle__visual {
    position: relative;
    flex: 0 0 auto;
    width: 36px;
    height: 20px;
    background: var(--color-neutral-200);
    border-radius: 10px;
    transition: background 150ms;
}
.toggle__visual::after {
    content: "";
    position: absolute;
    top: 2px;
    left: 2px;
    width: 16px;
    height: 16px;
    border-radius: 50%;
    background: var(--color-surface);
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.15);
    transition: transform 150ms;
}
.toggle__input:checked + .toggle__visual {
    background: var(--color-success-300);
}
.toggle__input:checked + .toggle__visual::after {
    transform: translateX(16px);
}
.toggle__label {
    font-size: 13px;
    font-weight: 500;
    color: var(--color-on-surface-muted);
}
.toggle__input:checked ~ .toggle__label {
    color: var(--color-success-700);
}


/* =============================================
   Dashboard header right-side action cluster
   (search + button + filters on the same row as the page title)
   ============================================= */
.dashboard-header__actions {
    display: flex;
    gap: 12px;
    align-items: center;
}


/* =============================================
   Filter row trailing note (shield + helper text)
   ============================================= */
.filter-row__note {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    margin-left: auto;
    font-size: 12px;
    color: var(--color-on-surface-muted);
    line-height: 1.4;
}
.filter-row__note > svg {
    flex: 0 0 auto;
    width: 14px;
    height: 14px;
    color: var(--color-on-surface-muted);
    stroke-width: 1.5;
}


/* =============================================
   Action group (compact row of icon buttons in a table cell)
   ============================================= */
.action-group {
    display: inline-flex;
    gap: 4px;
}
.action-group > .icon-btn {
    width: 28px;
    height: 28px;
}
.action-group > .icon-btn > svg {
    width: 14px;
    height: 14px;
    stroke-width: 1.5;
}
.data-table th:last-child,
.data-table td:last-child  { padding-right: 24px; text-align: right; }


/* =============================================
   Priority indicator (H/M/L letter chip in tables)
   ============================================= */
.priority {
    width: 24px;
    height: 24px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: var(--radius-control);
    font-size: 12px;
    font-weight: 700;
    line-height: 1;
}
.priority--high   { background: var(--color-error-50);   color: var(--color-error-600); }
.priority--medium { background: var(--color-warning-50); color: var(--color-warning-600); }
.priority--low    { background: var(--color-success-50); color: var(--color-success-600); }


/* =============================================
   Badge / Status pill
   ============================================= */
.badge {
    display: inline-flex;
    align-items: center;
    padding: 4px 12px;
    border-radius: 999px;
    font-size: 12px;
    font-weight: 500;
}
.badge--neutral  { background: var(--color-neutral-75);   color: var(--color-neutral-600);  }
.badge--success  { background: var(--color-success-75);   color: var(--color-success-600);  }
.badge--warning  { background: var(--color-warning-75);   color: var(--color-warning-600);  }
.badge--info     { background: var(--color-info-75);      color: var(--color-info-600);     }
.badge--error    { background: var(--color-error-75);     color: var(--color-error-600);    }
.badge--tertiary { background: var(--color-tertiary-75);  color: var(--color-tertiary-600); }
.badge--lime     { background: var(--color-lime-75);      color: var(--color-lime-700);     }
.badge--archived { background: var(--color-neutral-75);   color: var(--color-neutral-400);  }
/* AI Review — a batch being analysed can't be worked on until the run
   finishes. Muted "locked" scheme (low-contrast grey, dashed hairline)
   so it reads as inactive rather than an inviting/actionable status; the
   slow pulse hints it's busy in the background, not simply idle. */
.badge--ai-review {
    background: var(--color-neutral-100);
    color: var(--color-on-surface-muted);
    border: 1px dashed var(--color-outline-variant);
    /* keep the pill height identical to borderless siblings */
    padding: 3px 11px;
    animation: badge-ai-review-pulse 1.8s ease-in-out infinite;
}
@keyframes badge-ai-review-pulse {
    0%, 100% { opacity: 1;    }
    50%      { opacity: 0.55; }
}
@media (prefers-reduced-motion: reduce) {
    .badge--ai-review { animation: none; }
}
/* Batch row locked for work while its AI run is in flight — dim every
   cell except the status badge (which stays legible + pulsing to explain
   why), and lay a faint grey wash over the row. The row's Open action
   still works, so an operator can peek at the live progress. */
.batch-row--locked { background: var(--color-neutral-25); }
.batch-row--locked > td:not(.col-status) { opacity: 0.55; }

/* Inline icon inside a badge (e.g. shield-user next to "Google SSO"). */
.badge .badge__icon {
    width: 13px;
    height: 13px;
    margin-right: 5px;
    margin-left: -2px;
    stroke-width: 1.75;
    flex: 0 0 auto;
}

/* Equal-width chips — used so the "Google SSO" and "Invited" pills both
   render at the same size. The chip is an inline-grid; the visible face
   and a hidden ghost (always the widest variant) share a single grid
   cell. Grid auto-sizes to its widest child, so every chip ends up that
   wide and the face just centers inside.

   No magic numbers — if a new variant is longer, you only need to swap
   the ghost label in the template. */
.chip-eq         { display: inline-grid; }
.chip-eq__face,
.chip-eq__ghost  {
    grid-row    : 1;
    grid-column : 1;
    display     : inline-flex;
    align-items : center;
    justify-content: center;
    white-space : nowrap;
}
.chip-eq__ghost {
    visibility  : hidden;
    pointer-events: none;
    user-select : none;
}

/* Dot modifier — prepends a small colored dot (inherits the badge's text color) */
.badge--dot::before {
    content: "";
    flex: 0 0 auto;
    width: 6px;
    height: 6px;
    margin-right: 6px;
    background: currentColor;
    border-radius: 999px;
}


/* =============================================
   Progress bar
   ============================================= */
.progress {
    display: flex;
    flex-direction: column;
    gap: 3px;
    min-width: 90px;         /* fits "10,230 / 10,230" without wrapping the label */
    max-width: 110px;
    margin: 0 auto;          /* centers the bar inside its table cell */
}
.progress__label {
    font-family: var(--font-mono);
    font-size: 11px;
    color: var(--color-on-surface-variant);
}
.progress__bar {
    width: 100%;
    height: 3px;
    background: var(--color-neutral-100);
    border-radius: 1.5px;
    overflow: hidden;
}
.progress__fill {
    height: 100%;
    border-radius: 1.5px;
    transition: width 200ms ease;
}
.progress--primary  .progress__fill { background: var(--color-primary-500); }
.progress--info     .progress__fill { background: var(--color-info-500);    }
.progress--success  .progress__fill { background: var(--color-success-500); }
.progress--warning  .progress__fill { background: var(--color-warning-500); }
.progress--error    .progress__fill { background: var(--color-error-500);   }
.progress--archived .progress__fill { background: var(--color-neutral-400); }


/* =============================================
   Avatar
   ============================================= */
.avatar {
    flex: 0 0 auto;
    width: 36px;
    height: 36px;
    border-radius: 50%;
    object-fit: cover;
    background: var(--color-neutral-200);
    color: var(--color-neutral-700);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 12px;
    font-weight: 600;
    overflow: hidden;
    text-decoration: none;          /* avatars used as links stay glyph-only */
}
/* Avatar rendered as an <a> (reviewer → user link): keep the avatar look,
   add a subtle hover affordance. */
a.avatar:hover { box-shadow: 0 0 0 2px var(--color-primary-200); }
.avatar--xs    { width: 24px; height: 24px; font-size: 10px; }
.avatar--empty { background: var(--color-neutral-200); }     /* no initials, gray placeholder */

/* Avatar stack: overlapping circles for reviewer/owner lists */
.avatar-stack {
    display: inline-flex;
    align-items: center;
}
.avatar-stack > .avatar {
    border: 2px solid var(--color-surface);
    margin-left: -8px;
}
.avatar-stack > .avatar:first-child { margin-left: 0; }
.avatar-stack__more {
    flex: 0 0 auto;
    width: 24px;
    height: 24px;
    border-radius: 50%;
    background: var(--color-neutral-100);
    color: var(--color-neutral-700);
    font-size: 10px;
    font-weight: 600;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: 2px solid var(--color-surface);
    margin-left: -8px;
}


/* =============================================
   Icon-only round button (notifications, actions)
   ============================================= */
.icon-btn {
    width: 32px;
    height: 32px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: 1px solid var(--color-outline-variant);
    border-radius: 50%;
    background: var(--color-surface);
    color: var(--color-on-surface-variant);
    cursor: pointer;
    transition: background-color 120ms, color 120ms, border-color 120ms;
}
.icon-btn:hover {
    background: var(--color-neutral-100);
    color: var(--color-on-surface);
}
.icon-btn > svg { width: 16px; height: 16px; stroke-width: 1.25; }

.icon-btn--ghost {
    border: none;
    background: transparent;
}

/* Tonal icon button: light primary tint, primary-coloured icon. Reads as
   "the action on this page" without being as loud as a filled primary. */
.icon-btn--tonal {
    background: var(--color-primary-75);
    color: var(--color-primary-700);
    border-color: var(--color-primary-100);
}
.icon-btn--tonal:hover {
    background: var(--color-primary-100);
    color: var(--color-primary-700);
    border-color: var(--color-primary-200);
}
.icon-btn--tonal > svg { width: 18px; height: 18px; stroke-width: 1.75; }

/* Destructive variant — persistent danger tone, used when the action
   would sever live state (e.g. force-idle a running fetch, kill a live
   worker). Visible danger by default so the operator sees the risk
   before clicking; deeper tint on hover. */
.icon-btn--danger {
    border-color: color-mix(in srgb, var(--color-error-600) 30%, transparent);
    background: var(--color-error-75);
    color: var(--color-error-600);
}
.icon-btn--danger:hover {
    background: var(--color-error-50, var(--color-error-75));
    color: var(--color-error-700, var(--color-error-600));
    border-color: color-mix(in srgb, var(--color-error-600) 55%, transparent);
}
.icon-btn--danger:disabled {
    background: transparent;
    color: var(--color-on-surface-disabled);
    border-color: var(--color-outline-variant);
    cursor: not-allowed;
}

/* Warning variant — persistent amber, used for recovery actions that
   are *expected* but worth a second look (recover stuck row, clear
   non-blocking error count). Less alarming than danger. */
.icon-btn--warning {
    border-color: color-mix(in srgb, var(--color-warning-600) 30%, transparent);
    background: var(--color-warning-75);
    color: var(--color-warning-600);
}
.icon-btn--warning:hover {
    background: var(--color-warning-50, var(--color-warning-75));
    color: var(--color-warning-700, var(--color-warning-600));
    border-color: color-mix(in srgb, var(--color-warning-600) 55%, transparent);
}
.icon-btn--warning:disabled {
    background: transparent;
    color: var(--color-on-surface-disabled);
    border-color: var(--color-outline-variant);
    cursor: not-allowed;
}

/* =============================================
   Fast tooltip — `.app-tooltip` portaled to <body>
   ────────────────────────────────────────────────
   Driven by the JS handler in app.js. The handler creates a single
   `<div class="app-tooltip">` on document.body and re-positions it
   on mouseover of any element with `data-tip="…"`. Living on body
   means the tip escapes every overflow container (table cells,
   modal bodies, the surveyfeed card), so long tips can run wider
   than their button's parent and still be fully visible. The
   handler also surfaces tips on `[disabled]` buttons, which is
   exactly when an operator most needs the "why is this disabled?"
   hint.

   Usage: `<button data-tip="Helpful label">…</button>`. ~120ms delay.
   ============================================= */
.app-tooltip {
    position: fixed;
    z-index: 10001;             /* above modals' backdrops too */
    top: -9999px;               /* parked off-screen until positioned */
    left: -9999px;
    padding: 6px 10px;
    background: var(--color-neutral-900, #111827);
    color: #fff;
    border-radius: 6px;
    font-size: 12px;
    font-weight: 500;
    line-height: 1.35;
    /* Wrap long tips instead of truncating. The tooltip lives on body
       so it can grow as wide as it needs; the max-width keeps very
       long captions readable instead of a one-line ribbon. */
    max-width: 320px;
    white-space: normal;
    word-wrap: break-word;
    box-shadow: 0 6px 16px rgba(0, 0, 0, 0.22);
    opacity: 0;
    pointer-events: none;       /* don't interfere with hover targets underneath */
    transition: opacity 120ms ease;
}
.app-tooltip--visible {
    opacity: 1;
}
/* Small arrow tucked under the tip (or above if the tip flipped). The
   JS sets `data-placement="top"|"bottom"` so we orient the arrow
   without needing a separate template. */
.app-tooltip::after {
    content: "";
    position: absolute;
    width: 0;
    height: 0;
    border-left: 5px solid transparent;
    border-right: 5px solid transparent;
}
.app-tooltip[data-placement="top"]::after {
    bottom: -4px;
    left: 50%;
    transform: translateX(-50%);
    border-top: 5px solid var(--color-neutral-900, #111827);
}
.app-tooltip[data-placement="bottom"]::after {
    top: -4px;
    left: 50%;
    transform: translateX(-50%);
    border-bottom: 5px solid var(--color-neutral-900, #111827);
}

/* Row-level action cluster: small inline buttons / controls in the right-most
   cell of a data-table row, aligned right. */
.row-actions {
    display: inline-flex;
    align-items: center;
    gap: 12px;
    justify-content: flex-end;
}


/* ---- New Project dropdown ----------------------------------------------
   Anchor for the trigger button; the menu opens below it, aligned to the
   right edge so it sits flush against the page's right side. */

.new-project-dropdown {
    position: relative;
    display: inline-block;
}
.new-project-dropdown__menu {
    display: none;
    position: absolute;
    top: calc(100% + 10px);          /* extra room for the callout arrow */
    right: 0;
    min-width: 150px;
    background: var(--color-surface);
    border: 1px solid var(--color-outline-variant);
    border-radius: var(--radius);
    box-shadow: 0 10px 28px var(--color-shadow), 0 4px 8px var(--color-shadow);
    padding: 6px;
    z-index: 50;
    flex-direction: column;
    gap: 2px;
}
/* Callout arrow pointing up to the trigger button. A rotated square with
   only the top + left borders renders as a clean upward arrow once you
   tuck the bottom-right half behind the menu surface. */
.new-project-dropdown__menu::before {
    content: '';
    position: absolute;
    top: -7px;
    right: 10px;                     /* roughly centres under the 32px trigger */
    width: 12px;
    height: 12px;
    background: var(--color-surface);
    border-top: 1px solid var(--color-outline-variant);
    border-left: 1px solid var(--color-outline-variant);
    transform: rotate(45deg);
}
.new-project-dropdown[data-open] .new-project-dropdown__menu { display: flex; }
/* Items mirror .sidebar-item: muted default colour, neutral-100 hover bg,
   on-surface text + weight bump on hover, primary tint when "active". */
.new-project-dropdown__item {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 8px 10px;
    border-radius: var(--radius-control);
    text-decoration: none;
    color: var(--color-on-surface-variant);
    font-size: 14px;
    font-weight: 400;
    transition: background-color 120ms, color 120ms, font-weight 120ms;
}
.new-project-dropdown__item:hover {
    background: var(--color-neutral-100);
    color: var(--color-on-surface);
    font-weight: 500;
}
.new-project-dropdown__icon {
    flex: 0 0 auto;
    width: 18px;
    height: 18px;
    stroke-width: 1.25;
    transition: stroke-width 120ms;
}
.new-project-dropdown__item:hover .new-project-dropdown__icon {
    stroke-width: 1.75;
}



/* =============================================
   Pagination
   ============================================= */
.pagination {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 16px 24px;
    border-top: 1px solid var(--color-outline-variant);
    font-size: 13px;
    color: var(--color-on-surface-variant);
}
.pagination__pages { display: flex; gap: 4px; }
.pagination__left  { display: flex; align-items: center; gap: 20px; }

/* Rows-per-page selector — compact native <select> with an inline chevron. */
.rows-per-page {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    color: var(--color-on-surface-variant);
}
.rows-per-page select {
    height: 28px;
    padding: 0 26px 0 10px;
    font-family: inherit;
    font-size: 13px;
    color: var(--color-on-surface);
    background-color: var(--color-surface);
    border: 1px solid var(--color-outline);
    border-radius: var(--radius-control);
    cursor: pointer;
    appearance: none;
    -webkit-appearance: none;
    background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%2390929B' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'><polyline points='6 9 12 15 18 9'/></svg>");
    background-repeat: no-repeat;
    background-position: right 8px center;
    background-size: 13px 13px;
}
.rows-per-page select:hover  { border-color: var(--color-neutral-300); }
.rows-per-page select:focus  { outline: none; border-color: var(--color-primary); }
.page-btn {
    width: 24px;
    height: 24px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: 1px solid var(--color-outline-variant);
    border-radius: var(--radius-control);
    background: var(--color-surface);
    color: var(--color-on-surface);
    font-family: inherit;
    font-size: 11px;
    font-weight: 600;
    cursor: pointer;
}
.page-btn--active {
    background: var(--color-primary);
    color: var(--color-on-primary);
    border-color: var(--color-primary);
}
.page-ellipsis {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 24px;
    color: var(--color-on-surface-muted);
    font-size: 13px;
}


/* =============================================
   Filter row (search + dropdown filters)
   ============================================= */
.filter-row {
    display: flex;
    gap: 12px;
    align-items: center;
    margin-bottom: 16px;
    flex-wrap: wrap;
}

/* Search input (input with a leading magnifying-glass icon) */
.search-input {
    position: relative;
    flex: 1;
    min-width: 320px;       /* fit "Search by project name or ID..." placeholder without truncation */
    max-width: 480px;
}
.search-input__icon {
    position: absolute;
    left: 12px;
    top: 50%;
    transform: translateY(-50%);
    width: 14px;
    height: 14px;
    color: var(--color-on-surface-muted);
    pointer-events: none;
}
.search-input > .input {
    height: 32px;          /* match adjacent buttons + filter pills for visual consistency */
    padding-left: 34px;
}


/* =============================================
   Two-line table cell (primary + secondary text)
   - Used for cells that need a name + subtitle
   ============================================= */
.cell-stack {
    display: flex;
    flex-direction: column;
    gap: 2px;
}
.cell-stack__primary {
    font-size: 13px;
    font-weight: 500;
    color: var(--color-on-surface);
    line-height: 1.4;
}
.cell-stack__secondary {
    font-size: 12px;
    color: var(--color-on-surface-muted);
    line-height: 1.3;
}


/* =============================================
   Dashboard header (page title + filter row)
   ============================================= */
.dashboard-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 20px;
    flex-wrap: wrap;
    gap: 12px;
}
.dashboard-header .page-title {
    margin: 0;
}
.dashboard-filters {
    display: flex;
    gap: 8px;
    flex-wrap: wrap;
}
.filter-pill {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 6px 12px;
    background: var(--color-surface);
    border: 1px solid var(--color-outline);
    border-radius: var(--radius-control);
    font-family: inherit;
    font-size: 13px;
    color: var(--color-on-surface);
    cursor: pointer;
    transition: background-color 120ms;
}
.filter-pill:hover {
    background: var(--color-neutral-100);
}
.filter-pill > svg {
    flex: 0 0 auto;
    width: 14px;
    height: 14px;
    color: var(--color-on-surface-muted);
    stroke-width: 1.5;
}
/* Native <select> variant — same look as the button-pill, native chevron
   replaced with a background-image so the trigger reads as a filter pill. */
select.filter-pill {
    appearance: none;
    -webkit-appearance: none;
    padding-right: 30px;
    background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%2390929B' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'><polyline points='6 9 12 15 18 9'/></svg>");
    background-repeat: no-repeat;
    background-position: right 10px center;
    background-size: 14px 14px;
}
select.filter-pill:focus {
    outline: none;
    border-color: var(--color-primary);
}
/* Icon-only variant — square pill for icon toggle buttons */
.filter-pill--icon-only {
    padding: 6px 8px;
    gap: 0;
}
.filter-pill--icon-only > svg {
    width: 16px;
    height: 16px;
}
/* Active state — non-default filter applied (default tone: primary) */
.filter-pill--active {
    background: var(--color-primary-75);
    border-color: var(--color-primary);
    color: var(--color-primary-700);
}
.filter-pill--active > svg {
    color: var(--color-primary);
}
.filter-pill--active:hover {
    background: var(--color-primary-75);
}

/* Warning-tone active state — for filters that highlight attention/risk
   (Flagged, Critical, Overdue, etc.). Combine with .filter-pill--active. */
.filter-pill--tone-warning.filter-pill--active {
    background: var(--color-warning-75);
    border-color: var(--color-warning);
    color: var(--color-warning-700);
}
.filter-pill--tone-warning.filter-pill--active > svg {
    color: var(--color-warning);
}
.filter-pill--tone-warning.filter-pill--active:hover {
    background: var(--color-warning-75);
}


/* =============================================
   Multi-select filter (pill trigger + checkbox popover)
   ============================================= */
.ms-filter { position: relative; display: inline-block; }

/* Active look — any value selected. The container gets ``.ms-filter
   --active`` from the server when there's at least one selected
   value, so the visual cue fires whether the trigger shows a
   single-value label ("APAC") or a "Region [2]" count chip. The
   border + text colour are enough on their own; a tinted background
   makes the row read as "selected and disabled" rather than
   "filter is applied". */
.ms-filter--active .ms-filter__trigger,
.ms-filter__trigger:has(.ms-filter__count) {
    border-color: var(--color-primary);
    color: var(--color-primary-700);
}
.ms-filter__count {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 16px;
    height: 16px;
    padding: 0 4px;
    margin-left: 2px;
    border-radius: 999px;
    background: var(--color-primary);
    color: var(--color-on-primary);
    font-size: 10px;
    font-weight: 700;
}
.ms-filter__panel {
    position: absolute;
    z-index: 50;
    top: calc(100% + 4px);
    left: 0;
    min-width: 200px;
    max-height: 300px;
    overflow-y: auto;
    padding: 6px;
    background: var(--color-surface);
    border: 1px solid var(--color-outline-variant);
    border-radius: var(--radius-control);
    box-shadow: 0 10px 28px rgba(15, 23, 42, 0.14);
    display: none;
}
.ms-filter--open .ms-filter__panel    { display: block; }
.ms-filter--open .ms-filter__trigger  { border-color: var(--color-primary); }
.ms-filter__opt {
    display: flex;
    align-items: center;
    gap: 9px;
    padding: 7px 10px;
    border-radius: 6px;
    cursor: pointer;
    font-size: 13px;
    color: var(--color-on-surface);
    white-space: nowrap;
}
.ms-filter__opt:hover { background: var(--color-neutral-100); }
.ms-filter__opt input {
    width: 15px;
    height: 15px;
    accent-color: var(--color-primary);
    cursor: pointer;
    flex: 0 0 auto;
}
/* Option label sits flexed between the checkbox and the optional status
   chip — when both are present the chip stays right-aligned against the
   panel's right edge so a row of batch options reads as ``id … chip``. */
.ms-filter__opt-label {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
}
.ms-filter__opt-chip {
    flex: 0 0 auto;
    margin-left: auto;
}
/* Smaller status chip inside the batches dropdown — the default .badge
   size dominates the row visually. Scoped to the batches filter so other
   dropdowns keep their default chip sizing. */
.ms-filter[data-param="batch"] .ms-filter__opt-chip {
    padding: 2px 8px;
    font-size: 10.5px;
    font-weight: 500;
    line-height: 1.4;
    /* Minimum breathing room before the chip — even when the row is
       narrow, ``margin-left: auto`` alone leaves the chip touching the
       batch id. The explicit 16px ensures the visual gap. */
    margin-left: 16px;
}

/* Reviewer batches dropdown — the option ids are Decipher-style batch
   numbers (e.g. ``A001-0415-001``) that read better in a tabular
   monospace face. Scoped via the ``data-param="batch"`` attribute so
   the other dropdowns in the app stay in the default proportional
   font. */
.ms-filter[data-param="batch"] .ms-filter__opt-label {
    font-family: var(--font-mono);
    font-size: 12.5px;
    letter-spacing: 0;
}
/* Trigger label when a SINGLE batch is selected — the macro substitutes
   the batch id directly into the pill text ("A000-0611-001"), which at
   the default 13px reads as oversized for a header chip. Drop to a
   compact monospace size so the id fits without forcing the toolbar
   to grow. Scoped to the batches picker, single-selection only. */
.ms-filter[data-param="batch"].ms-filter--active:not(:has(.ms-filter__count)) .ms-filter__trigger {
    font-family: var(--font-mono);
    font-size: 12px;
    letter-spacing: 0;
}

/* Reviewer toolbar override — the batch picker here stays neutral so it reads
   as a header control rather than an applied filter that draws attention. */
.review-toolbar .ms-filter--active .ms-filter__trigger,
.review-toolbar .ms-filter__trigger:has(.ms-filter__count) {
    background:    var(--color-neutral-100);
    border-color:  var(--color-outline);
    color:         var(--color-on-surface);
}
.review-toolbar .ms-filter__count {
    background: var(--color-on-surface-muted);
    color:      var(--color-surface);
}
.review-toolbar .ms-filter--open .ms-filter__trigger {
    border-color: var(--color-outline);
}


/* =============================================
   KPI Card grid (top row of analytics dashboard)
   ============================================= */
.kpi-grid {
    display: grid;
    /* Third slot (Others) is 25% narrower than the first two — the content there
       is sparser, so the card doesn't need an equal share of the row. */
    grid-template-columns: 1fr 1fr 0.75fr;
    gap: 16px;
    margin-bottom: 16px;
}
.kpi-card {
    padding: 18px 20px;
    background: var(--color-surface);
    border: 1px solid var(--color-outline-variant);
    border-radius: var(--radius);
}
.kpi-card__title {
    margin: 0 0 14px 0;
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.06em;
    color: var(--color-on-surface-variant);
    text-transform: uppercase;
}
.kpi-card__metrics {
    display: flex;
    gap: 24px;
}
.kpi-metric {
    flex: 1;
    min-width: 0;
}
.kpi-metric__label {
    font-size: 10px;
    font-weight: 600;
    color: var(--color-on-surface-muted);
    text-transform: uppercase;
    letter-spacing: 0.06em;
    margin-bottom: 6px;
}
.kpi-metric__value {
    display: flex;
    align-items: baseline;
    gap: 8px;
    flex-wrap: wrap;
    font-size: 22px;
    font-weight: 700;
    line-height: 1;
    color: var(--color-on-surface);
}
.kpi-metric__value--error   { color: var(--color-error-500); }
.kpi-metric__value--warning { color: var(--color-warning-600); }
.kpi-metric__value--success { color: var(--color-success-600); }


/* =============================================
   Chart card (middle row of analytics dashboard)
   ============================================= */
.chart-grid {
    display: grid;
    /* Third slot (Rejections donut) is 25% narrower than the bar-chart cards —
       the donut + legend is compact, so the card doesn't need an equal share. */
    grid-template-columns: 1fr 1fr 0.75fr;
    gap: 16px;
    margin-bottom: 16px;
}
.chart-card {
    padding: 18px 20px;
    background: var(--color-surface);
    border: 1px solid var(--color-outline-variant);
    border-radius: var(--radius);
}
.chart-card__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 16px;
}
.chart-card__title {
    margin: 0;
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.06em;
    color: var(--color-on-surface-variant);
    text-transform: uppercase;
}


/* =============================================
   Chart legend (inline, e.g. top of bar chart)
   ============================================= */
.chart-legend {
    display: flex;
    gap: 12px;
    font-size: 11px;
    color: var(--color-on-surface-variant);
}
.chart-legend__item {
    display: inline-flex;
    align-items: center;
    gap: 6px;
}
.chart-legend__dot {
    flex: 0 0 auto;
    width: 8px;
    height: 8px;
    border-radius: 50%;
}


/* =============================================
   Bar chart (single + stacked)
   - Bars sized via inline `height: NN%` set per bar.
   - Use .chart-bar-stack to stack multiple bars per slot.
   ============================================= */
.chart-bars__plot {
    display: flex;
    align-items: end;
    justify-content: space-between;   /* distribute thinner bars evenly across the plot width */
    gap: 8px;                         /* minimum gap between bars */
    height: 160px;
    padding-bottom: 1px;
    border-bottom: 1px solid var(--color-outline-variant);
}
.chart-bar-stack {
    flex: 0 1 14px;                   /* preferred 14px width, can shrink if container is tight */
    max-width: 16px;
    height: 100%;
    display: flex;
    flex-direction: column;
    justify-content: flex-end;       /* push the whole stack to the BOTTOM of the plot */
    align-items: stretch;
    min-width: 0;
}
.chart-bar {
    width: 100%;
    border-radius: 0;
}
.chart-bar-stack > .chart-bar:first-child {
    border-radius: 2px 2px 0 0;       /* only the topmost bar in a stack gets rounded corners */
}
.chart-bars__labels {
    display: flex;
    justify-content: space-between;
    gap: 8px;
    margin-top: 8px;
}
.chart-bar-label {
    flex: 0 1 14px;
    max-width: 16px;
    text-align: center;
    font-size: 10px;
    color: var(--color-on-surface-muted);
}


/* =============================================
   Donut chart (CSS conic-gradient based)
   - Set `background: conic-gradient(...)` inline with
     stops computed in the route handler.
   ============================================= */
.donut-card-body {
    display: flex;
    align-items: center;
    gap: 32px;
}
.donut-wrap {
    flex: 0 0 auto;
    position: relative;
    width: 130px;
    height: 130px;
}
.donut {
    width: 100%;
    height: 100%;
    border-radius: 50%;
}
.donut::after {
    content: "";
    position: absolute;
    inset: 15%;                        /* was 22% — ring thickness reduced ~30% */
    background: var(--color-surface);
    border-radius: 50%;
}
.donut-center {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    z-index: 2;
    text-align: center;
    pointer-events: none;
}
.donut-center__value {
    font-size: 18px;
    font-weight: 700;
    color: var(--color-on-surface);
    line-height: 1;
}
/* Error tone — matches the red used for client/qa counts and the rejected
   metric tile, so the donut centre reads as "this many rejections" at a glance. */
.donut-center__value--error { color: var(--color-error-500); }
.donut-center__label {
    margin-top: 3px;
    font-size: 10px;
    color: var(--color-on-surface-muted);
}
.donut-legend {
    flex: 1;
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 8px;
}

/* Variant of .chart-card__head used when the right side carries the Client/QA
   split. Aligns the title label baseline with the split labels (both are the
   same 11px uppercase tier) and gives a bit more breathing room before the body
   so the legend doesn't crowd against the split counts above. */
/* Stack the title above the split so the four actor columns (Client / QA
   / PM / AI) get the card's full width instead of being squeezed beside
   the title. */
.chart-card__head--with-split {
    flex-direction: column;
    align-items: stretch;
    gap: 14px;
    margin-bottom: 24px;
}

.rejections-split {
    display: flex;
    align-items: stretch;
    gap: 12px;
    width: 100%;
}
.rejections-split__col {
    flex: 1;
    display: flex;
    flex-direction: column;
    gap: 6px;
    min-width: 0;
}
.rejections-split__label {
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--color-on-surface-muted);
}
.rejections-split__value {
    display: flex;
    align-items: baseline;
    gap: 8px;
}
.rejections-split__count {
    font-size: 16px;
    font-weight: 700;
    line-height: 1;
    color: var(--color-error-500);
}
.rejections-split__pct {
    font-size: 12px;
    color: var(--color-on-surface-muted);
}
.rejections-split__divider {
    width: 1px;
    background: var(--color-outline-variant);
    align-self: stretch;
}
.donut-legend__item {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 12px;
}
.donut-legend__dot {
    flex: 0 0 auto;
    width: 10px;
    height: 10px;
    border-radius: 50%;
}
/* Label takes remaining space so the value lands flush right — labels and
   percentages read as two tidy columns. */
.donut-legend__label {
    flex: 1;
    min-width: 0;
    color: var(--color-on-surface);
}
.donut-legend__value {
    flex: 0 0 auto;
    color: var(--color-on-surface-muted);
    font-weight: 600;
    font-variant-numeric: tabular-nums;
}


/* =============================================
   Wizard / multi-step forms
   ============================================= */

/* Wizard layout (vertical rail on left + content on right) */
.wizard {
    display: flex;
    gap: 32px;
    align-items: flex-start;
}
.wizard__rail {
    flex: 0 0 200px;
    position: sticky;
    top: 16px;
    padding: 4px 0;
}
.wizard__content {
    flex: 1;
    min-width: 0;
}

/* Vertical step rail (each circle connected by a line to the next) */
.step-rail {
    list-style: none;
    margin: 0;
    padding: 0;
}
.step-rail__step {
    display: flex;
    align-items: flex-start;
    gap: 12px;
    padding-bottom: 32px;
    position: relative;
}
.step-rail__step:last-child {
    padding-bottom: 0;
}
.step-rail__step:not(:last-child)::before {
    content: "";
    position: absolute;
    left: 11px;                /* center of 24px circle = 12px, line is 2px → 11 */
    top: 26px;
    bottom: 6px;
    width: 2px;
    background: var(--color-neutral-200);
}
.step-rail__step--complete:not(:last-child)::before {
    background: var(--color-success-400);
}
.step-rail__circle {
    position: relative;
    z-index: 1;
    flex: 0 0 auto;
    width: 24px;
    height: 24px;
    border-radius: 50%;
    background: var(--color-neutral-100);
    color: var(--color-on-surface-variant);
    font-size: 12px;
    font-weight: 700;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}
.step-rail__step--active .step-rail__circle {
    background: var(--color-primary-500);
    color: var(--color-on-primary);
    box-shadow: 0 0 0 4px var(--color-primary-75);     /* subtle halo to mark current */
}
.step-rail__step--complete .step-rail__circle {
    /* Softer tier than success-500 — the completed step shouldn't compete for
       attention with the primary-tinted "active" step. */
    background: var(--color-success-400);
    color: var(--color-on-success);
}
.step-rail__step--complete .step-rail__circle > svg {
    width: 14px;
    height: 14px;
    stroke-width: 2.5;
}
.step-rail__title {
    flex: 1;
    padding-top: 3px;          /* baseline-align with circle */
    font-size: 13px;
    font-weight: 600;
    color: var(--color-on-surface);
    line-height: 1.3;
}
.step-rail__step--pending .step-rail__title {
    color: var(--color-on-surface-variant);
    font-weight: 500;
}
.step-rail__step--active .step-rail__title {
    color: var(--color-primary);
}


/* Wizard section (card-like content block) */
.wizard-section {
    background: var(--color-surface);
    border: 1px solid var(--color-outline-variant);
    border-radius: var(--radius);
    padding: 16px 20px;
    margin-bottom: 12px;
}
/* Two-column layout inside a single wizard-section. A thin divider
   separates the columns; on narrow viewports they stack. */
.wizard-cols {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: 24px;
}
.wizard-cols__col + .wizard-cols__col {
    border-left: 1px solid var(--color-outline-variant);
    padding-left: 24px;
}
.wizard-cols__col > .wizard-section__title:first-child {
    margin-top: 0;
}
.wizard-section--tabbed {
    padding: 0;          /* tabs + tab-content manage their own padding */
}
.wizard-section--tabbed .tabs {
    padding: 0 20px;
}
.wizard-section--tabbed .tab-content {
    padding: 18px 20px;
}

/* Tab panes (one shows at a time, switched via data-tab attribute) */
.tab-pane            { display: none; }
.tab-pane--active    { display: block; }
.wizard-section__title {
    margin: 0 0 14px 0;
    font-size: 14px;
    font-weight: 600;
    color: var(--color-on-surface);
}
.wizard-subsection-title {
    margin: 22px 0 14px 0;
    padding-bottom: 8px;
    border-bottom: 1px solid var(--color-outline-variant);
    font-size: 11px;
    font-weight: 700;
    /* Medium-emphasis tier — full on-surface read as too heavy for an 11px
       uppercase label; this sits between -muted (eyebrow text) and -surface. */
    color: var(--color-on-surface-variant);
    text-transform: uppercase;
    letter-spacing: 0.08em;
}
.wizard-subsection-title:first-of-type {
    margin-top: 0;
}
/* Variant for titles that live inside .subsection-head — the wrapper owns the
   underline and bottom-margin, so the title strips its own. */
.wizard-subsection-title--bare {
    margin: 0;
    padding-bottom: 0;
    border-bottom: 0;
}
/* Layout-status row error treatment — used when the chosen datamap is
   rejected by the validator (missing required variables). Keeps the row
   in the same slot as the "X questions loaded" copy but switches to a
   tinted error chip so the operator can't miss it. */
.layout-status--error {
    padding: 6px 10px;
    border-radius: 6px;
    background: var(--color-error-bg, rgba(220, 38, 38, 0.08));
    color: var(--color-error, #b91c1c);
}

/* Required-variables confirmation strip — green chips for each
   meta column the platform expects on every datamap. Sits below the
   layout dropdown's status row so positive feedback is visible
   without scrolling. */
.datamap-required {
    margin-top: 10px;
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
}
.datamap-required__label {
    font-size: 11.5px;
    font-weight: 500;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--color-on-surface-muted);
}
.datamap-required__chips {
    display: flex;
    align-items: center;
    gap: 6px;
    flex-wrap: wrap;
}
/* Subsection head row: title on the left, AI hint (or other meta) on the right.
   The wrapper carries the underline + bottom space so right-side meta doesn't
   take an extra row. Extra bottom margin gives the following control room to breathe. */
.subsection-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 16px;
    padding-bottom: 8px;
    margin-bottom: 22px;
    border-bottom: 1px solid var(--color-outline-variant);
}
.subsection-head:first-of-type { margin-top: 0; }
/* AI hint inside the head row drops its own vertical margins — the wrapper
   handles spacing now. */
.subsection-head .ai-hint { margin: 0; }


/* Form helpers */
.field-grid {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: 14px 20px;
    margin-bottom: 14px;
}
.field-grid--cols-3 { grid-template-columns: repeat(3, 1fr); }
.input-row {
    display: flex;
    gap: 8px;
    align-items: stretch;
}
.input-row > .input-wrap,
.input-row > .input {
    flex: 1;
}
.input-wrap {
    position: relative;
}
.input-wrap > .input {
    padding-right: 38px;       /* room for the trailing icon */
}
.input-wrap__icon {
    position: absolute;
    right: 12px;
    top: 50%;
    transform: translateY(-50%);
    width: 18px;
    height: 18px;
    color: var(--color-success-600);
    pointer-events: none;
}


/* =============================================
   URL Fetcher

   Hero input that ingests an external URL and triggers a fetch.
   Reads as ONE designed control: violet-tinted panel, leading globe,
   inline verified pill, integrated fetch action.
   ============================================= */
.url-fetcher {
    display: flex;
    align-items: center;
    gap: 12px;
    height: 56px;
    padding: 6px 6px 6px 16px;
    background: var(--color-tertiary-50);
    border: 1px solid var(--color-tertiary-200);
    border-radius: 10px;
    transition: border-color 120ms, box-shadow 120ms;
}
.url-fetcher:focus-within {
    border-color: var(--color-tertiary-500);
    box-shadow: 0 0 0 3px var(--color-tertiary-100);
}
.url-fetcher__icon {
    flex: 0 0 auto;
    width: 18px;
    height: 18px;
    color: var(--color-tertiary-600);
}
.url-fetcher__input {
    flex: 1;
    min-width: 0;
    height: 100%;
    padding: 0;
    background: transparent;
    border: 0;
    outline: 0;
    font-family: var(--font-mono);
    font-size: 13px;
    color: var(--color-on-surface);
    letter-spacing: 0.01em;
}
/* Suppress the blue autofill background Chrome/Edge paint when the field
   carries a saved value. The inset box-shadow is the standards-aligned
   workaround — it covers the browser's wash without breaking text color.
   The shadow colour must match the wrapper's background (.url-fetcher uses
   tertiary-50) so the input visually disappears into the pill instead of
   reading as a white box. */
.url-fetcher__input:-webkit-autofill,
.url-fetcher__input:-webkit-autofill:hover,
.url-fetcher__input:-webkit-autofill:focus {
    -webkit-text-fill-color: var(--color-on-surface);
    -webkit-box-shadow: 0 0 0 1000px var(--color-tertiary-50) inset;
            box-shadow: 0 0 0 1000px var(--color-tertiary-50) inset;
    /* The 5000s transition is the canonical "outwait autofill" hack —
       browsers re-apply the colour on focus changes; this stalls it past
       any reasonable session length. */
    transition: background-color 5000s ease-in-out 0s;
}
.url-fetcher__input::placeholder {
    color: var(--color-on-surface-muted);
}
.url-fetcher__status {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 4px 10px;
    background: var(--color-success-50);
    color: var(--color-success-700);
    border-radius: 999px;
    font-size: 11px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}
/* Failure variant — same shape, red palette. JS swaps the class on the
   same span when the Fetch Details call returns ok=False. */
.url-fetcher__status--error {
    background: var(--color-error-50);
    color:      var(--color-error-700);
}
.url-fetcher__status > svg {
    width: 12px;
    height: 12px;
}
.url-fetcher__action {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    gap: 6px;
    height: 100%;
    padding: 0 14px;
    background: var(--color-tertiary-500);
    color: #fff;
    border: 0;
    border-radius: 6px;
    font-family: inherit;
    font-size: 13px;
    font-weight: 600;
    cursor: pointer;
    transition: filter 120ms;
}
.url-fetcher__action:hover {
    filter: brightness(0.95);
}
.url-fetcher__action > svg {
    width: 14px;
    height: 14px;
    stroke-width: 2;        /* solid CTA on Decipher wizard — keep emphasis at full weight */
}


/* Select (button-styled fake dropdown - matches input height) */
.select {
    width: 100%;
    height: 40px;
    padding: 0 16px;
    background: var(--color-surface);
    border: 1px solid var(--color-outline);
    border-radius: var(--radius-control);
    font-family: inherit;
    font-size: 14px;
    color: var(--color-on-surface);
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    text-align: left;
    transition: border-color 120ms;
}
.select:hover {
    border-color: var(--color-neutral-300);
}
.select__placeholder {
    color: var(--color-on-surface-muted);
}
.select__chevron {
    flex: 0 0 auto;
    width: 14px;
    height: 14px;
    color: var(--color-on-surface-muted);
    stroke-width: 1.5;
}


/* Textarea */
.textarea {
    width: 100%;
    min-height: 78px;
    padding: 10px 14px;
    font-family: inherit;
    font-size: 14px;
    line-height: 1.5;
    color: var(--color-on-surface);
    background: var(--color-surface-container-low);
    border: 1px solid var(--color-outline-variant);
    border-radius: var(--radius-control);
    outline: none;
    resize: vertical;
    transition: border-color 120ms;
}
.textarea::placeholder { color: var(--color-on-surface-muted); }
.textarea:focus        { border-color: var(--color-primary); }


/* Upload zone — centered drop target with a dashed border. Smaller than
   the original (icon + title + button stack with reduced padding). */
.upload-zone {
    text-align: center;
    padding: 20px 20px;
    border: 1.5px dashed var(--color-outline);
    border-radius: var(--radius);
    background: var(--color-surface);
    margin-bottom: 14px;
    transition: border-color 120ms, background-color 120ms;
}
.upload-zone:hover {
    border-color: var(--color-primary-400);
    background: var(--color-primary-50);
}
.upload-zone__icon {
    margin: 0 auto 10px;
    width: 40px;
    height: 40px;
    border-radius: 50%;
    background: var(--color-primary-75);
    color: var(--color-primary-600);
    display: inline-flex;
    align-items: center;
    justify-content: center;
}
.upload-zone__icon--primary  { background: var(--color-primary-75);  color: var(--color-primary-600);  }
.upload-zone__icon--tertiary { background: var(--color-tertiary-75); color: var(--color-tertiary-600); }
.upload-zone__icon--info     { background: var(--color-info-75);     color: var(--color-info-600);     }
.upload-zone__icon--success  { background: var(--color-success-75);  color: var(--color-success-600);  }
.upload-zone__icon > svg {
    width: 20px;
    height: 20px;
    stroke-width: 1.5;
}
.upload-zone__title {
    margin: 0 0 10px 0;
    font-size: 13px;
    font-weight: 600;
    color: var(--color-on-surface);
}
.upload-zone__desc {
    max-width: 480px;
    margin: -4px auto 10px;
    font-size: 12px;
    line-height: 1.4;
    color: var(--color-on-surface-variant);
}


/* Info banner (icon + text panel) */
.info-banner {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    padding: 12px 16px;
    border-radius: var(--radius);
    margin-bottom: 14px;
    font-size: 12px;
    line-height: 1.5;
}
.info-banner > p { margin: 0; }
.info-banner__icon {
    flex: 0 0 auto;
    width: 16px;
    height: 16px;
    margin-top: 1px;
    stroke-width: 1.5;
}
.info-banner--info {
    background: var(--color-info-75);
    color: var(--color-info-700);
}
.info-banner--info .info-banner__icon {
    color: var(--color-info-500);
}


/* AI hint (sparkles + colored text, inline) */
.ai-hint {
    display: flex;
    align-items: center;
    gap: 8px;
    margin: 12px 0 14px;
    font-size: 13px;
    color: var(--color-primary-600);
}
.ai-hint__icon {
    flex: 0 0 auto;
    width: 16px;
    height: 16px;
    stroke-width: 1.5;
}


/* File list (uploaded file rows with tags) — rows separated by a thin
   divider rather than individual card borders. */
.file-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
}
.file-list__item {
    display: flex;
    align-items: center;
    gap: 14px;
    padding: 12px 4px;
    background: transparent;
    border-bottom: 1px solid var(--color-outline-variant);
}
.file-list__item:last-child {
    border-bottom: none;
}
.file-list__icon {
    flex: 0 0 auto;
    width: 36px;
    height: 36px;
    border-radius: var(--radius);
    display: inline-flex;
    align-items: center;
    justify-content: center;
}
.file-list__icon > svg {
    width: 18px;
    height: 18px;
    stroke-width: 1.5;
}
.file-list__icon--info     { background: var(--color-info-75);     color: var(--color-info-600); }
.file-list__icon--tertiary { background: var(--color-tertiary-75); color: var(--color-tertiary-600); }
.file-list__icon--success  { background: var(--color-success-75);  color: var(--color-success-600); }
.file-list__icon--warning  { background: var(--color-warning-75);  color: var(--color-warning-600); }
.file-list__name {
    flex: 1;
    min-width: 0;
    font-size: 13px;
    font-weight: 500;
    color: var(--color-on-surface-variant);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.file-list__tags {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 6px;
    min-width: 0;
}
.file-list__meta {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    gap: 10px;
    font-size: 12px;
}
.file-list__size {
    color: var(--color-on-surface-variant);
    font-weight: 500;
}
.file-list__time {
    color: var(--color-on-surface-muted);
}

/* Tagged file rows: filename + meta on row 1 (filename truncates with
   ellipsis if narrow), tags wrap to row 2. Untagged rows keep the simple
   inline flex layout above. */
.file-list__item:has(.file-list__tags) {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr) auto auto;
    grid-template-areas:
        "icon name meta remove"
        "icon tags tags tags";
    column-gap: 12px;
    row-gap: 8px;
    align-items: center;
}
.file-list__item:has(.file-list__tags) .file-list__icon   { grid-area: icon;   align-self: center; }
.file-list__item:has(.file-list__tags) .file-list__name   { grid-area: name;   min-width: 0;        }
.file-list__item:has(.file-list__tags) .file-list__meta   { grid-area: meta;                       }
.file-list__item:has(.file-list__tags) .file-list__remove { grid-area: remove;                     }
.file-list__item:has(.file-list__tags) .file-list__tags   { grid-area: tags;                       }
.file-list__remove {
    flex: 0 0 auto;
    width: 28px;
    height: 28px;
    border: none;
    background: none;
    border-radius: var(--radius-control);
    cursor: pointer;
    color: var(--color-on-surface-muted);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    transition: background-color 120ms, color 120ms;
}
.file-list__remove:hover {
    background: var(--color-neutral-100);
    color: var(--color-on-surface);
}
.file-list__remove > svg {
    width: 16px;
    height: 16px;
    stroke-width: 1.5;
}


/* File tags (small pill labels inside file rows) */
.file-tag {
    display: inline-flex;
    align-items: center;
    height: 22px;
    padding: 0 10px;
    border-radius: 12px;
    background: var(--color-neutral-100);
    color: var(--color-on-surface-variant);
    font-family: inherit;
    font-size: 11px;
    font-weight: 500;
    line-height: 1;
    border: 1px solid transparent;
    white-space: nowrap;
}
.file-tag--primary {
    padding: 0 12px;     /* slightly more breathing room for the descriptive tag */
}
.file-tag--add {
    background: transparent;
    color: var(--color-on-surface-muted);
    border-style: dashed;
    border-color: var(--color-outline);
    cursor: pointer;
    transition: background-color 120ms, color 120ms;
}
.file-tag--add:hover {
    background: var(--color-neutral-75);
    color: var(--color-on-surface);
}
/* Modal body — generic padding wrapper between header and actions. */
.modal__body {
    padding: 0 24px 16px;
}

/* Drag-over state for the upload zone. */
.upload-zone--drop {
    border-color: var(--color-primary);
    background:   var(--color-primary-50);
}

/* =============================================
   Tag picker modal (Step 2 Add-tag)
   =============================================
   Wider modal split into two columns: predefined categories on the left,
   survey question ids on the right (with a search box + scrollable list).
   The whole popup is capped at ~70% of the viewport height so it always
   fits regardless of how many questions the survey has. */
/* .modal.modal--tag-picker — chained so this wins over the base .modal
   rule regardless of source order (both are class selectors; matching
   both bumps specificity from 0,1,0 → 0,2,0). */
.modal.modal--tag-picker {
    /* Wider than the default 420px modal — needs room for the chip id +
       title row in the right column. 90vw fallback keeps it from spilling
       past the viewport on narrow windows. */
    width: 90vw;
    max-width: 900px;
    max-height: 80vh;
    display: flex;
    flex-direction: column;
}
.modal.modal--tag-picker .modal__body {
    /* The two-column body owns the remaining height between header and
       actions. flex:1 + overflow:hidden lets the inner columns scroll
       independently. */
    flex: 1;
    min-height: 0;
    padding-top: 8px;
}
.tag-picker__body {
    display: grid;
    /* Left column: just wide enough for the longest predefined category.
       Right column: gets everything else — so qid + title both have room. */
    grid-template-columns: 180px minmax(0, 1fr);
    gap: 18px;
    min-height: 0;
}
.tag-picker__column {
    display: flex;
    flex-direction: column;
    min-height: 0;
    min-width: 0;
}
.tag-picker__column-head {
    flex: 0 0 auto;
    display: flex;
    flex-direction: column;
    gap: 8px;
    margin-bottom: 8px;
}
.tag-picker__label {
    font-size: 12px;
    font-weight: 600;
    color: var(--color-on-surface-muted);
    text-transform: uppercase;
    letter-spacing: 0.04em;
}
.tag-picker__list {
    list-style: none;
    margin: 0;
    padding: 4px;
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    border: 1px solid var(--color-outline-variant);
    border-radius: var(--radius-control);
    background: var(--color-neutral-50);
}
.tag-picker__row {
    margin: 0;
    padding: 0;
}
.tag-picker__row + .tag-picker__row { margin-top: 2px; }
.tag-picker__chip {
    width: 100%;
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 6px 10px;
    background: transparent;
    border: 1px solid transparent;
    border-radius: 3px;            /* near-square; was 6px */
    cursor: pointer;
    text-align: left;
    font-family: inherit;
    font-size: 12.5px;
    color: var(--color-on-surface);
    transition: background-color 100ms;
}
.tag-picker__chip:hover {
    background:   var(--color-surface);
}
.tag-picker__chip--active {
    background: var(--color-primary-75);
    color:      var(--color-primary-700);
    /* No border-color change — only a soft background tint marks the
       selection, so it reads as a chip "fill" rather than a boxed item. */
}
.tag-picker__chip-id {
    font-weight: 600;
    flex: 0 0 auto;
}
/* Mono font only on the question-id chips — predefined categories (Data
   File, Questionnaire, …) read as natural label text. */
#tag-list-questions .tag-picker__chip-id {
    font-family: var(--font-mono);
    min-width: 60px;       /* a reusable left rail so the titles line up */
}
.tag-picker__chip-title {
    flex: 1;
    min-width: 0;
    color: var(--color-on-surface-muted);
    font-size: 12px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.tag-picker__chip--active .tag-picker__chip-title { color: var(--color-primary-700); }
.tag-picker__empty {
    padding: 16px 8px;
    font-size: 12.5px;
    color: var(--color-on-surface-muted);
    text-align: center;
}

/* Compact variant of the search-input used in dense panels (modals,
   filter rows). Trims the height and — crucially — drops the
   320/480 min/max-width the table-screen .search-input ships with so
   the compact one shrinks to fit whatever (potentially narrow) column
   contains it. Width fills the parent so it never spills past the
   modal edge. */
.search-input--compact {
    flex: 0 1 auto;
    width: 100%;
    min-width: 0;
    max-width: none;
}
.search-input--compact .input {
    height: 30px;
    font-size: 12.5px;
    width: 100%;
    min-width: 0;
}
.search-input--compact .search-input__icon {
    width: 14px;
    height: 14px;
}


/* Segmented control (None / Country / Language / Both) */
.segmented {
    display: inline-flex;
    background: var(--color-neutral-100);
    border-radius: var(--radius);
    padding: 4px;
    gap: 4px;
}
.segmented__btn {
    padding: 8px 24px;
    border: none;
    background: transparent;
    border-radius: var(--radius-control);
    font-family: inherit;
    font-size: 13px;
    font-weight: 500;
    color: var(--color-on-surface);
    cursor: pointer;
    transition: background-color 120ms, color 120ms;
}
.segmented__btn:hover {
    background: var(--color-neutral-50);
}
.segmented__btn--active {
    background: var(--color-primary-75);
    color: var(--color-primary-600);
    font-weight: 600;
}
.segmented__btn--active:hover {
    background: var(--color-primary-75);
}


/* Wizard footer (right-aligned action bar) */
.wizard-footer {
    display: flex;
    justify-content: flex-end;
    align-items: center;
    gap: 12px;
    padding: 8px 0 16px;
}


/* Button helpers (see size system header above) */
.btn--auto    { width: auto; }                       /* size to content instead of 100% */
.btn:disabled { opacity: 0.5; cursor: not-allowed; }
.btn > svg    { flex: 0 0 auto; width: 14px; height: 14px; }

.btn--lg {
    height: 40px;                                    /* matches .input / .select */
    padding: 0 16px;
    gap: 8px;
    font-size: 14px;
}
.btn--lg > svg { width: 16px; height: 16px; }

.btn--ghost {
    background: transparent;
    color: var(--color-on-surface-variant);
    border: 1px solid transparent;
}
.btn--ghost:hover {
    background: var(--color-neutral-100);
    color: var(--color-on-surface);
}
.pagination .icon-btn {
    width: 24px;
    height: 24px;
}
.pagination .icon-btn > svg {
    width: 16px;
    height: 16px;
    stroke-width: 2;
}


/* =============================================
   Project Card

   Expandable card used on the /batches view. Header is a single row
   (toggle | title block | wide progress | status pill | actions).
   The body holds an inset batches table and an "Add Batch" footer link,
   hidden until .project-card--expanded is applied.
   ============================================= */
.project-card-list {
    display: flex;
    flex-direction: column;
    gap: 12px;
}

/* Project rows sit on an ultra-soft neutral wash — distinguishes them from the page
   surface without competing with the inset batch table (which stays white). */
.project-card {
    background: var(--color-neutral-25);
}

.project-card__header {
    /* Grid (not flex) so every project row's columns line up across cards.
       Column tracks:  toggle | priority | title (fills) | region | status | progress | batches | refresh | actions
       justify-items:start keeps each chip at the left of its column instead of stretching;
       the status pill overrides to center within its track. */
    display: grid;
    grid-template-columns: 24px 24px minmax(0, 1fr) 72px 120px 80px 160px 26px 32px;
    align-items: center;
    justify-items: start;
    gap: 16px;
    padding: 10px 20px;       /* tight rows — height driven by the cell-stack title (≈40px content) */
}

.project-card__toggle {
    flex: 0 0 auto;
    width: 24px;
    height: 24px;
    padding: 0;
    background: transparent;
    border: 0;
    border-radius: var(--radius-control);
    color: var(--color-on-surface-muted);
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    transition: background-color 120ms;
}
.project-card__toggle:hover {
    background: var(--color-neutral-100);
    color: var(--color-on-surface);
}
.project-card__toggle > svg {
    width: 16px;
    height: 16px;
    stroke-width: 2;
    transition: transform 200ms ease;
}
.project-card--expanded .project-card__toggle > svg {
    transform: rotate(180deg);
}

/* Title spans the flexible "1fr" column. Flex row so a leading CRM-id chip can sit
   beside the cell-stack — that way the secondary line (client) aligns under the project
   name, not under the chip. min-width:0 lets long names ellipsis-truncate. */
.project-card__title {
    display: flex;
    align-items: center;
    gap: 8px;
    min-width: 0;
    max-width: 100%;
}
.project-card__title > .cell-stack {
    min-width: 0;
    flex: 1 1 auto;
}
.project-card__title .cell-stack__primary,
.project-card__title .cell-stack__secondary {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Status pill sits centered within its grid track (visually balanced between region and progress) */
.project-card__header > .badge--dot {
    justify-self: center;
}

/* CRM identifier — small monospace chip sitting beside the cell-stack title.
   Reads as an external reference without competing with the human-readable project name. */
.crm-id {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    padding: 2px 6px;
    background: var(--color-neutral-75);
    color: var(--color-on-surface-variant);
    border-radius: 4px;
    font-family: var(--font-mono);
    font-size: 12px;
    font-weight: 500;
    letter-spacing: 0.02em;
}

/* Inline batch summary cell ("3 batches · 2 open"). Single line. */
.project-card__batches {
    font-size: 12px;
    color: var(--color-on-surface-variant);
    white-space: nowrap;
}
.project-card__batches-sep {
    margin: 0 4px;
    color: var(--color-on-surface-muted);
    opacity: 0.6;
}

/* Body is hidden until expanded — keeps the markup uniform across cards */
.project-card__body {
    display: none;
    border-top: 1px solid var(--color-outline-variant);
}
.project-card--expanded .project-card__body {
    display: block;
}



/* =============================================
   Inset Data Table

   data-table variant for nesting inside a card body. Stays white (matches the
   card surface so the area doesn't read as a dull gray slab); the column-label
   band gets a faint neutral-50 strip so the header still feels distinct.
   Rows breathe more, body rows hover, and dividers are lighter.
   ============================================= */
.data-table--inset {
    background: var(--color-surface);
}
.data-table--inset thead {
    background: var(--color-neutral-50);
}
.data-table--inset thead th {
    padding-top: 11px;
    padding-bottom: 11px;
    font-size: 10px;
    font-weight: 700;
    letter-spacing: 0.09em;
    color: var(--color-on-surface-muted);
    border-bottom: 1px solid var(--color-outline-variant);
}
.data-table--inset tbody td {
    padding-top: 14px;
    padding-bottom: 14px;
    border-bottom: 1px solid var(--color-neutral-100);   /* lighter divider than the default outline-variant */
}


/* Cell-level action — a quieter alternative to .btn for actions inside dense table rows.
   Borrows the small-caps + letter-spacing idiom from .field-label / .data-table th so the
   action text reads as a refined micro-label, not generic button copy. Primary-tinted
   background only appears on hover, confirming clickability without page-level chrome. */
.cell-action {
    display: inline-flex;
    align-items: center;
    padding: 6px 10px;
    background: transparent;
    border: 0;
    border-radius: 6px;
    color: var(--color-primary-600);
    font-family: inherit;
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    text-decoration: none;
    cursor: pointer;
    transition: background-color 120ms, color 120ms;
}
.cell-action:hover {
    background: var(--color-primary-50);
    color: var(--color-primary-700);
}
.cell-action > svg {
    width: 14px;
    height: 14px;
    stroke-width: 1.25;     /* matches sidebar nav icon thickness */
}
/* Tone modifier — external-data pull (violet/tertiary). Same layout as .cell-action,
   only the color tone changes. Transparent at rest (the toned icon
   carries the identity); background appears on hover only — matches
   the operator's preference for a quieter project-header row.
   The reviewer-toolbar reconcile button has its own filled override
   further down in this file. */
.cell-action--fetch {
    background: transparent;
    color: var(--color-tertiary-600);
}
.cell-action--fetch:hover {
    background: var(--color-tertiary-100);
    color: var(--color-tertiary-700);
}

/* Selection-driven Reconcile pill on the batches screen — appears
   only when batches in the project group are ticked (parallel to
   Merge / Assign / Review). Uses the tertiary tone so it stays
   semantically distinct from the blue selection-cluster siblings
   (Merge / Assign / Review are read-only navigations; Reconcile
   pushes state to Decipher). Filled background matches the visual
   weight of the sibling pills. ``gap: 6px`` lines up the icon /
   label / count exactly like the sibling rule. */
/* Transparent at rest so it sits quietly beside the other project-header
   icon actions (fetch / clear / delete); the tint appears on hover only.
   The batches selection-pill and reviewer-toolbar contexts re-fill it
   below where a filled weight is wanted. */
.cell-action--reconcile {
    background: transparent;
    color:      var(--color-tertiary-600);
    gap: 6px;
}
.cell-action--reconcile:hover {
    background: var(--color-tertiary-100);
    color:      var(--color-tertiary-800);
}
/* Batches selection pill — filled to match its Merge / Assign / Review
   siblings when batches in the group are ticked. */
.batches-row--group-header__actions .cell-action--reconcile {
    background: var(--color-tertiary-50);
    color:      var(--color-tertiary-700);
}
.batches-row--group-header__actions .cell-action--reconcile:hover {
    background: var(--color-tertiary-100);
    color:      var(--color-tertiary-800);
}

/* Reviewer toolbar context — the Reconcile button sits alone in the
   header (not part of a multi-button project row), so it wears the
   tinted background at rest. Hover deepens the same tint. */
.review-toolbar .cell-action--reconcile {
    background: var(--color-tertiary-100);
    color: var(--color-tertiary-700);
}
.review-toolbar .cell-action--reconcile:hover {
    background: var(--color-tertiary-200);
}

/* AI Review button — sits next to Reconcile on the reviewer toolbar.
   Same icon-only chrome, sparkles glyph; primary-tinted at rest so the
   two header actions read as distinct affordances (reconcile = data
   pull, ai-review = automated triage) instead of a single "tools"
   group. */
.cell-action--ai-review {
    background: transparent;
    color: var(--color-primary-600);
}
.cell-action--ai-review:hover {
    background: var(--color-primary-100);
    color: var(--color-primary-700);
}
.review-toolbar .cell-action--ai-review {
    background: var(--color-primary-100);
    color: var(--color-primary-700);
}
.review-toolbar .cell-action--ai-review:hover {
    background: var(--color-primary-200);
}

/* Export to Excel — success/emerald tone, filled in the reviewer toolbar
   to match the weight of its AI-analysis + reconcile siblings. */
.cell-action--export {
    background: transparent;
    color: var(--color-success-600);
}
.cell-action--export:hover {
    background: var(--color-success-50);
    color: var(--color-success-700);
}
.review-toolbar .cell-action--export {
    background: var(--color-success-50);
    color: var(--color-success-700);
}
.review-toolbar .cell-action--export:hover {
    background: var(--color-success-100);
}
/* Disabled cell-action (e.g. Export with no batch selected) — muted and
   inert, no hover affordance. */
.cell-action:disabled,
.cell-action[disabled] {
    opacity: 0.45;
    cursor: not-allowed;
    pointer-events: none;
}

.cell-action__text {
    font-variant-numeric: tabular-nums;
}

/* Danger-tone sibling — used by destructive per-project buttons
   ("Clear data" today). Transparent at rest, red wash on hover.
   The icon stays in error-tone so the risk still reads at a glance,
   and the JS handler always pops a confirmDialog before sending the
   request — the colour is a secondary signal, not the only guard. */
.cell-action--danger {
    background: transparent;
    color:      var(--color-error-700);
}
.cell-action--danger:hover {
    background: var(--color-error-75);
    color:      var(--color-error-800, var(--color-error-700));
}

/* Neutral-tone sibling — used by the group-header Recalc button so the
   counter-maintenance action reads as background utility next to the
   tertiary-toned data-movement buttons (fetch / reconcile). Transparent
   at rest like its siblings; neutral wash on hover. */
.cell-action--recalc {
    background: transparent;
    color: var(--color-neutral-700);
}
.cell-action--recalc:hover {
    background: var(--color-neutral-200);
    color: var(--color-neutral-800);
}

/* Merge button — appears only when 2+ batches are checked in a
   project group. Uses an info wash to read as a structural action
   distinct from the tertiary-toned data-movement siblings.
   Carries both an icon AND a count chip, so we add the inter-element
   gap that the icon-only siblings don't need. */
.cell-action--merge,
.cell-action--assign,
.cell-action--review {
    background: var(--color-info-50);
    color:      var(--color-info-700);
    gap: 6px;
}
.cell-action--merge:hover,
.cell-action--assign:hover,
.cell-action--review:hover {
    background: var(--color-info-100);
    color:      var(--color-info-800);
}
.cell-action__count {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 18px;
    padding: 0 6px;
    /* Matches the SVG icon size on icon-only siblings so the Merge
       button doesn't grow taller than Download / Reconcile / Recalc
       sitting next to it in the group-header bar. */
    height: 14px;
    border-radius: 999px;
    background: var(--color-info-100);
    color: var(--color-info-800);
    font-family: var(--font-mono);
    font-size: 11px;
    font-weight: 700;
    line-height: 1;
    letter-spacing: 0;
    text-transform: none;
}
.cell-action--merge:hover .cell-action__count,
.cell-action--assign:hover .cell-action__count,
.cell-action--review:hover .cell-action__count {
    background: var(--color-info-200);
}

/* Selection column — leftmost, narrow, centered checkbox. Sits next
   to the existing col-actions / col-progress utilities. */
.col-select {
    width: 1%;
    white-space: nowrap;
    text-align: center;
    padding-left: 16px;
    padding-right: 8px;
}

/* Batch multi-select actions (project detail → Batches tab). Pinned to
   the far right of the tab row, same pill look as the main batches
   screen's group-header actions (Merge / Assign). Each button appears
   only when its selection threshold is met. */
.project-tabs-actions {
    margin-left: auto;      /* push to the far right of the .tabs flex row */
    align-self: center;     /* centre against the tab labels */
    display: flex;
    align-items: center;
    gap: 6px;
}

/* ── Custom checkbox ────────────────────────────────────────────────
   Reusable across the app — drop in via:

       <label class="checkbox">
         <input type="checkbox" ... />
         <span class="checkbox__box">
           <svg class="checkbox__tick"><use href="#icon-check"/></svg>
         </span>
       </label>

   The native ``<input>`` stays in the DOM (form submission + keyboard
   + screen-reader support) but is visually replaced by ``.checkbox__box``.
   The tick appears via ``:checked`` adjacency; focus rings show via
   ``:focus-visible`` so keyboard nav is obvious without polluting
   mouse-driven clicks. Info-tone fill matches the Merge button's
   palette so the visual relationship reads as "selecting these for an
   info-level action". */
.checkbox {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    /* Keep the visual footprint exactly the box size so the row
       cell auto-sizes around it. */
    line-height: 0;
    user-select: none;
}
.checkbox > input[type="checkbox"] {
    /* Native input lives off-screen but stays focusable + form-active. */
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}
.checkbox__box {
    width: 16px;
    height: 16px;
    /* ``--color-outline`` is one step darker than ``--color-outline-variant``;
       the variant tone made unselected boxes nearly invisible against
       the card surface. */
    border: 1.5px solid var(--color-outline);
    border-radius: 3px;
    background: var(--color-surface);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    /* Anchor for the indeterminate-state ``::after`` bar. */
    position: relative;
    transition: background-color 120ms, border-color 120ms;
}
.checkbox__tick {
    width: 12px;
    height: 12px;
    color: var(--color-surface);
    stroke-width: 3;
    opacity: 0;
    transition: opacity 80ms;
}
.checkbox:hover .checkbox__box {
    border-color: var(--color-info-500);
}
.checkbox > input[type="checkbox"]:checked + .checkbox__box {
    background: var(--color-info-500);
    border-color: var(--color-info-500);
}
.checkbox > input[type="checkbox"]:checked + .checkbox__box .checkbox__tick {
    opacity: 1;
}
/* Indeterminate (mixed) — used by the project group-header checkbox
   when some-but-not-all of its batches are ticked. Filled background
   like ``:checked`` but with a horizontal bar in place of the tick so
   the operator can tell at a glance that the selection is partial. */
.checkbox > input[type="checkbox"]:indeterminate + .checkbox__box {
    background: var(--color-info-500);
    border-color: var(--color-info-500);
}
.checkbox > input[type="checkbox"]:indeterminate + .checkbox__box::after {
    content: "";
    position: absolute;
    inset: 50% 3px auto 3px;
    height: 2px;
    background: white;
    border-radius: 1px;
    transform: translateY(-50%);
}
.checkbox > input[type="checkbox"]:focus-visible + .checkbox__box {
    outline: 2px solid var(--color-info-300);
    outline-offset: 2px;
}
.checkbox > input[type="checkbox"]:disabled + .checkbox__box {
    background: var(--color-neutral-100);
    border-color: var(--color-outline-variant);
    cursor: not-allowed;
}
/* When the inner input is disabled, the wrapping label keeps the
   ``cursor: pointer`` from ``.checkbox`` above — :has() lets us
   override it so the whole hit area (not just the box) reads as
   not-clickable. */
.checkbox:has(> input[type="checkbox"]:disabled) {
    cursor: not-allowed;
}


/* Filter pill that carries a quiet "label:" prefix (e.g. "Sort by: Last Activity") */
.filter-pill__label-prefix {
    color: var(--color-on-surface-muted);
    font-weight: 400;
    margin-right: 4px;
}


/* Semantic numeric cell classes — apply to <span> inside a <td> for emphasized counts.
   Keeps typography on a named component instead of utility classes on the cell itself. */
.num-good    { color: var(--color-success-600); font-weight: 600; }   /* high-is-good values (QA scores, success rates) */
.num-flagged { color: var(--color-warning-600); font-weight: 600; }   /* "needs attention" counts */
.num-empty   { color: var(--color-on-surface-muted); }                /* dashed-out zero / null values */


/* Batches flat-table — data columns are centre-aligned. The group-header row keeps its
   own left alignment via the more-specific rule below; the action column (last-child)
   keeps its right alignment from the base .data-table rule. */
.batches-flat th:not(:last-child),
.batches-flat tbody tr:not(.batches-row--group-header) td:not(:last-child) {
    text-align: center;
}
/* Compact batch rows — slimmer vertical padding so the table can show more rows in
   the same viewport. Doesn't affect the group-header row (its own rule below). */
.batches-flat tbody tr:not(.batches-row--group-header) td {
    padding-top: 5px;
    padding-bottom: 5px;
}

/* Row-actions column — the kebab cell at the row end. ``width: 1%`` is
   the standard shrink-to-fit hint for table cells: the column collapses
   to its content width and leaves the remaining space for the data
   cells. Used on the actions ``<th>`` + ``<td>`` so the kebab doesn't
   soak up unallocated width. */
.col-actions {
    width: 1%;
    white-space: nowrap;
}

/* Gap between the last count column (RCN) and the action-cell kebab
   on the batches tables. The actions cell stays right-aligned at the
   table edge — bumping its ``padding-left`` widens the cell, which
   eats space from the RCN cell's right side and leaves visible white
   between the RCN number and the three-dot icon. Scoped to the two
   batches surfaces so other tables (projects list, surveyfeed admin)
   keep their tighter actions column. */
.batches-flat .col-actions,
.data-table--home-batches .col-actions {
    padding-left: 32px;
}

/* Progress column — the ``.progress`` widget caps at 110px, but the
   surrounding ``<td>`` would otherwise absorb any leftover table width.
   Same ``width: 1%`` trick collapses the cell to the widget, leaving
   the slack for the truly flexible cells (Batch # / Reviewer). */
.col-progress {
    width: 1%;
    white-space: nowrap;
}

/* Status column — the badge inside has a fixed visual footprint, but
   the surrounding ``<th>`` / ``<td>`` would otherwise share leftover
   table width. Shrink-to-fit keeps the column tight to the badge so
   the gap to the next column stays even row-over-row. */
.col-status {
    width: 1%;
    white-space: nowrap;
}

/* Tighter horizontal padding for cells whose content already reads
   compactly (mono ids, short reviewer names). Trims the default
   ``.data-table td { padding: 11px 12px }`` to 6px each side so the
   column hugs its content. The first-child override below keeps a
   reasonable indent from the card edge for the leftmost column.
   Stacks cleanly with ``data-table--fit``: ``width: auto`` + tighter
   padding = visibly narrower column. */
.data-table th.col-tight,
.data-table td.col-tight {
    padding-left: 6px;
    padding-right: 6px;
}
.data-table th.col-tight:first-child,
.data-table td.col-tight:first-child {
    /* Keep some left indent so the column isn't flush with the card
       edge — half of the standard 24px first-child gutter. */
    padding-left: 12px;
}


/* ── Count cells (RSS / FLG / RCN) ────────────────────────────────────
   Three adjacent table cells styled as a single rounded pill. Each
   cell is its own ``<th>`` / ``<td>``, so the browser's normal table
   layout auto-sizes per column — RSS (often 4-5 digits) gets wider
   when needed while FLG / RCN stay tight, AND cells in the same
   column stay uniform across rows.

   The pill visual lives on an inner ``<span>``: cells get ``padding: 0``
   so adjacent inner spans touch edge-to-edge with no gap, and the
   span carries the bg + borders + radius + label/value typography.
   The selectors below outweigh ``.batches-flat tbody tr ... td``'s
   compact-padding override via a four-element chain. */
.data-table tbody tr td.count-cell,
.data-table thead th.count-cell-h {
    padding: 0;
    width: 1%;
    white-space: nowrap;
    text-align: center;
}
/* Tiny left gutter on the leading chip cell so the pill doesn't sit
   hard against the previous column's right edge. Applied only to the
   first cell so the inter-segment dividers stay flush. */
.data-table tbody tr td.count-cell--first,
.data-table thead th.count-cell-h--first {
    padding-left: 8px;
}

.count-cell-value,
.count-cell-label {
    display: block;
    padding: 2px 8px;
    background: var(--color-warning-50);
    border-top:    1px solid var(--color-warning-100);
    border-bottom: 1px solid var(--color-warning-100);
    font-family: var(--font-mono);
    font-size: 11px;
    font-weight: 500;
    color: var(--color-warning-700);
    line-height: 1.4;
}

/* Outline + radius on the pill's left edge — first cell only. */
.count-cell--first > .count-cell-value {
    border-left: 1px solid var(--color-warning-100);
    border-top-left-radius: 4px;
    border-bottom-left-radius: 4px;
}
/* Outline + radius on the pill's right edge — last cell only. */
.count-cell--last > .count-cell-value {
    border-right: 1px solid var(--color-warning-100);
    border-top-right-radius: 4px;
    border-bottom-right-radius: 4px;
}
/* Internal dividers — every cell except the last carries the right border. */
.count-cell:not(.count-cell--last) > .count-cell-value {
    border-right: 1px solid var(--color-warning-100);
}

/* Header variant — neutral palette so the label row reads as a legend
   rather than data. Borders + radius mirror the body so the header
   pill silhouette is identical, just quieter. */
.count-cell-h > .count-cell-label {
    background: var(--color-neutral-50);
    border-top-color:    var(--color-outline-variant);
    border-bottom-color: var(--color-outline-variant);
    font-size: 10px;
    letter-spacing: 0.08em;
    font-weight: 600;
    color: var(--color-on-surface-muted);
}
.count-cell-h--first > .count-cell-label {
    border-left: 1px solid var(--color-outline-variant);
    border-top-left-radius: 4px;
    border-bottom-left-radius: 4px;
}
.count-cell-h--last > .count-cell-label {
    border-right: 1px solid var(--color-outline-variant);
    border-top-right-radius: 4px;
    border-bottom-right-radius: 4px;
}
.count-cell-h:not(.count-cell-h--last) > .count-cell-label {
    border-right: 1px solid var(--color-outline-variant);
}

/* Plain link — looks like regular text but navigates on click. Underline-on-hover
   is the only affordance, keeping the cell content visually quiet. */
.link-plain {
    color: inherit;
    text-decoration: none;
}
.link-plain:hover {
    text-decoration: underline;
}

/* ID column treatment — switches to Geist Mono so code-like identifiers
   (HC93-0520-002, CR4F, etc.) render with the proportional precision of a
   mono face. Applies in tandem with .link-plain on clickable batch-ID cells.
   ``nowrap`` keeps fixed-format ids on a single line — they're inherently
   short, never want to wrap mid-token (e.g. "A001-0403-" wrapped to
   three lines is the worst-case rendering, never the intended one). */
.id-mono {
    font-family: var(--font-mono);
    letter-spacing: 0.01em;
    white-space: nowrap;
}

/* Home "My Active Batches" table — batch rows under a group header
   get a left indent on their first cell so the project header reads
   as the parent and the batches read as its children. The batches
   list view achieves the same visual via the leading checkbox
   column; the home view has no checkbox, so we add the indent
   directly. Scoped to the unique table modifier so other tables that
   carry ``data-batch-id`` rows (e.g. the batches list itself) keep
   their existing layout. */
.data-table--home-batches tbody tr[data-batch-id] > td:first-child {
    padding-left: 60px;
}

/* Group header — a quiet "section label" row that sits above each project's batches
   on the flat batches table. The neutral-50 tint contrasts with the white batch rows,
   chunking the table into project groups without needing a heavy separator border.
   Selector includes :first-child so it wins specificity against the base data-table
   first-child padding rule (which would otherwise indent the project name by 24px). */
.batches-row--group-header > td:first-child {
    /* 24px on both sides — left matches ``.col-select``'s effective
       padding so the project select-all checkbox sits in the same
       column as the per-batch checkboxes below it; right matches the
       action column td's right-padding so the bulk-action button
       cluster aligns with the per-row action buttons. */
    padding: 6px 24px;
    /* White by default so a run of collapsed project headers reads as a
       clean list rather than a slab of grey. Expanded headers get a subtle
       bottom gradient (rule below) to tie them to the batch rows beneath. */
    background-color: var(--color-surface);
    border-bottom: 1px solid var(--color-outline-variant);
    border-top: 1px solid var(--color-outline-variant);
    font-size: 12px;
    font-weight: 600;
    letter-spacing: 0.02em;
    color: var(--color-on-surface-variant);
    text-align: left;
    transition: background-color 120ms;
}
/* Expanded project header → a soft gradient rising from the bottom edge to
   ~1/3 up, visually connecting the header to the batch rows it reveals.
   Applies on both the home and batches tables (keyed on aria-expanded). */
.batches-row--group-header[aria-expanded="true"] > td:first-child {
    background-image: linear-gradient(to top,
        var(--color-neutral-100) 0%, transparent 33%);
}
/* Two-up layout inside the header: project name left, refresh button right */
/* Empty-state row inside a project group — currently only rendered
   under draft projects that haven't received any batches yet. Reads as
   a quiet helper line on the same surface as a normal batch row. */
.batches-row--empty > td {
    padding: 14px 16px !important;
    background: var(--color-surface);
}
.batches-row--empty__msg {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    color: var(--color-on-surface-muted);
    font-size: 12px;
}
.batches-row--empty__msg > svg {
    width: 14px;
    height: 14px;
    stroke-width: 1.5;
}

.batches-row--group-header__bar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
}
/* Group-level "select all batches in this project" checkbox. Sits at
   the start of the bar, before the priority badge and project name,
   visually echoing the per-batch checkbox column directly below it. */
.batches-row--group-header__select {
    flex: 0 0 auto;
    margin-right: 4px;
}
/* Left half — CRM chip beside the project ID + name, with breathing room between them */
.batches-row--group-header__name {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    /* ``flex:1`` lets the name section absorb the slack the new
       checkbox leaves, so the actions cluster still hugs the right
       edge of the bar via ``justify-content: space-between``. */
    flex: 1 1 auto;
    min-width: 0;
}
/* Right half — stacked icon buttons (download / reconcile / recalc) */
.batches-row--group-header__actions {
    display: inline-flex;
    align-items: center;
    gap: 4px;
}
/* First header row: skip the top border so the table's thead provides the boundary. */
.batches-row--group-header:first-child > td:first-child {
    border-top: none;
}

/* ---- Collapsible project groups (home "My Active Projects" only) ---- */
/* The header row is a toggle: clicking it expands/collapses the project's
   batch rows (which are server-rendered ``hidden``). Scoped to the home
   table so the batches-list view's group headers are unaffected. */
.data-table--home-batches .batches-row--group-header {
    cursor: pointer;
}
.data-table--home-batches .batches-row--group-header:hover > td:first-child,
.data-table--home-batches .batches-row--group-header:focus-visible > td:first-child {
    background-color: var(--color-neutral-50);
}
.data-table--home-batches .batches-row--group-header:focus-visible {
    outline: 2px solid var(--color-primary);
    outline-offset: -2px;
}
.batches-group-chevron {
    flex: 0 0 auto;
    width: 16px;
    height: 16px;
    color: var(--color-on-surface-muted);
    transition: transform 150ms ease;
}
.batches-row--group-header[aria-expanded="true"] .batches-group-chevron {
    transform: rotate(90deg);
}
/* Batch count chip on the right of a collapsed/expanded project header. */
.batches-group-count {
    flex: 0 0 auto;
    font-size: 11px;
    font-weight: 500;
    letter-spacing: normal;
    text-transform: none;
    color: var(--color-on-surface-muted);
}

/* ---- Collapsible project groups (batches list) ---- */
/* The chevron button (and empty header areas) toggle each project's batch
   rows. The header keeps its select-all + bulk-action controls. */
.batches-flat .batches-row--group-header {
    cursor: pointer;
}
.batches-flat .batches-row--group-header:hover > td:first-child {
    background-color: var(--color-neutral-50);
}
.batches-group-toggle {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 2px;
    margin: 0;
    background: none;
    border: none;
    border-radius: var(--radius-control);
    cursor: pointer;
    color: var(--color-on-surface-muted);
}
.batches-group-toggle:hover {
    background: var(--color-neutral-200);
    color: var(--color-on-surface);
}
.batches-group-toggle:focus-visible {
    outline: 2px solid var(--color-primary);
    outline-offset: 1px;
}


/* =============================================
   Batches table (single-table layout)

   One table contains every project and every batch. Project rows are <tr>s with
   a colspan-8 <td> holding their own bar layout; batch rows under each project
   fill the global batch-column headers in <thead>. Each project + its batches
   sit inside their own <tbody class="batches-group">, so toggling the .--open
   class on that tbody shows/hides the batches without touching the rest.
   ============================================= */
.batches-table {
    width: 100%;
    border-collapse: collapse;
    background: var(--color-surface);
}

/* Global headers — only describe the batch columns; project rows span across them */
.batches-table thead th {
    padding: 11px 16px;
    background: var(--color-neutral-50);
    border-bottom: 1px solid var(--color-outline-variant);
    font-size: 10px;
    font-weight: 700;
    letter-spacing: 0.09em;
    color: var(--color-on-surface-muted);
    text-transform: uppercase;
    text-align: left;
}
.batches-table__th-action { width: 1%; }   /* Actions column sized to content */

/* ----- Project group row -------------------------------------------------- */

.batches-group__project > td {
    padding: 0;                            /* the .batches-group__bar carries the padding */
    background: var(--color-neutral-25);   /* ultra-soft tint distinguishes project rows from batches */
    border-bottom: 1px solid var(--color-outline-variant);
}

/* Slim 6-column project bar: chevron · priority · title (fills) · status · counts · menu.
   All the heavy chrome (progress bar, region badge, CRM chip, refresh button) has been
   stripped or folded into the title's secondary line — keeps the project row reading
   as a section divider, not a data row. */
.batches-group__bar {
    display: grid;
    grid-template-columns: 24px 24px minmax(0, 1fr) 100px 160px 32px;
    align-items: center;
    justify-items: start;
    gap: 16px;
    padding: 8px 16px;
}
.batches-group__bar > .badge {
    justify-self: center;
}

.batches-group__toggle {
    width: 24px;
    height: 24px;
    padding: 0;
    background: transparent;
    border: 0;
    border-radius: var(--radius-control);
    color: var(--color-on-surface-muted);
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    transition: background-color 120ms;
}
.batches-group__toggle:hover {
    background: var(--color-neutral-100);
    color: var(--color-on-surface);
}
.batches-group__toggle > svg {
    width: 14px;
    height: 14px;
    stroke-width: 2;
    transition: transform 200ms ease;
}
.batches-group--open .batches-group__toggle > svg {
    transform: rotate(90deg);             /* chevron-right → chevron-down when open (tree-style affordance) */
}

.batches-group__title {
    display: flex;
    align-items: center;
    gap: 8px;
    min-width: 0;
    max-width: 100%;
}
.batches-group__title > .cell-stack {
    min-width: 0;
    flex: 1 1 auto;
}
.batches-group__title .cell-stack__primary {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.batches-group__meta {
    font-size: 12px;
    color: var(--color-on-surface-variant);
    white-space: nowrap;
}
.batches-group__meta-sep {
    margin: 0 4px;
    color: var(--color-on-surface-muted);
    opacity: 0.6;
}

/* ----- Batch rows --------------------------------------------------------- */

.batches-group__batch > td {
    padding: 16px;
    border-bottom: 1px solid var(--color-neutral-100);   /* lighter divider than between groups */
}

/* Collapsed groups hide their batch rows while keeping the project row */
.batches-group:not(.batches-group--open) .batches-group__batch {
    display: none;
}

/* The last batch row in an open group should not double its border with the
   next group's project border — drop its bottom border. */
.batches-group--open .batches-group__batch:last-child > td {
    border-bottom: none;
}


/* =============================================
   Reviewer screen
   ============================================= */

/* --- Toolbar --------------------------------------------------------------- */

.review-toolbar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 16px;
    margin-bottom: 20px;
    flex-wrap: wrap;
}
.review-toolbar__title-wrap {
    display: flex;
    align-items: center;
    gap: 12px;
    flex: 0 1 auto;
    min-width: 0;
}
.review-toolbar__title {
    margin: 0;                                    /* override .page-title's 28px bottom margin */
    flex: 0 1 auto;
    min-width: 0;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
/* Title-as-link — inherit the heading's colour/size so the page
   header still reads as a title, but mark it as clickable on hover
   with a subtle primary tint. */
.review-toolbar__title-link {
    color: inherit;
    text-decoration: none;
}
.review-toolbar__title-link:hover {
    color: var(--color-primary-700);
}
.review-toolbar__actions {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
}

/* Batch tag chips — small pill-shaped, with a × remove button */
.batch-tags {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 4px 8px;
    background: var(--color-surface);
    border: 1px solid var(--color-outline-variant);
    border-radius: 999px;
}
.batch-tag {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 2px 4px 2px 10px;
    background: transparent;
    border: 0;
    border-radius: 999px;
    font-family: inherit;
    font-size: 12px;
    font-weight: 500;
    color: var(--color-on-surface);
    cursor: default;
}
.batch-tag__remove {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 16px;
    height: 16px;
    padding: 0;
    background: transparent;
    border: 0;
    border-radius: 50%;
    color: var(--color-on-surface-muted);
    cursor: pointer;
    transition: background-color 120ms, color 120ms;
}
.batch-tag__remove:hover {
    background: var(--color-neutral-100);
    color: var(--color-on-surface);
}
.batch-tag__remove > svg {
    width: 10px;
    height: 10px;
    stroke-width: 2;
}
.batch-tag--more {
    cursor: pointer;
    color: var(--color-on-surface-variant);
    transition: color 120ms;
}
.batch-tag--more:hover { color: var(--color-on-surface); }
.batch-tag--more > svg {
    width: 12px;
    height: 12px;
    stroke-width: 1.5;
}

/* Counter pill — icon + number, used for pass/reject summary chips */
.counter-pill {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 4px 12px;
    border-radius: 999px;
    font-size: 13px;
    font-weight: 600;
}
.counter-pill > svg {
    width: 14px;
    height: 14px;
    stroke-width: 1.5;
}
.counter-pill--success {
    background: var(--color-success-50);
    color: var(--color-success-700);
}
.counter-pill--warning {
    background: var(--color-warning-50);
    color: var(--color-warning-700);
}

/* Reviewer toolbar summary chips — Accepted / Rejected / To reconcile.
   Compact, labelled, colour-coded pills that read at a glance. */
.review-stat-chips { display: inline-flex; align-items: center; gap: 8px; }
.review-stat-chip {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 3px 10px;
    border-radius: 999px;
    font-size: 12px;
    line-height: 1.5;
    white-space: nowrap;
}
.review-stat-chip > svg { width: 14px; height: 14px; stroke-width: 1.75; }
.review-stat-chip__label { font-weight: 500; }
.review-stat-chip__num   { font-weight: 700; font-variant-numeric: tabular-nums; }
.review-stat-chip--accept {
    background: var(--color-success-50);
    color: var(--color-success-700);
}
.review-stat-chip--reject {
    background: var(--color-error-50, #fef2f2);
    color: var(--color-error-700, #b91c1c);
}
.review-stat-chip--recon {
    background: var(--color-warning-50);
    color: var(--color-warning-700);
}
/* Nothing to reconcile → calm it down so it doesn't read as a warning. */
.review-stat-chip--recon.review-stat-chip--zero {
    background: var(--color-neutral-50);
    color: var(--color-on-surface-muted);
}


/* --- Review shell: fixed-height layout (Excel-like, page does not scroll) -- */

.review-shell {
    display: flex;
    flex-direction: column;
    height: calc(100vh - 48px);   /* .app-main has 16px top + 32px bottom padding */
    min-height: 0;                 /* allow flex child to shrink/scroll inside */
}

/* Full-bleed page variant — the page IS the table. .app-main loses its padding;
   the toolbar carries its own internal padding + a single border-bottom that becomes
   the only horizontal divider between the page title area and the data grid below. */
.app-main--full {
    padding: 0;
    display: flex;
    flex-direction: column;
    height: 100vh;
}
.app-main--full .review-shell {
    flex: 1;
    height: auto;
    min-height: 0;
}
.app-main--full .review-toolbar {
    margin: 0;
    padding: 16px 24px;
    border-bottom: 1px solid var(--color-outline-variant);
    flex-shrink: 0;
}

/* Over-limit banner — shown when the selected batches would return more
   rows than the reviewer can render efficiently. The service skips the
   data fetch entirely and the user is asked to narrow the batches
   dropdown. Anchored to the page flow, not the toolbar. */
.review-overlimit {
    flex: 1;
    margin: 24px;
    padding: 20px 24px;
    display: flex;
    align-items: flex-start;
    gap: 14px;
    border: 1px solid var(--color-warning-200);
    border-radius: 10px;
    background: var(--color-warning-50);
    color: var(--color-on-surface);
    max-width: 720px;
    align-self: flex-start;
}
.review-overlimit__icon {
    flex: 0 0 auto;
    width: 24px;
    height: 24px;
    color: var(--color-warning-600);
    stroke-width: 2;
}
.review-overlimit__body  { flex: 1; min-width: 0; }
.review-overlimit__title {
    font-size: 14px;
    font-weight: 600;
    color: var(--color-on-surface);
    margin-bottom: 6px;
}
.review-overlimit__text {
    font-size: 13px;
    line-height: 1.5;
    color: var(--color-on-surface-muted);
}
.review-overlimit__text strong { color: var(--color-on-surface); font-weight: 600; }


/* ───── Header right-click menu ───────────────────────────────────────
   Portalled to <body> so it isn't clipped by the panes' overflow:hidden.
   Two top-level items: Sort and Uniques. Uniques has a submenu pinned
   to the right of the parent item. */
.review-thmenu {
    position: fixed;
    min-width: 180px;
    background: var(--color-surface);
    border: 1px solid var(--color-outline-variant);
    border-radius: 8px;
    box-shadow: 0 8px 24px rgba(15, 23, 42, 0.12);
    padding: 6px 0;
    font-size: 13px;
    z-index: 1000;
    user-select: none;
}
.review-thmenu__col-name {
    padding: 4px 12px 6px;
    font-size: 10px;
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--color-on-surface-muted);
    border-bottom: 1px solid var(--color-outline-variant);
    margin-bottom: 4px;
}
.review-thmenu__item {
    position: relative;
    padding: 7px 14px;
    cursor: pointer;
    color: var(--color-on-surface);
    display: flex;
    align-items: center;
    gap: 8px;
    white-space: nowrap;
}
.review-thmenu__item:hover {
    background: var(--color-neutral-50);
}
.review-thmenu__item[aria-disabled="true"] {
    color: var(--color-on-surface-muted);
    cursor: not-allowed;
    background: transparent;
}
.review-thmenu__chev {
    margin-left: auto;
    font-size: 12px;
    color: var(--color-on-surface-muted);
}
.review-thmenu__divider {
    height: 1px;
    background: var(--color-outline-variant);
    margin: 4px 0;
}
/* Small caption above a group of options (e.g. the flag checkboxes). */
.review-thmenu__hint {
    padding: 6px 14px 2px;
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--color-on-surface-muted);
}
/* Checkbox-style option (P/A/N/G/D/U flag filter) — a leading tick box. */
.review-thmenu__item--check { padding-left: 12px; }
.review-thmenu__box {
    flex: 0 0 auto;
    width: 15px;
    height: 15px;
    border: 1.5px solid var(--color-outline-variant);
    border-radius: 4px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 10px;
    line-height: 1;
    color: #fff;
    background: var(--color-surface);
}
.review-thmenu__box--on {
    background: var(--color-primary-600);
    border-color: var(--color-primary-600);
}

/* ───── Cell right-click menu (row-scoped actions) ────────────────────
   Tiny portalled menu opened by right-clicking any data cell. Currently
   one action ("View record"); structure mirrors .review-thmenu so adding
   more rows later is mechanical. */
.review-cellmenu {
    position: fixed;
    min-width: 160px;
    background: var(--color-surface);
    border: 1px solid var(--color-outline-variant);
    border-radius: 8px;
    box-shadow: 0 8px 24px rgba(15, 23, 42, 0.18);
    padding: 6px 0;
    font-size: 13px;
    z-index: 1000;
    user-select: none;
}
/* Over the uniques modal (z-index 1100) and its chip menus (1200) — the
   base 1000 renders behind the modal, so the menu would be invisible. */
.review-cellmenu--over-modal {
    z-index: 1300;
}
.review-cellmenu__item {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 8px 14px;
    cursor: pointer;
    color: var(--color-on-surface);
}
.review-cellmenu__item:hover { background: var(--color-neutral-50); }
.review-cellmenu__icon {
    width: 14px;
    height: 14px;
    color: var(--color-on-surface-muted);
    stroke-width: 1.5;
    flex: 0 0 auto;
}

/* ───── Respondent record-view modal ──────────────────────────────────
   Full-questionnaire view: each question's label + title above the
   respondent's answer + any per-cell flag chips. Same backdrop pattern
   as the filter/uniques modals. */
.review-record-backdrop {
    position: fixed;
    inset: 0;
    background: rgba(15, 23, 42, 0.45);
    z-index: 1100;
    display: flex;
    align-items: center;
    justify-content: center;
}
.review-record-modal {
    background: var(--color-surface);
    border-radius: 12px;
    box-shadow: 0 24px 60px rgba(15, 23, 42, 0.24);
    width: min(760px, calc(100vw - 48px));
    max-height: calc(100vh - 80px);
    display: flex;
    flex-direction: column;
}
.review-record-modal__head {
    padding: 18px 24px 14px;
    border-bottom: 1px solid var(--color-outline-variant);
    display: grid;
    grid-template-columns: 1fr auto;
    align-items: start;
    column-gap: 12px;
    row-gap: 8px;
}
.review-record-modal__title {
    font-size: 16px;
    font-weight: 600;
    color: var(--color-on-surface);
    grid-row: 1;
    grid-column: 1;
}
.review-record-modal__meta {
    grid-row: 2;
    grid-column: 1 / -1;
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 10px 18px;
    font-size: 12px;
    color: var(--color-on-surface-muted);
}
/* Left zone — review state: status + AI score + TPANGDU strip, grouped
   in a subtle card so they read as one "review summary" block. */
.rrv-state {
    display: inline-flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 14px;
    padding: 6px 12px;
    background: var(--color-neutral-25);
    border: 1px solid var(--color-outline-variant);
    border-radius: 10px;
}
.rrv-stat {
    display: inline-flex;
    align-items: center;
    gap: 6px;
}
.rrv-stat__k {
    font-size: 10px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--color-on-surface-muted);
}
/* TPANGDU: header letters stacked over this row's counts (columns align
   because both strips share the same fixed cell widths). */
.rrv-stat--flags {
    flex-direction: column;
    gap: 1px;
}
/* Right zone — identity metadata as muted key/value pairs. */
.rrv-ident {
    display: inline-flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 6px 18px;
}
.rrv-ident__item {
    display: inline-flex;
    align-items: baseline;
    gap: 5px;
}
.rrv-ident__k {
    font-size: 10px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--color-on-surface-muted);
    opacity: 0.75;
}
.review-record-modal__close {
    grid-row: 1;
    grid-column: 2;
    background: transparent;
    border: 0;
    width: 30px;
    height: 30px;
    border-radius: 6px;
    cursor: pointer;
    color: var(--color-on-surface-muted);
}
.review-record-modal__close:hover {
    background: var(--color-neutral-50);
    color: var(--color-on-surface);
}
.review-record-modal__body {
    padding: 12px 20px;
    overflow: auto;
    display: flex;
    flex-direction: column;
    gap: 8px;
}
.review-record-modal__foot {
    padding: 12px 22px;
    border-top: 1px solid var(--color-outline-variant);
    display: flex;
    justify-content: flex-end;
    gap: 8px;
}

/* ---- Record-view control row -------------------------------------
   Sits between the head + body. Holds nav buttons (Prev/Next), the
   record-status dropdown, the 2R flag toggle, and the
   "Answered only" filter. Mirrors the grid view's controls but
   compact for the modal context. */
/* Control row — flush, low-chrome strip. Single subtle backdrop, no
   per-group cards. Visual grouping is by a single 1px divider between
   navigation and the row-level controls. */
.review-record-modal__controls {
    display: flex;
    align-items: center;
    gap: 14px;
    padding: 8px 22px;
    border-bottom: 1px solid var(--color-outline-variant);
    background: var(--color-surface);
}
.rrv-ctrl {
    display: inline-flex;
    align-items: center;
    gap: 6px;
}
/* Thin vertical rule used between control groups. */
.rrv-sep {
    width: 1px;
    align-self: stretch;
    background: var(--color-outline-variant);
    margin: 2px 4px;
}
/* Push the answered-only filter to the right edge of the row. */
.rrv-ctrl--filter { margin-left: auto; }

/* Icon-only nav buttons — pill grouping with the position counter:
   the three sit together with no gaps so they read as one navigator. */
.rrv-ctrl--nav { gap: 0; }
.rrv-btn {
    appearance: none;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-family: inherit;
    color: var(--color-on-surface-muted);
    background: transparent;
    border: 1px solid var(--color-outline-variant);
    cursor: pointer;
    transition: background-color 120ms, color 120ms, border-color 120ms;
}
.rrv-btn--icon {
    width: 28px;
    height: 28px;
    padding: 0;
}
.rrv-btn--icon:first-child  { border-radius: 7px 0 0 7px; }
.rrv-btn--icon:last-child   { border-radius: 0 7px 7px 0; margin-left: -1px; }
.rrv-btn:hover:not(:disabled) {
    color: var(--color-on-surface);
    background: var(--color-neutral-50);
    border-color: var(--color-outline);
    z-index: 1;
}
.rrv-btn:disabled {
    opacity: 0.35;
    cursor: not-allowed;
}
.rrv-btn > svg {
    width: 14px;
    height: 14px;
    stroke-width: 2;
}
/* Position counter wedged between the two arrow buttons. */
.rrv-pos {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 64px;
    height: 28px;
    padding: 0 10px;
    font-size: 12px;
    font-weight: 500;
    color: var(--color-on-surface);
    font-variant-numeric: tabular-nums;
    background: var(--color-surface);
    border-top: 1px solid var(--color-outline-variant);
    border-bottom: 1px solid var(--color-outline-variant);
    margin-left: -1px;
}

/* Answered-only switch reuses the shared .toggle component (see "Toggle
   switch" section above) so it matches the user Active/Inactive control. */

/* CSS-driven answered-only filter: hide cards we marked as empty in
   openRecordView. The body needs an explicit modifier class so the
   default view (showing everything) remains. */
.review-record-modal__body--answered-only .review-record-item--empty {
    display: none;
}

/* Meta strip chips — re-uses the design language of the row-status
   widget for consistency, but standalone (no chip portalling). */
.review-record-status {
    display: inline-flex;
    align-items: center;
    padding: 2px 10px;
    border-radius: 999px;
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.02em;
}
.review-record-status--success { background: var(--color-success-75); color: var(--color-success-700); }
.review-record-status--error   { background: var(--color-error-75);   color: var(--color-error-700);   }
.review-record-status--warning { background: var(--color-warning-75); color: var(--color-warning-700); }
.review-record-status--neutral { background: var(--color-neutral-100); color: var(--color-on-surface-muted); }
.review-record-meta__item { color: var(--color-on-surface-muted); }

/* Each question / answer card. Compact treatment: question header sits
   directly above the answer with no divider; the answer block carries
   the accent rail so the hierarchy is still legible at a glance. */
.review-record-item {
    background: var(--color-surface);
    border: 1px solid var(--color-outline-variant);
    border-radius: 8px;
    padding: 10px 12px;
    /* Focusable for keyboard navigation; the outline below is the
       focus indicator so we disable the browser's default. */
    outline: none;
}
/* Active card — driven by the modal's arrow-key navigation. Visible
   accent ring that doesn't depend on focus, since clicking inside the
   modal or on the controls may shift focus away from the card. */
.review-record-item--active,
.review-record-item:focus-visible {
    border-color: var(--color-primary-500);
    box-shadow: 0 0 0 2px color-mix(in srgb, var(--color-primary-500) 18%, transparent);
}
.review-record-item__qhead {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: 8px;
    margin-bottom: 6px;
}
/* Stat chip in the record card's qhead floats to the right edge so
   it lines up across cards and reads as a grace note, not as part of
   the question label. ``align-self`` centers it vertically against
   the baseline-aligned text. */
.review-record-item__qhead > .stat-chip {
    margin-left: auto;
    align-self: center;
}
.review-record-item__qlabel {
    font-size: 10.5px;
    font-weight: 700;
    color: var(--color-primary-700);
    text-transform: uppercase;
    letter-spacing: 0.06em;
}
.review-record-item__qtitle {
    font-size: 13px;
    color: var(--color-on-surface);
    font-weight: 600;
    line-height: 1.35;
}
/* Answer block — pale background + left accent rail. No eyebrow caption;
   the rail and offset are enough to mark this as the respondent's reply. */
.review-record-item__answer {
    position: relative;   /* anchor for the top-right flag chips */
    background: var(--color-neutral-25);
    border-left: 3px solid var(--color-primary-300);
    border-radius: 0 6px 6px 0;
    padding: 8px 12px;
    font-size: 13px;
    line-height: 1.5;
    color: var(--color-on-surface);
    white-space: pre-wrap;
    overflow-wrap: anywhere;
}
/* Reserve room at the top-right so flag chips never overlap the answer's
   first line (the reviewer grid overlaps, but answers here run wider). */
.review-record-item:has(.response-flag:not(.response-flag--brand)) .review-record-item__answer {
    padding-right: 72px;
}
.review-record-item__answer--empty {
    color: var(--color-on-surface-muted);
    font-style: italic;
    border-left-color: var(--color-outline);
}
/* Flagged answer — mirror the reviewer grid's flagged-cell treatment:
   red text (weight 500), a soft red gradient wash, and a red accent rail.
   Triggered whenever the card carries any flag chip (in its qhead). */
.review-record-item:has(.response-flag:not(.response-flag--brand)) .review-record-item__answer {
    color: var(--color-error-600);
    font-weight: 500;
    border-left-color: var(--color-error-500);
    background: linear-gradient(
        to bottom,
        color-mix(in srgb, var(--color-error-500) 16%, transparent) 0%,
        color-mix(in srgb, var(--color-error-500) 6%, transparent) 45%,
        var(--color-neutral-25) 100%
    );
}
/* Flags float at the top-right INSIDE the answer box — same placement as
   the reviewer grid's flagged cells (.response-flags at top:3px right:3px). */
.review-record-item__flags {
    position: absolute;
    top: 6px;
    right: 8px;
    display: inline-flex;
    gap: 4px;
    z-index: 1;
}
.review-thmenu__sub {
    position: fixed;
    min-width: 160px;
    background: var(--color-surface);
    border: 1px solid var(--color-outline-variant);
    border-radius: 8px;
    box-shadow: 0 8px 24px rgba(15, 23, 42, 0.12);
    padding: 6px 0;
    font-size: 13px;
    z-index: 1001;
}

/* ───── Uniques modal ─────────────────────────────────────────────────
   Backdrop + centered card; the card holds a scrollable table of
   (value, count, accept/reject). Reuses status-chip styling for the
   action cell. */
.review-uniques-backdrop {
    position: fixed;
    inset: 0;
    /* Lower opacity so the live grid behind the modal stays legible —
       in particular, the per-cell chips we mirror onto the matching
       question cells while a unique is being flagged. */
    background: rgba(15, 23, 42, 0.18);
    z-index: 1100;
    display: flex;
    align-items: center;
    justify-content: center;
}
.review-uniques-modal {
    background: var(--color-surface);
    border-radius: 12px;
    box-shadow: 0 24px 60px rgba(15, 23, 42, 0.24);
    width: min(720px, calc(100vw - 48px));
    /* Widen in Brand Mode to seat the extra Identified Brand column without
       squeezing the Value column. */
    transition: width 140ms ease;
    /* Fixed height (not max-height) so the window doesn't grow/shrink as
       the operator steps between questions with different row counts — the
       body scrolls internally instead. */
    height: min(760px, calc(100vh - 80px));
    display: flex;
    flex-direction: column;
    position: relative;   /* anchor for the loading overlay */
}
.review-uniques-modal--brand {
    width: min(920px, calc(100vw - 48px));
}
/* Loading overlay — a light wash + centered spinner over the whole modal.
   Shown only when a fetch is slow (JS reveals it after ~180ms), so fast
   prev/next hops keep the current table visible with no flicker. */
.review-uniques-modal__spinner {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    background: color-mix(in srgb, var(--color-surface) 62%, transparent);
    border-radius: 12px;
    z-index: 4;
    pointer-events: none;
}
.review-uniques-modal__spinner-svg {
    width: 30px;
    height: 30px;
    color: var(--color-primary-600);
    animation: uniques-spin 0.8s linear infinite;
}
@keyframes uniques-spin { to { transform: rotate(360deg); } }
/* Brand-analysis progress popup — a small card floated over the modal with a
   spinner + live status text, on a soft wash that blocks interaction while a
   run is in flight. */
.review-uniques-progress {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    background: color-mix(in srgb, var(--color-surface) 55%, transparent);
    border-radius: 12px;
    z-index: 6;   /* above the plain loading spinner (4) */
}
.review-uniques-progress__card {
    display: flex;
    align-items: center;
    gap: 14px;
    max-width: min(360px, calc(100% - 48px));
    padding: 16px 20px;
    background: var(--color-surface);
    border: 1px solid var(--color-outline-variant);
    border-radius: 12px;
    box-shadow: 0 12px 32px rgba(15, 23, 42, 0.18);
}
.review-uniques-progress__spinner {
    flex: 0 0 auto;
    width: 26px;
    height: 26px;
    color: var(--tw-cyan-600);
    animation: uniques-spin 0.8s linear infinite;
}
.review-uniques-progress__text {
    min-width: 0;
}
.review-uniques-progress__title {
    font-size: 13px;
    font-weight: 600;
    color: var(--color-on-surface);
}
.review-uniques-progress__status {
    margin-top: 2px;
    font-size: 12px;
    color: var(--color-on-surface-muted);
}
.review-uniques-modal__head {
    padding: 18px 22px;
    border-bottom: 1px solid var(--color-outline-variant);
    display: flex;
    flex-wrap: wrap;   /* action buttons drop to their own row below the question */
    align-items: flex-start;
    gap: 10px;
    position: relative;   /* anchor for the portalled multi-question panel */
}
.review-uniques-modal__title-stack {
    /* Fills the top row so the close button is pushed to the top-right; the
       question text wraps within it. The controls row wraps below. */
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 4px;
}
/* Controls row — full-width row below the title/question: prev/next counter
   on the left, action buttons pushed to the right. */
.review-uniques-modal__controls {
    flex: 0 0 100%;
    display: flex;
    align-items: center;
    gap: 10px;
}
/* Fixed header layout so the popup doesn't reshuffle as the operator
   steps between questions: title + subtitle always on ONE line (subtitle
   truncates if tight), and the question text always occupies exactly two
   lines' worth of space (clamped + reserved), so the header height stays
   constant whether a question is one line or three. */
.review-uniques-modal__title-row {
    display: flex;
    align-items: baseline;
    gap: 10px;
    flex-wrap: nowrap;
    min-width: 0;
    /* Clear the absolutely-positioned close button in the top-right corner. */
    padding-right: 40px;
}
.review-uniques-modal__title {
    font-size: 16px;
    font-weight: 600;
    color: var(--color-on-surface);
    white-space: nowrap;
    flex: 0 0 auto;
}
.review-uniques-modal__subtitle {
    font-size: 12px;
    color: var(--color-on-surface-muted);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    min-width: 0;
    flex: 0 1 auto;
}
.review-uniques-modal__question {
    font-size: 13px;
    color: var(--color-on-surface-muted);
    line-height: 1.35;
    /* Clamp to two lines and reserve that height so a short question
       doesn't shrink the header (and a long one doesn't grow it). */
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    min-height: 2.7em;      /* ≈ 2 × line-height */
}
/* Setting an explicit ``display`` above overrides the ``[hidden]``
   attribute's ``display:none``; restore it so non-question columns can
   still hide the question line. */
.review-uniques-modal__question[hidden] { display: none; }
.review-uniques-modal__close {
    /* Pinned to the modal's top-right corner (head is position: relative). */
    position: absolute;
    top: 14px;
    right: 16px;
    background: transparent;
    border: 0;
    width: 32px;
    height: 32px;
    border-radius: 6px;
    cursor: pointer;
    color: var(--color-on-surface-muted);
}
.review-uniques-modal__close:hover {
    background: var(--color-neutral-50);
    color: var(--color-on-surface);
}
/* Head action cluster — brand actions + Brand Mode + Multi question. Pushed to
   the right of the controls row, opposite the prev/next counter. */
.review-uniques-modal__head-actions {
    margin-left: auto;
    display: flex;
    align-items: center;
    gap: 6px;
}
/* Question traversal — a segmented < N / total > pill (prev / counter /
   next), mirroring the record view's nav control. */
.review-uniques-qnav {
    display: inline-flex;
    align-items: center;
}
.review-uniques-qnav__btn {
    appearance: none;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 28px;
    height: 28px;
    padding: 0;
    color: var(--color-on-surface-muted);
    background: transparent;
    border: 1px solid var(--color-outline-variant);
    cursor: pointer;
    transition: background-color 120ms, color 120ms, border-color 120ms;
}
.review-uniques-qnav__btn:first-child { border-radius: 7px 0 0 7px; }
.review-uniques-qnav__btn:last-child  { border-radius: 0 7px 7px 0; margin-left: -1px; }
.review-uniques-qnav__btn:hover:not(:disabled) {
    color: var(--color-on-surface);
    background: var(--color-neutral-50);
    border-color: var(--color-outline);
    z-index: 1;
}
.review-uniques-qnav__btn:disabled {
    opacity: 0.35;
    cursor: not-allowed;
}
.review-uniques-qnav__btn > svg {
    width: 14px;
    height: 14px;
    stroke-width: 2;
}
.review-uniques-qnav__pos {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 62px;
    height: 28px;
    padding: 0 10px;
    font-size: 12px;
    font-weight: 500;
    color: var(--color-on-surface);
    font-variant-numeric: tabular-nums;
    background: var(--color-surface);
    border-top: 1px solid var(--color-outline-variant);
    border-bottom: 1px solid var(--color-outline-variant);
    margin-left: -1px;
}
/* Multi question toggle button. */
.review-uniques-modal__multiq {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    padding: 6px 10px;
    /* Neutral at rest; a light primary tint when selected (see --on below).
       The AI Brand Analyser is the exception — always light-primary. */
    background: var(--color-neutral-50);
    border: 1px solid transparent;
    border-radius: 8px;
    font-family: inherit;
    font-size: 12px;
    font-weight: 600;
    color: var(--color-on-surface-variant);
    white-space: nowrap;
    cursor: pointer;
    transition: background-color 120ms, color 120ms, border-color 120ms;
}
.review-uniques-modal__multiq:hover {
    background: var(--color-neutral-100);
    color: var(--color-on-surface);
}
.review-uniques-modal__multiq > svg {
    width: 14px;
    height: 14px;
    stroke-width: 1.5;
}
/* Selected/active — brand mode on, or sibling questions pooled in. Buttons
   rest neutral and adopt a light PRIMARY tint (not a solid fill) when active. */
.review-uniques-modal__multiq--on {
    background: var(--color-primary-100);
    color: var(--color-primary-700);
}
.review-uniques-modal__multiq--on:hover {
    background: color-mix(in srgb, var(--color-primary-600) 15%, var(--color-primary-100));
    color: var(--color-primary-700);
}
/* Portalled question checklist under the head. */
.review-uniques-multiq-panel {
    position: absolute;
    top: calc(100% - 6px);
    right: 22px;
    z-index: 20;
    width: min(360px, calc(100vw - 60px));
    background: var(--color-surface);
    border: 1px solid var(--color-outline-variant);
    border-radius: 10px;
    box-shadow: 0 16px 40px rgba(15, 23, 42, 0.22);
    overflow: hidden;
}
.review-uniques-multiq-panel__head {
    padding: 10px 14px;
    font-size: 12px;
    font-weight: 600;
    color: var(--color-on-surface-muted);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    border-bottom: 1px solid var(--color-outline-variant);
}
.review-uniques-multiq-panel__list {
    max-height: 320px;
    overflow: auto;
    padding: 6px;
}
.review-uniques-multiq-opt {
    display: flex;
    align-items: baseline;
    gap: 8px;
    padding: 7px 8px;
    border-radius: 6px;
    cursor: pointer;
}
.review-uniques-multiq-opt:hover {
    background: var(--color-neutral-50);
}
.review-uniques-multiq-opt input {
    margin: 0;
    flex: 0 0 auto;
    align-self: center;
}
.review-uniques-multiq-opt__id {
    font-family: var(--font-mono, monospace);
    font-size: 12px;
    font-weight: 600;
    color: var(--color-on-surface);
    flex: 0 0 auto;
}
.review-uniques-multiq-opt__title {
    font-size: 12px;
    color: var(--color-on-surface-muted);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    min-width: 0;
}
.review-uniques-multiq-panel__foot {
    display: flex;
    justify-content: flex-end;
    gap: 8px;
    padding: 10px 14px;
    border-top: 1px solid var(--color-outline-variant);
}
.review-uniques-multiq-panel__foot .btn {
    padding: 6px 14px;
    font-size: 12px;
    min-width: 0;
}
.review-uniques-modal__body {
    overflow: auto;
    /* No top/bottom padding — the sticky thead has to be flush with the
       scroll-container's top edge or scrolling rows can peek through the
       padding gap above it. Horizontal padding gives the table breathing
       room from the modal edges (lines up with the head's title gutter at
       22px) without affecting the sticky thead behaviour. */
    padding: 0 22px;
}
.review-uniques-table {
    width: 100%;
    border-collapse: collapse;
    font-size: 13px;
    /* Vertical rails on the left + right edges of the table. Combined
       with the body's 22px horizontal padding they sit inset from the
       modal edges, framing the data as a contained block (and matching
       the existing Value↔Count column divider so the table has three
       parallel vertical rules in total). */
    border-left:  1px solid var(--color-outline-variant);
    border-right: 1px solid var(--color-outline-variant);
}
.review-uniques-table thead th {
    position: sticky;
    top: 0;
    /* Neutral tint so the header reads as a separate band from the
       white data rows below. Solid (no transparency) so no scroll
       bleed through during fast scrolling. */
    background: var(--color-neutral-100);
    text-align: left;
    padding: 12px 16px;
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--color-neutral-700);
    /* ``border-collapse: collapse`` lets a ``border-bottom`` render outside
       the sticky cell so it doesn't stick with the header. Drawing the
       divider as an ``inset`` box-shadow puts the line inside the cell's
       own paint area, so it pins along with the header. */
    border-bottom: 0;
    box-shadow: inset 0 -1px 0 var(--color-outline);
    /* Sit above the body cells so scrolled rows slide UNDER the sticky header
       instead of painting over it. Must clear the selected-cell ring
       (``.is-active …::after``, z-index 3) too — otherwise the blue selection
       box paints over the header when its row scrolls up under it. */
    z-index: 4;
}
/* Filterable header (Value / Identified Brand) — label plus a funnel button. */
.review-uniques-table__fhead-in {
    display: inline-flex;
    align-items: center;
    gap: 6px;
}
.uq-filter-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 20px;
    height: 20px;
    padding: 0;
    border: 0;
    border-radius: 4px;
    background: transparent;
    color: var(--color-neutral-500);
    cursor: pointer;
    transition: background-color 120ms, color 120ms;
}
.uq-filter-btn:hover {
    background: var(--color-neutral-200);
    color: var(--color-on-surface);
}
.uq-filter-btn > svg {
    width: 13px;
    height: 13px;
}
/* Active — this column has a filter applied. */
.uq-filter-btn--active {
    color: var(--color-primary-600);
}
.uq-filter-btn--active:hover {
    color: var(--color-primary-700);
}
/* The uniques filter modal reuses the reviewer filter styles but must clear
   the uniques modal (z-index 1100) and its portalled chip menus (1200). */
.review-filter-backdrop--uq {
    z-index: 1300;
}
/* Operator dropdown in the uniques filter body (a native select in place of
   the grid filter's portalled op-chip menu). */
.review-filter-op-select {
    font: inherit;
    font-size: 13px;
    padding: 6px 10px;
    border: 1px solid var(--color-outline-variant);
    border-radius: 6px;
    background: var(--color-surface);
    color: var(--color-on-surface);
    cursor: pointer;
}
.review-filter-op-select:focus {
    outline: none;
    border-color: var(--color-primary-500);
}
.review-uniques-table tbody td {
    /* Match the main grid's data cell padding (.review-row > td: 14px)
       so the value text sits with the same breathing room as in the
       main reviewer. */
    padding: 14px;
    border-bottom: 1px solid var(--color-neutral-150);
    color: var(--color-on-surface);
    vertical-align: middle;
}
.review-uniques-table tbody tr:hover td { background: var(--color-neutral-25); }
/* Selected rows — a contiguous range (Shift+click / Shift+↑↓) or a
   discontiguous pick (Ctrl/Cmd+click). Flag / status actions apply to
   the whole selection at once. Tint only the VALUE cell (not the whole
   row) so it reads as a cell-range selection, matching the active ring. */
.review-uniques-table tbody tr.is-selected td.review-uniques-table__value {
    background: var(--color-primary-50);
}
.review-uniques-table tbody tr.is-selected:hover td.review-uniques-table__value {
    background: color-mix(in srgb, var(--color-primary-100) 70%, var(--color-primary-50));
}
/* Active row — the focus / lead of the selection. Carries the keyboard
   focus for letter-key cell flags (g / p / d / n / a / u). Drawn as a
   2px primary inset ring on the VALUE cell only, matching the main
   reviewer's per-cell selection style (``inset 0 0 0 2px var(--color-primary)``).
   The other cells in the row are untouched. */
.review-uniques-table tbody tr.is-active td.review-uniques-table__value::after {
    content: "";
    position: absolute;
    inset: 0;
    box-shadow: inset 0 0 0 2px var(--color-primary);
    pointer-events: none;
    /* Overlay (not a box-shadow on the td) so the ring stacks ABOVE the
       top-left brand corner ribbon (.response-brand, z-index: 2) instead of
       being clipped by it at the cell's corner. */
    z-index: 3;
}
.review-uniques-table tbody tr { cursor: pointer; }
/* Kill the native text-selection highlight while range-selecting rows so
   Shift+click reads as a row range, not a text drag. */
.review-uniques-table { user-select: none; }
.review-uniques-table__value {
    max-width: 380px;
    overflow-wrap: anywhere;
    /* Anchor for the absolutely-positioned chip strip below — same
       pattern the main grid's ``.review-table__col-q`` uses. */
    position: relative;
    /* Reserve a strip on the right edge so long wrapping text doesn't
       run under the chips when several are stacked. Roughly 4 chip
       slots (4 × 18px + 4 × 2px gap = ~80px). */
    padding-right: 88px;
    /* Vertical separator between Value and Count columns so the value
       text doesn't feel adjacent to the numbers. Applied to both header
       and body cells of this column. */
    border-right: 1px solid var(--color-outline-variant);
}
.review-uniques-table__value .response-flags {
    /* Pin to the top-right of the value cell, matching the main grid's
       ``.response-flags`` exactly (top: 3px; right: 3px) so the chip
       hugs the corner like it does in the main reviewer. */
    position: absolute;
    top: 3px;
    right: 3px;
    display: inline-flex;
    align-items: center;
    gap: 2px;
}
/* Flagged value cell — mirror the reviewer grid's flagged-cell treatment:
   red answer text + a soft red top-wash, so a flagged unique value reads
   the same here as it does in the main grid. */
.review-uniques-table__value:has(.response-flag:not(.response-flag--brand)) {
    color: var(--color-error-600);
    font-weight: 500;
}
.review-uniques-table__value:has(.response-flag:not(.response-flag--brand))::before {
    content: "";
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    height: 28px;
    background: linear-gradient(
        to bottom,
        color-mix(in srgb, var(--color-error-500) 18%, transparent) 0%,
        color-mix(in srgb, var(--color-error-500) 8%, transparent) 45%,
        transparent 100%
    );
    pointer-events: none;
    z-index: 0;
}
/* Lift the value text + chips above the decorative wash. The chip strip
   must KEEP its ``position: absolute`` (top-right pin) — only bump its
   z-index; setting position here would drop it back inline with the text. */
.review-uniques-table__value:has(.response-flag:not(.response-flag--brand)) .review-uniques-table__text {
    position: relative;
    z-index: 1;
}
.review-uniques-table__value:has(.response-flag:not(.response-flag--brand)) .response-flags {
    z-index: 1;
}
/* Partial flagging — only SOME of this value's cells carry SOME of the
   shown flags (not every occurrence has every flag). Recolour the text +
   wash from red to blue so a uniform (all-cells-all-flags) value stays red
   and a mixed one reads distinctly. Same specificity as the red base
   above; placed after it so this wins. */
.review-uniques-table__value--flag-partial:has(.response-flag:not(.response-flag--brand)) {
    color: var(--tw-cyan-600);
}
.review-uniques-table__value--flag-partial:has(.response-flag:not(.response-flag--brand))::before {
    background: linear-gradient(
        to bottom,
        color-mix(in srgb, var(--tw-cyan-500) 18%, transparent) 0%,
        color-mix(in srgb, var(--tw-cyan-500) 8%, transparent) 45%,
        transparent 100%
    );
}
/* Row-number gutter — narrow index rail with a vertical divider after it.
   Shared layout (both header + body cells); the header ``#`` keeps the
   standard header background/text, while the BODY cells get a much lighter
   tint (``!important`` holds it through row hover/selection). */
.review-uniques-table__num {
    width: 1%;
    padding-left: 14px;
    padding-right: 14px;
    text-align: right;
    white-space: nowrap;
    border-right: 1px solid var(--color-outline-variant);
}
.review-uniques-table tbody td.review-uniques-table__num {
    font-family: var(--font-mono);
    font-size: 12px;
    color: var(--color-on-surface-muted);
    background: var(--color-neutral-25) !important;
}
/* Correct Brand column (Brand Mode) — the AI-corrected canonical brand. */
.review-uniques-table__brand-head,
.review-uniques-table__brand {
    width: 200px;
    max-width: 240px;
    border-left: 1px solid var(--color-outline-variant);
    border-right: 1px solid var(--color-outline-variant);
}
.review-uniques-table__brand-name {
    font-weight: 600;
    color: var(--tw-cyan-600);
}
.review-uniques-table__brand-none {
    font-size: 12px;
    font-style: italic;
    color: var(--color-on-surface-muted);
}
/* Editable Identified Brand field — inline (borderless) until hover/focus so
   the column reads as text but invites editing; pick an existing brand from
   the datalist or type a new one. Cyan to match the brand-name treatment. */
.review-uniques-table__brand-input {
    width: 100%;
    box-sizing: border-box;
    padding: 4px 6px;
    font: inherit;
    font-size: 13px;
    font-weight: 600;
    color: var(--tw-cyan-600);
    background: transparent;
    border: 1px solid transparent;
    border-radius: 6px;
    cursor: text;
    transition: background-color 120ms, border-color 120ms;
}
.review-uniques-table__brand-input::placeholder {
    color: var(--color-on-surface-muted);
    font-weight: 400;
}
.review-uniques-table__brand-input:hover {
    border-color: var(--color-outline-variant);
}
.review-uniques-table__brand-input:focus {
    outline: none;
    background: var(--color-surface);
    border-color: var(--color-primary-500);
}
/* Custom Identified Brand dropdown — a portalled, styled combobox list
   anchored under the focused input (replaces the native <datalist>). Above
   the uniques modal (1100) and its chip menus (1200). */
.uq-brand-dropdown {
    position: fixed;
    z-index: 1300;
    min-width: 180px;
    max-width: 320px;
    max-height: 280px;
    overflow-y: auto;
    padding: 4px;
    background: var(--color-surface);
    border: 1px solid var(--color-outline-variant);
    border-radius: 8px;
    box-shadow: 0 12px 32px rgba(15, 23, 42, 0.16);
}
.uq-brand-dropdown__opt {
    padding: 7px 10px;
    font-size: 13px;
    color: var(--color-on-surface);
    border-radius: 6px;
    cursor: pointer;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.uq-brand-dropdown__opt:hover,
.uq-brand-dropdown__opt--active {
    background: var(--color-primary-50);
    color: var(--color-primary-700);
}
.uq-brand-dropdown__empty {
    padding: 8px 10px;
    font-size: 12px;
    color: var(--color-on-surface-muted);
}
/* ── Flip View — one row per identified brand, values folded into chips ── */
.uq-flip-values {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px;
}
.uq-flip-chip {
    display: inline-block;
    max-width: 220px;
    padding: 2px 8px;
    font-size: 12px;
    line-height: 1.5;
    color: var(--color-on-surface);
    background: var(--color-neutral-100);
    border-radius: 999px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    vertical-align: middle;
}
.uq-flip-unbranded {
    font-style: italic;
    color: var(--color-on-surface-muted);
}
/* "+N more" — opens the full value list popup. */
.uq-flip-more {
    padding: 2px 8px;
    font: inherit;
    font-size: 12px;
    font-weight: 600;
    color: var(--color-primary-700);
    background: var(--color-primary-50);
    border: 0;
    border-radius: 999px;
    cursor: pointer;
    white-space: nowrap;
}
.uq-flip-more:hover {
    background: color-mix(in srgb, var(--color-primary-600) 15%, var(--color-primary-50));
}
/* Portalled popup listing every value folded into a brand (scrollable). */
.uq-flip-popup {
    position: fixed;
    z-index: 1300;
    width: min(360px, calc(100vw - 32px));
    max-height: 320px;
    display: flex;
    flex-direction: column;
    background: var(--color-surface);
    border: 1px solid var(--color-outline-variant);
    border-radius: 8px;
    box-shadow: 0 12px 32px rgba(15, 23, 42, 0.16);
    overflow: hidden;
}
.uq-flip-popup__head {
    padding: 10px 12px;
    font-size: 12px;
    font-weight: 600;
    color: var(--color-on-surface-variant);
    border-bottom: 1px solid var(--color-outline-variant);
}
.uq-flip-popup__list {
    overflow-y: auto;
    padding: 4px;
}
.uq-flip-popup__item {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 6px 8px;
    font-size: 13px;
    color: var(--color-on-surface);
    border-radius: 6px;
}
.uq-flip-popup__item:hover { background: var(--color-neutral-50); }
.uq-flip-popup__val {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.uq-flip-popup__cnt {
    flex: 0 0 auto;
    font-family: var(--font-mono);
    font-size: 12px;
    color: var(--color-on-surface-muted);
    font-variant-numeric: tabular-nums;
}
.review-uniques-table__count {
    font-family: var(--font-mono);
    font-size: 12px;
    color: var(--color-on-surface-muted);
    text-align: center;
    width: 80px;
    white-space: nowrap;
}
/* P·A·N·G·D·U column — per-flag counts for the value, using the reviewer
   grid's flag-strip look (per-flag tinted header letters + coloured body
   digits). Centered, sized to the strip. */
.review-uniques-table__flags-head,
.review-uniques-table__flags {
    text-align: center;
    white-space: nowrap;
    width: 1%;
}
.review-uniques-table__flags-head .flag-strip,
.review-uniques-table__flags .flag-strip {
    font-size: 11px;
    /* Neutral gray border around the whole P·A·N·G·D·U unit; the internal
       per-cell dividers supply the gray lines between counts. */
    border: 1px solid var(--color-outline-variant);
    border-radius: 6px;
}
/* Fixed cell height + centering so the strip keeps its shape (and the
   dividers stay full-height) even when a count is blank — otherwise an
   all-empty row collapses the box to a thin line. */
.review-uniques-table .flag-strip__cell {
    display: flex;
    align-items: center;
    justify-content: center;
    height: 22px;
    padding: 0;
}
.review-uniques-table__action {
    width: 80px;
    text-align: right;
    /* Hosts the same .review-status chip used by the main grid's Status
       column. Right-align so the chip sits flush with the column edge. */
}
/* Chip menus portalled out of the uniques modal need to clear the
   modal's z-index (1100) — otherwise they land behind the backdrop.
   The main-grid menus stay at the default 60 so they don't fight any
   other overlay. */
.review-status__menu[data-source="uniques"] {
    z-index: 1200;
}
.review-uniques-modal__foot {
    padding: 12px 22px;
    border-top: 1px solid var(--color-outline-variant);
    display: flex;
    align-items: center;
    justify-content: flex-start;
    gap: 16px;
    font-size: 12px;
    color: var(--color-on-surface-muted);
}
.review-uniques-modal__foot-tip {
    min-width: 0;
}
/* Brand-mode action bar — the AI Brand Analysis + Flip View buttons. Sits
   inline in the head-actions cluster, before the Brand Mode button; shown
   only while Brand Mode is active. */
.review-uniques-brand-actions {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    gap: 8px;
}
.review-uniques-brand-action {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    padding: 7px 14px;
    /* Neutral at rest; the light primary tint only appears in the selected
       (--on) state, matching the Brand Mode / Multi question buttons. */
    background: var(--color-neutral-50);
    border: 1px solid transparent;
    border-radius: 8px;
    font-family: inherit;
    font-size: 12px;
    font-weight: 600;
    color: var(--color-on-surface-variant);
    white-space: nowrap;
    cursor: pointer;
    transition: background-color 120ms, color 120ms;
}
.review-uniques-brand-action:hover {
    background: var(--color-neutral-100);
    color: var(--color-on-surface);
}
/* Selected — light primary tint (not a solid fill). */
.review-uniques-brand-action--on {
    background: var(--color-primary-100);
    color: var(--color-primary-700);
}
.review-uniques-brand-action--on:hover {
    background: color-mix(in srgb, var(--color-primary-600) 15%, var(--color-primary-100));
    color: var(--color-primary-700);
}
.review-uniques-brand-action > svg {
    width: 15px;
    height: 15px;
    stroke-width: 1.5;
}
.review-uniques-brand-action:disabled { opacity: 0.55; cursor: default; }

/* ───── Filter modal ──────────────────────────────────────────────────
   Excel-style column filter. Same backdrop pattern as the uniques
   modal; the card is narrower since we don't need to host a wide
   value column. */
.review-filter-backdrop {
    position: fixed;
    inset: 0;
    background: rgba(15, 23, 42, 0.18);
    z-index: 1100;
    display: flex;
    align-items: center;
    justify-content: center;
}
.review-filter-modal {
    background: var(--color-surface);
    border-radius: 12px;
    box-shadow: 0 24px 60px rgba(15, 23, 42, 0.24);
    width: min(440px, calc(100vw - 48px));
    max-height: calc(100vh - 80px);
    display: flex;
    flex-direction: column;
}
/* Header stacks title + subtitle vertically so neither wraps awkwardly
   in the narrow modal. Close button lives in its own grid cell pinned
   top-right; any unusually long column name truncates with ellipsis. */
.review-filter-modal__head {
    padding: 16px 20px;
    border-bottom: 1px solid var(--color-outline-variant);
    display: grid;
    grid-template-columns: 1fr auto;
    align-items: start;
    column-gap: 12px;
    row-gap: 4px;
}
.review-filter-modal__title {
    font-size: 15px;
    font-weight: 600;
    color: var(--color-on-surface);
    grid-row: 1;
    grid-column: 1;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    min-width: 0;
}
.review-filter-modal__subtitle {
    font-size: 12px;
    color: var(--color-on-surface-muted);
    grid-row: 2;
    grid-column: 1 / -1;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    min-width: 0;
}
.review-filter-modal__close {
    grid-row: 1;
    grid-column: 2;
    background: transparent;
    border: 0;
    width: 28px;
    height: 28px;
    border-radius: 6px;
    cursor: pointer;
    color: var(--color-on-surface-muted);
}
.review-filter-modal__close:hover {
    background: var(--color-neutral-50);
    color: var(--color-on-surface);
}
.review-filter-modal__body {
    padding: 14px 20px;
    display: flex;
    flex-direction: column;
    gap: 10px;
    overflow: hidden;
    min-height: 0;
}
.review-filter-modal__foot {
    padding: 12px 20px;
    border-top: 1px solid var(--color-outline-variant);
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 8px;
}

/* Operator chip ("By value:" + .filter-pill button). The button reuses
   the standard ``.filter-pill`` look (same trigger as the batches
   dropdown) and opens a portalled menu via JS. */
.review-filter-byvalue {
    display: flex;
    align-items: center;
    gap: 10px;
    font-size: 13px;
    color: var(--color-on-surface);
}
.review-filter-byvalue__label { color: var(--color-on-surface-muted); }
button.review-filter-op {
    /* Inherits ``.filter-pill`` (padding, border, hover, chevron sizing).
       We just stretch it to fill the row and right-align the chevron. */
    flex: 1;
    justify-content: space-between;
    min-width: 0;
}
.review-filter-op__label {
    flex: 1;
    text-align: left;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.review-filter-op__chev {
    flex: 0 0 auto;
    width: 14px;
    height: 14px;
    color: var(--color-on-surface-muted);
    stroke-width: 1.5;
    transition: transform 150ms;
}
button.review-filter-op[aria-expanded="true"] .review-filter-op__chev {
    transform: rotate(180deg);
}

/* Portalled operator menu — position: fixed, ``--color-outline-variant``
   border + soft shadow, matches the .review-status__menu look used by
   the per-row status chip in the main grid. */
.review-filter-op-menu {
    list-style: none;
    margin: 0;
    padding: 4px;
    width: max-content;
    min-width: 160px;
    background: var(--color-surface);
    border: 1px solid var(--color-outline-variant);
    border-radius: 8px;
    box-shadow: 0 8px 24px rgba(15, 23, 42, 0.18);
    /* Above the filter modal backdrop (z-index 1100). */
    z-index: 1200;
    font-size: 13px;
    color: var(--color-on-surface);
}
.review-filter-op-opt {
    padding: 8px 12px;
    border-radius: 4px;
    cursor: pointer;
    user-select: none;
}
.review-filter-op-opt:hover { background: var(--color-neutral-50); }
.review-filter-op-opt[aria-selected="true"] {
    background: var(--color-primary-50);
    color: var(--color-primary-700);
    font-weight: 500;
}

/* Single-value text input (Contains / Starts With / …). */
.review-filter-value {
    padding: 8px 10px;
    border: 1px solid var(--color-outline);
    border-radius: 6px;
    font-size: 13px;
    color: var(--color-on-surface);
    background: var(--color-surface);
}
.review-filter-value:focus {
    outline: 0;
    border-color: var(--color-primary);
    box-shadow: 0 0 0 3px var(--color-primary-100);
}
.review-filter-hint {
    font-size: 11px;
    color: var(--color-on-surface-muted);
    line-height: 1.4;
}

/* Search input (for narrowing the checkbox list). */
.review-filter-search {
    padding: 8px 10px;
    border: 1px solid var(--color-outline);
    border-radius: 6px;
    font-size: 13px;
    color: var(--color-on-surface);
    background: var(--color-surface);
}
.review-filter-search:focus {
    outline: 0;
    border-color: var(--color-primary);
    box-shadow: 0 0 0 3px var(--color-primary-100);
}

/* Scrolling list of checkboxes. */
.review-filter-list {
    flex: 1;
    overflow: auto;
    border: 1px solid var(--color-outline-variant);
    border-radius: 6px;
    padding: 4px 0;
    max-height: 320px;
    min-height: 120px;
    background: var(--color-surface);
}
.review-filter-item {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 6px 12px;
    font-size: 13px;
    color: var(--color-on-surface);
    cursor: pointer;
    user-select: none;
}
.review-filter-item:hover { background: var(--color-neutral-25); }
.review-filter-item[hidden] { display: none; }
.review-filter-cb { flex: 0 0 auto; margin: 0; }

/* Header tint for columns with an active filter. Wins over the
   .review-pane--corner / --top neutral-25 background by being on the
   <th> itself with higher selector specificity. A small funnel marker
   is drawn via ::after so the column reads as filtered even on small
   screens or when the colour difference is subtle. */
.review-table thead th.is-filtered {
    background: var(--color-primary-50);
    color: var(--color-primary-700);
}
.review-table thead th.is-filtered::after {
    content: "▼";
    display: inline-block;
    margin-left: 6px;
    font-size: 9px;
    color: var(--color-primary-600);
    transform: translateY(-1px);
}
/* The flag-strip header packs 8 tight cells; the ▼ marker would overflow
   it. The background tint alone signals the active flag filter there. */
.review-table thead th.review-table__col-flags.is-filtered::after { content: none; }

/* Sort indicator — a small caret on the currently-sorted column header so
   the operator can see which column is sorted (and open its menu → Clear
   sort). Cleared from every other header when the sort changes/clears. */
.review-table thead th.is-sorted-asc,
.review-table thead th.is-sorted-desc {
    color: var(--color-primary-700);
}
.review-table thead th.is-sorted-asc::after,
.review-table thead th.is-sorted-desc::after {
    display: inline-block;
    margin-left: 6px;
    font-size: 9px;
    color: var(--color-primary-600);
    transform: translateY(-1px);
}
.review-table thead th.is-sorted-asc::after  { content: "▲"; }
.review-table thead th.is-sorted-desc::after { content: "▼"; }


/* 4-pane freeze layout (Excel-like). The four panes share a 2-col × 2-row grid:
       corner | top
       left   | body
   Only .review-pane--body scrolls natively; the top + left panes are JS-synced from
   the body's scroll position so they appear to scroll along but never reveal their
   own scrollbars across the frozen header/columns. */
.review-grid {
    flex: 1;
    min-height: 0;
    display: grid;
    /* The first track is the sum of the three frozen column widths
       (.col-rownum + .col-2r + .col-status — 36 + 44 + 60 = 140). The
       value is driven by the ``--review-grid-fixed-width`` CSS variable
       so the column-toggle popover can shrink the track when one or
       more fixed columns are hidden (e.g. hiding 2R drops the var to
       96px). The table inside the pane is width:100% + table-layout:
       fixed, so the track and the visible-column sum must stay in
       sync — JS handles that. The 140px fallback covers SSR + the
       brief pre-JS window. */
    grid-template-columns: var(--review-grid-fixed-width, 140px) minmax(0, 1fr);
    grid-template-rows: auto minmax(0, 1fr);
    background: var(--color-surface);
    overflow: hidden;                              /* clip panes inside the grid */
    position: relative;                            /* anchor for .review-loading */
}

/* Loading overlay — covers the whole grid until the deferred-row pass
   finishes (or immediately after first paint for batches that fit in
   the initial slice). The slight white wash lets the painted rows
   peek through during the fade-out so the transition feels natural.
   ``pointer-events`` stays on by default to block clicks on rows that
   haven't fully landed yet; the JS removes them via the --hiding state. */
.review-loading {
    position: absolute;
    inset: 0;
    z-index: 30;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 12px;
    background: rgba(255, 255, 255, 0.9);
    transition: opacity 0.22s ease-out;
}
.review-loading--hiding {
    opacity: 0;
    pointer-events: none;
}
.review-loading__spinner {
    width: 32px;
    height: 32px;
    color: var(--color-primary-600);
    animation: review-loading-spin 0.9s linear infinite;
    fill: none;
    stroke: currentColor;
    stroke-width: 2;
}
.review-loading__label {
    font-size: 13px;
    color: var(--color-on-surface-muted);
    font-variant-numeric: tabular-nums;
}
@keyframes review-loading-spin {
    from { transform: rotate(0deg); }
    to   { transform: rotate(360deg); }
}

/* Pre-navigation overlay — shown on the outgoing page when a click
   targets a slow destination. Sits over everything (z-index above
   the modal layer) so it covers any open popovers / toolbars on the
   way out. Faded in after a short delay; the new page replacing the
   DOM is the natural "hide". */
.page-loading-overlay {
    position: fixed;
    inset: 0;
    z-index: 9999;
    background: rgba(255, 255, 255, 0.9);
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 12px;
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.15s ease-out;
}
.page-loading-overlay--show {
    opacity: 1;
    pointer-events: auto;
}
.page-loading-overlay__spinner {
    width: 32px;
    height: 32px;
    color: var(--color-primary-600);
    animation: review-loading-spin 0.9s linear infinite;
    fill: none;
    stroke: currentColor;
    stroke-width: 2;
}
.page-loading-overlay__label {
    font-size: 13px;
    color: var(--color-on-surface-muted);
}

.review-pane--corner { grid-area: 1 / 1 / 2 / 2; overflow: hidden; }
.review-pane--top    { grid-area: 1 / 2 / 2 / 3; overflow: hidden; }
.review-pane--left   { grid-area: 2 / 1 / 3 / 2; overflow: hidden; }
.review-pane--body   { grid-area: 2 / 2 / 3 / 3; overflow: scroll; }

/* Frozen-pane tint — only the header band (corner + top) carries the neutral-25 wash
   to signal "frozen header". The left columns stay surface-white so they read as a
   continuation of the data area rather than a separate "frozen sidebar". */
.review-pane--corner,
.review-pane--top {
    background: var(--color-neutral-25);
}

/* Freeze-pane separator — 2px slate-300 marks where the frozen columns end and the
   scrolling area begins. Thicker than the inner cell borders so the boundary reads
   as structural, not as another column line. */
.review-pane--corner,
.review-pane--left {
    /* Previously a 2px slate-300 freeze-pane separator. Removed because
       the status cell's confirmation rail (per-row coloured stripe on
       the right edge) already serves as the visual boundary between
       the frozen-left and body panes. */
    border-right: 0;
}
.review-pane--corner,
.review-pane--top {
    /* Top + bottom share the same slate-300 outline so the header band
       reads as a contained strip rather than fading into the toolbar
       above. Matching weights also keep the band visually balanced
       when the grid is stuck to the very top of the viewport. */
    border-top:    1px solid var(--color-outline);
    border-bottom: 1px solid var(--color-outline);
}

/* Inner tables that are JS-translated must be GPU-promoted so the transform runs on
   the compositor thread (synchronously with the body's native scroll) — avoids the
   "1-frame behind" feel that scrollTop assignment causes. */
.review-pane--top  > table,
.review-pane--left > table {
    will-change: transform;
    transform: translate3d(0, 0, 0);   /* baseline — keeps the layer composited */
}

/* Visible scrollbars only on the body pane. Subtle slate styling.
   overscroll-behavior: none kills the elastic rubberband bounce at the top/bottom +
   left/right edges of the body. Without this, the body's content elastically shifts
   past the boundary on macOS/iOS, but the JS-synced frozen panes don't — breaking
   the illusion that all panes are one cohesive surface during overscroll. */
.review-pane--body {
    overscroll-behavior: none;
    scrollbar-width: thin;
    scrollbar-color: var(--color-neutral-400) var(--color-neutral-75);
}
.review-pane--body::-webkit-scrollbar         { width: 12px; height: 12px; }
.review-pane--body::-webkit-scrollbar-track   { background: var(--color-neutral-75); }
.review-pane--body::-webkit-scrollbar-thumb   {
    background: var(--color-neutral-400);
    border-radius: 6px;
    border: 2px solid var(--color-neutral-75);
}
.review-pane--body::-webkit-scrollbar-thumb:hover { background: var(--color-neutral-500); }
.review-pane--body::-webkit-scrollbar-corner  { background: var(--color-neutral-75); }

/* --- Review table (per-pane) ----------------------------------------------- */

.review-table {
    width: 100%;
    border-collapse: collapse;
    table-layout: fixed;          /* honor explicit column widths so the 4 panes align */
}

/* border-box on every review-table cell so an inline ``style.height``
   stamp (used by the row-height sync) means OUTER cell height, not
   content-area height. With the default ``content-box``, an identical
   ``height: 90px`` stamp produces different total cell heights between
   the left pane (~0–6px padding) and the body pane (14px padding) —
   each row would diverge by ~28px, and at 100+ rows that accumulates
   into the drift you'd see when scrolling toward the end. border-box
   pins the stamped value to outer height so both panes' rows resolve
   to exactly the same px regardless of padding. */
.review-table th,
.review-table td {
    box-sizing: border-box;
}

/* Header — matches the canonical .data-table th used across the rest of the app.
   Background is transparent so the frozen pane's neutral-25 tint shows through. */
.review-table thead th {
    /* ``position: relative`` so the per-header hide button can pin to
       the top-right corner via absolute positioning. */
    position: relative;
    padding: 14px 12px;
    text-align: left;
    background: transparent;
    font-size: 13px;
    font-weight: 600;
    letter-spacing: 0.08em;
    color: var(--color-neutral-700);                  /* slate-700 — confident header text */
    text-transform: uppercase;
    vertical-align: middle;
    white-space: nowrap;
}

/* Per-header hide button — small eye-off icon in the top-right corner
   of each <th>. Hidden until hover so it doesn't compete with the
   header label, but always tappable via keyboard focus. Clicking it
   hides the column by flipping its matching popup checkbox. */
.review-col-hide-btn {
    position: absolute;
    top: 4px;
    right: 4px;
    width: 20px;
    height: 20px;
    padding: 0;
    border: 0;
    background: transparent;
    border-radius: 4px;
    cursor: pointer;
    opacity: 0;
    transition: opacity 120ms, background-color 120ms;
    color: var(--color-on-surface-muted);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    /* Keep above the header text + the filtered-column ::after marker
       so the icon is the click target in its corner. */
    z-index: 2;
}
.review-table thead th:hover .review-col-hide-btn,
.review-col-hide-btn:focus-visible {
    opacity: 1;
}
.review-col-hide-btn:hover {
    background: var(--color-neutral-100);
    color: var(--color-on-surface);
}
.review-col-hide-btn > svg {
    width: 12px;
    height: 12px;
    stroke-width: 1.5;
}

/* Per-column resize handle — sits on the right edge of each <th> in the
   top pane. Invisible by default, lights up on hover and during an
   active drag so the user can see exactly what they're grabbing. */
.review-col-resize-handle {
    position: absolute;
    top: 0;
    right: 0;
    bottom: 0;
    width: 6px;
    cursor: col-resize;
    background: transparent;
    transition: background 120ms;
    /* Sit above the column-name text but below the hide button (which
       is in the top-right corner) so neither hijacks the other's clicks. */
    z-index: 1;
    /* No -ms-user-select etc. needed; the global ``is-col-resizing``
       class kills selection on <body> during a drag. */
}
.review-col-resize-handle:hover,
body.is-col-resizing .review-col-resize-handle {
    background: var(--color-primary-300);
}
/* During a drag, freeze the page cursor on col-resize so it doesn't
   flicker as the mouse passes over other elements; also kill text
   selection so dragging doesn't leave a half-selected header. */
body.is-col-resizing {
    cursor: col-resize !important;
    user-select: none;
    -webkit-user-select: none;
}
body.is-col-resizing * {
    cursor: col-resize !important;
}
/* Question-column headers render the Decipher qlabel verbatim (e.g.
   ``Q19``, ``hQ17``) — Decipher's casing is the standard the rest of
   the app uses, so don't let the generic .review-table thead th rule
   uppercase ``hQ17`` into ``HQ17``. */
.review-table thead th.review-table__col-q {
    text-transform: none;
    letter-spacing: 0;
    white-space: normal;
    vertical-align: top;
}
/* The qid stays the prominent line (matches the inherited header
   weight). The qtitle below it is a quiet, normal-case subtitle —
   long question text is clipped to two lines so the header doesn't
   blow the row's height out; the full text lives in the title=
   attribute as a hover tooltip. */
.review-table__col-q-label {
    font-weight: 600;
    font-size: 13px;
    color: var(--color-neutral-700);
    /* Span the column header's full width and split: qid on the left,
       the stat chip floats to the right edge so the chips on adjacent
       columns line up vertically and the eye can sweep them quickly. */
    display: flex;
    align-items: center;
    gap: 8px;
}
.review-table__col-q-label > .stat-chip {
    margin-left: auto;
}

/* ---- Response-coverage chip --------------------------------------
   Small inline pill that reads "unique | total" for the column it sits
   beside (or "Responses unique | total" at the toolbar level). Two-tone
   typography pulls the chip away from the surrounding headings —
   ``unique`` lands in the project primary (the chip's job: show how
   varied the responses are) and ``total`` sits in a muted neutral so
   the eye treats it as the denominator. Tabular numerals so columns
   line up regardless of digit width; the pipe is a literal character
   (no ARIA), kept faint via colour so it reads as a divider not text. */
.stat-chip {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    height: 18px;
    padding: 0 8px;
    background: color-mix(in srgb, var(--color-primary-500) 6%, var(--color-surface));
    border: 1px solid color-mix(in srgb, var(--color-primary-500) 12%, transparent);
    border-radius: 999px;
    font-size: 10.5px;
    font-weight: 400;
    line-height: 1;
    white-space: nowrap;
}
.stat-chip__num {
    font-variant-numeric: tabular-nums;
    font-weight: 500;
}
.stat-chip__num--unique,
.stat-chip__num--total {
    color: var(--color-on-surface-muted);
}
.stat-chip__sep {
    color: color-mix(in srgb, var(--color-primary-500) 25%, transparent);
    font-weight: 400;
}
.stat-chip__label {
    text-transform: uppercase;
    letter-spacing: 0.06em;
    font-size: 9.5px;
    font-weight: 500;
    color: var(--color-on-surface-muted);
    margin-right: 2px;
}
/* Toolbar variant — sits in the actions row beside Batches/Passes/etc.
   Slightly taller to match the surrounding controls' line-height. */
.stat-chip--toolbar {
    height: 24px;
    padding: 0 10px;
    font-size: 11px;
    gap: 6px;
}
.stat-chip--toolbar .stat-chip__num { font-size: 11.5px; }
/* Read-only reference columns in the body pane (date / uuid /
   transaction_id). Need an explicit ``width`` (not just min/max) — under
   ``table-layout: fixed`` the q-cols' explicit 222px exhausts the table
   width first, and any auto-width column gets squeezed to 0px. Min-width
   is honored on the cell box but not on the column track. */
/* Selector specificity (0,2,1) so it beats ``.review-row > td``
   (0,1,1) on font-size / padding — without this the row rule
   would re-apply 13px and 14px padding. */
.review-table th.review-table__col-ref,
.review-table td.review-table__col-ref {
    width: 160px;
    min-width: 160px;
    max-width: 160px;
    padding: 6px 10px;
    font-family: var(--font-mono);
    font-size: 11px;
    color: var(--color-on-surface);
    white-space: normal;
    overflow-wrap: anywhere;
    word-break: break-all;
    vertical-align: middle;
}
.review-table thead th.review-table__col-ref {
    font-family: inherit;
    font-weight: 600;
    font-size: 11px;
    color: var(--color-neutral-700);
    text-transform: uppercase;
    letter-spacing: 0.04em;
}

/* ----- Column show/hide popover ---------------------------------- */
/* Anchor sits inline in the toolbar; popover positions absolutely off
   the anchor and floats over the page beneath the toolbar. Closed by
   the JS via [hidden] when no preferences-edit is in progress. */
.col-toggle-anchor {
    position: relative;
    display: inline-block;
}
/* Popover is closed by default — JS adds .col-toggle-pop--open to show.
   We don't use the HTML ``hidden`` attribute because the open state needs
   ``display: flex`` for the column layout, which would override
   ``hidden``'s default ``display: none`` and leave the popover always
   visible. The class-based approach keeps the cascade unambiguous.
   ``overflow: visible`` so the dropdown arrow (::before / ::after pseudo
   elements above the top edge) doesn't get clipped. */
.col-toggle-pop {
    display: none;
    position: absolute;
    top: calc(100% + 8px);
    left: 0;
    z-index: 50;
    width: 320px;
    max-height: 70vh;
    overflow: visible;
    background: var(--color-surface);
    border: 1px solid var(--color-outline-variant);
    border-radius: 10px;
    box-shadow: 0 8px 24px rgba(15, 23, 42, 0.12);
    flex-direction: column;
}
.col-toggle-pop--open {
    display: flex;
}

/* Triangular pointer that visually connects the popover to its trigger
   button. Classic two-pseudo-element technique:
     ::before — outline triangle (border colour), 8px tall, 16px base.
     ::after  — fill triangle (surface colour), 1px shorter so the
                outline shows as a hairline along all three edges.
   Both sit above the popover's top edge (``bottom: 100%``) with their
   base touching it, so the popover's top border extends seamlessly into
   the arrow's base.                                                       */
.col-toggle-pop::before,
.col-toggle-pop::after {
    content: "";
    position: absolute;
    width: 0;
    height: 0;
    border-style: solid;
    border-color: transparent;
}
.col-toggle-pop::before {
    left: 12px;
    bottom: 100%;
    border-width: 0 8px 8px 8px;
    border-bottom-color: var(--color-outline-variant);
}
.col-toggle-pop::after {
    left: 13px;
    bottom: 100%;
    border-width: 0 7px 7px 7px;
    border-bottom-color: var(--color-surface);
}
.col-toggle-pop__head {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 10px 12px;
    border-bottom: 1px solid var(--color-outline-variant);
}
/* Scrollable body — keeps the head + Reset button pinned at the top
   while the groups stack can grow indefinitely. ``min-height: 0`` lets
   the flex child shrink so ``overflow-y: auto`` actually engages
   instead of pushing the popover taller than its max-height. */
.col-toggle-pop__body {
    flex: 1 1 auto;
    overflow-y: auto;
    min-height: 0;
}
.col-toggle-pop__title {
    font-size: 13px;
    font-weight: 600;
    color: var(--color-on-surface);
}
.col-toggle-pop__reset {
    border: none;
    background: transparent;
    color: var(--color-primary);
    font: inherit;
    font-size: 12px;
    cursor: pointer;
    padding: 0;
}
.col-toggle-pop__reset:hover { text-decoration: underline; }

.col-toggle-pop__group {
    padding: 8px 12px;
    border-bottom: 1px solid var(--color-outline-variant);
}
.col-toggle-pop__group:last-child { border-bottom: none; }
.col-toggle-pop__group-label {
    font-size: 10.5px;
    font-weight: 500;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    color: var(--color-on-surface-muted);
    margin-bottom: 6px;
}
/* Variant that lays the label + an inline action (e.g. "Hide empty") on
   one row. Scoped so the Reference label's "(view only)" hint keeps
   sitting inline right after the word. */
.col-toggle-pop__group-label--action {
    display: flex;
    align-items: center;
    gap: 8px;
}
/* Small inline action inside a group label (Questions → "Hide empty").
   A quiet ghost pill — primary text, no border, a soft primary wash on
   hover — pinned to the right edge of the label row. */
.col-toggle-pop__mini {
    margin-left: auto;          /* push to the far right of the flex row */
    border: none;
    background: transparent;
    color: var(--color-primary);
    font: inherit;
    font-size: 11px;
    font-weight: 500;
    letter-spacing: normal;
    text-transform: none;
    line-height: 1;
    padding: 4px 9px;
    border-radius: 999px;
    cursor: pointer;
    transition: background-color 120ms, color 120ms;
}
.col-toggle-pop__mini:hover {
    background: var(--color-primary-50);
    color: var(--color-primary-700);
}
.col-toggle-pop__hint {
    text-transform: none;
    letter-spacing: 0;
    color: var(--color-on-surface-muted);
    font-weight: 400;
}
.col-toggle-row {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 5px 0;
    cursor: pointer;
}
.col-toggle-row__cb { margin: 0; flex: 0 0 auto; }
.col-toggle-row__label {
    flex: 1 1 auto;
    min-width: 0;
    font-size: 12.5px;
    color: var(--color-on-surface);
    display: flex;
    align-items: center;
    gap: 6px;
    overflow: hidden;
}
.col-toggle-row__sub {
    color: var(--color-on-surface-muted);
    font-size: 11.5px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    min-width: 0;
}
/* Response-coverage chip inside the column-toggle row. Keeps the
   single-number ".stat-chip" pill from being squashed by the flex
   layout when the question title beside it is long — the title gets
   ellipsis'd instead. */
.col-toggle-row__label > .stat-chip { flex: 0 0 auto; }
.review-table__col-q-title {
    margin-top: 2px;
    font-weight: 400;
    font-size: 12px;
    letter-spacing: 0;
    color: var(--color-on-surface-muted);
    line-height: 1.35;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}
/* Frozen-column widths (Row# / Source / 2R / Status) — used by the
   corner + left panes. Equal min + max + width keeps them lock-stepped
   across the two panes' tables. */
.review-table__col-rownum { width: 36px;  min-width: 36px;  max-width: 36px;  text-align: center; }
/* Specificity (0,2,1) beats .review-row > td (0,1,1) on font-size /
   padding — without this the row rule re-applies 13px and 14px padding
   and the small mono digits get drowned out. */
.review-table th.review-table__col-rownum,
.review-table td.review-table__col-rownum {
    padding: 6px 4px;
    font-family: var(--font-mono);
    font-size: 11px;
    color: var(--color-on-surface-muted);
}
.review-table__col-source { width: 40px;  min-width: 40px;  max-width: 40px;  text-align: center; }
/* Beats .review-row > td { padding: 14px } via higher specificity (0,2,1 vs 0,1,1) */
.review-table th.review-table__col-source,
.review-table td.review-table__col-source {
    padding: 0 0 0 5px;
}
.review-table__col-2r     { width: 44px;  min-width: 44px;  max-width: 44px;  text-align: center; }
/* Beats .review-row > td { padding: 14px } via higher specificity (0,2,1 vs 0,1,1).
   ``position: relative`` so the sync icon (now sharing the cell with
   the 2R flag button) can pin to the top-left corner. */
.review-table th.review-table__col-2r,
.review-table td.review-table__col-2r {
    padding: 0;
    position: relative;
}
/* Right-align the flag button in body cells so it sits closer to the
   Status chip in the neighbouring column. The header (which carries
   the "2R" label) keeps the centred default from above. */
.review-table tbody td.review-table__col-2r {
    text-align: right;
    padding-right: 2px;
}
/* The Status column hosts the 2R flag toggle + the status chip on one
   line. (Sync indicator was removed and is now expressed via the cell
   tint, not an inline icon.) 80px just fits the flag + chip cluster
   without leaving dead space on the left. */
.review-table__col-status { width: 80px;  min-width: 80px;  max-width: 80px;  text-align: center; }
.review-table th.review-table__col-status,
.review-table td.review-table__col-status {
    padding-left: 8px;
    padding-right: 15px;
}
/* Dedicated rail column — sits at the far right of the frozen pane in
   both the corner pane (header) and the left pane (body). Hosts the
   per-row provenance tick (.review-status__tick) plus the neutral
   header strip (.review-status__head-rail). Always present so it
   doesn't depend on which Stats columns are toggled on — the rail
   sits at the cell's right edge, and that cell is always the
   rightmost in the row.

   Width = 16 px (same as the tick's footprint). ``position: relative``
   establishes the containing block for the rail elements inside — a
   reliable replacement for the row-level ``position: relative``
   approach that some browsers don't honor for table rows. */
.review-table__col-rail {
    width: 16px;
    min-width: 16px;
    max-width: 16px;
    padding: 0 !important;
    border: 0;
    position: relative;
}
/* Status header reads smaller than the rest — it's a 6-character
   label in an 80px column, so the canonical 13px header size
   forced a too-tight tracking that visually outweighed the chip
   below it. Slimmer letter-spacing for the same reason. Specificity
   (0,2,1) beats the generic ``.review-table thead th`` (0,1,1). */
.review-table thead th.review-table__col-status {
    font-size: 11px;
    letter-spacing: 0.04em;
    /* No column divider between Status and the AI stat column in the
       header — the AI label sits on its own with no visual line
       crowding it. The body still uses the standard cell boundary. */
    border-right: 0;
}
.review-table thead th.review-table__col-stat--ai {
    border-left: 0;
}
/* Inline action row inside the body cell — sync · flag · chip on one
   line, evenly spaced with the chip pushed to the right edge of the
   action group. */
.review-row__actions {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 6px;
    height: 100%;
}

/* Question column widths — used by top + body panes. Fixed at 222px
   so more columns fit on screen without horizontal scrolling; long
   answers wrap into the cell. Reduced from 342px (~35% narrower) per
   operator feedback that the wider column was wasting screen real
   estate for typical short open-end answers. */
.review-table__col-q {
    width: 222px;
    min-width: 222px;
    max-width: 222px;
    overflow-wrap: break-word;
    /* Anchor for .response-flags so chips pin to the td's top-right corner
       regardless of where .review-cell ends up vertically (td vertical-align
       defaults to middle and would otherwise drop the chips mid-row). */
    position: relative;
}

/* Body row — hairline row separators at slate-150 (custom 100↔200 midpoint).
   100 was washing out against the row background; 150 still reads as a
   quiet grid line without competing with text. */
.review-row > td {
    padding: 14px;
    font-size: 13px;
    color: var(--color-on-surface);
    border-bottom: 1px solid var(--color-neutral-150);
    vertical-align: middle;
}

/* Last row drops its bottom border so it doesn't double up with the pane's bottom edge */
.review-table tbody tr:last-child > td {
    border-bottom: none;
}

/* Vertical cell separators — slate-150 to match the row separators (the
   custom 100↔200 midpoint stop). Last column drops the right border so
   it doesn't double up with the pane's edge. Source column also drops
   its right border to merge visually with the 2R column. */
.review-table th,
.review-table td {
    border-right: 1px solid var(--color-neutral-150);
}
.review-table th:last-child,
.review-table td:last-child,
.review-table th.review-table__col-source,
.review-table td.review-table__col-source,
.review-table th.review-table__col-2r,
.review-table td.review-table__col-2r,
/* Stats group reads as a single continuous strip (Status flowing into
   AI flowing into the merged Flags chips) — no column dividers
   between them. The rail at the far right draws the only hard line. */
.review-table th.review-table__col-status,
.review-table td.review-table__col-status,
.review-table th.review-table__col-stat,
.review-table td.review-table__col-stat,
.review-table th.review-table__col-flags,
.review-table td.review-table__col-flags {
    border-right: none;
}

/* Row tone — applied to both left and body panes so the Status cell tints with the
   row (matching the Q-column cells on the right side). Source and 2R cells get an
   explicit white override below so they stay neutral regardless of tone. */
/* Accept and "no decision yet" rows both ride on plain surface — the
   confirmation-rail tick on the right edge of the status cell already
   carries the "set by AI / QA / client / nobody" signal, so an
   additional neutral-tinted background just added noise without
   adding information. Reject keeps its red tint because the row's
   *outcome* differs from accept/unreviewed, not just the provenance. */
.review-row--success > td { background: var(--color-surface);     }
.review-row--error   > td { background: var(--color-error-50);    }
.review-row--neutral > td { background: var(--color-surface);     }

/* A question cell carrying any validation flag is rendered with red text —
   the row's background colour stays whatever its status dictates, only the
   answer text in the flagged cell shifts to error tone. :has() keeps this
   in sync with the keyboard-toggled flags without any JS. */
.review-table__col-q:has(.response-flag:not(.response-flag--brand)) {
    color: var(--color-error-600);
    font-weight: 500;
}
/* Soft top-edge gradient on flagged cells — a quiet red wash at the
   top of the cell fading to transparent. Reads as a tab-style accent
   that makes flagged cells stand out at a glance when scanning a long
   column, without changing the body background (which would compete
   with the row's status tint). The pseudo-element sits at the
   absolute top of the td, above the inner ``.review-cell`` but
   beneath the floating flag chip (which is z-indexed higher via its
   own absolute positioning). */
.review-table__col-q:has(.response-flag:not(.response-flag--brand))::before {
    content: "";
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    height: 28px;
    background: linear-gradient(
        to bottom,
        color-mix(in srgb, var(--color-error-500) 18%, transparent) 0%,
        color-mix(in srgb, var(--color-error-500) 8%, transparent) 45%,
        transparent 100%
    );
    pointer-events: none;
    z-index: 0;
}
/* Lift the answer text above the gradient ::before so the wash sits
   behind the type rather than washing the first line out. Scoped to
   ``.review-cell__text`` (the inner span only) — NOT to ``.review-cell``
   itself, because the floating flag chip (.response-flags) is
   absolutely positioned against the td and would re-anchor to
   .review-cell if we made it the nearest positioned ancestor. The
   chip is already at z-index 1, so text + chip end up on the same
   layer above the decorative gradient. */
.review-table__col-q:has(.response-flag:not(.response-flag--brand)) .review-cell__text {
    position: relative;
    z-index: 1;
}

/* Source, 2R, and Status body cells stay white regardless of the row
   tone, so the status pill sits on a clean background instead of fighting
   with the row's tint. Specificity (0,2,2) wins against .review-row--success > td (0,1,2). */
.review-table tbody td.review-table__col-source,
.review-table tbody td.review-table__col-2r,
.review-table tbody td.review-table__col-status {
    background: var(--color-surface);
}
/* Sync-state background on the merged status cell — two-state model:
   tinted when the row's review outcome doesn't match what's on the
   downstream targets (Decipher / Marketplace) and the operator still
   owes a Reconcile push; plain white when the row is either un-
   reviewed (no decision yet) or fully in sync. The borders pick up a
   slightly darker tint so the cell reads as one self-contained band
   instead of a coloured fill inside neutral gridlines.

   The two state classes both layer over the white default rule above
   (same specificity 0,2,2; later wins by source order). The previous
   three-class scheme (``--reconciled`` purple, ``--error`` amber,
   ``--unreconciled`` white) is gone — the per-row state is now
   derived live from ``status`` vs ``s_status`` / ``m_status``, not
   from a separate persisted summary. */
.review-table tbody td.review-table__col-status--needs_recon {
    background: var(--color-warning-75);
    border-right: 0;
    border-bottom-color: var(--color-warning-150);
}
.review-table tbody td.review-table__col-status--ok {
    background: var(--color-surface);
    border-right: 0;
    border-bottom-color: var(--color-neutral-150);
}
/* Row# cells share the frozen-header tint (neutral-25) so the gutter
   reads as a margin rather than another data column. Specificity
   (0,2,2) wins against the .review-row--<tone> > td row rules. */
.review-table tbody td.review-table__col-rownum {
    background: var(--color-neutral-25);
}

/* ----- Stats columns (frozen pane) -------------------------------------
   AI is its own column. T + P/A/N/G/D/U live INSIDE a single shared
   ``.review-table__col-flags`` cell so the body reads as a row of
   amber chips with no column dividers running between them — matches
   the design mockup. Widths are pinned so the JS-driven frozen-pane
   width recompute (see FIXED_COL_WIDTHS in reviewer.j2) lines up with
   what these declare. */
.review-table__col-stat {
    text-align: center;
    padding: 4px 6px;
    font-size: 12px;
    font-variant-numeric: tabular-nums;
    /* No right border — the AI column flows directly into the merged
       flags column with no divider between them. The frozen pane's
       hard edge is drawn by the rail column at the far right. */
    border-right: 0;
}
.review-table__col-stat--ai     { width: 56px; min-width: 56px; max-width: 56px; }
/* The Flags column hosts up to 7 chips inline. Width is the sum of
   stats:total (1 chip) + stats:detail (6 chips) widths declared in
   FIXED_COL_WIDTHS; the chips inside use ``display: inline-block``
   with their own padding so the parent doesn't need to pin a fixed
   width — natural content sizing handles toggle-driven visibility
   without any extra JS. */
.review-table__col-flags {
    /* No horizontal padding — the ``.flag-strip`` child owns the full
       chip strip, and dropping horizontal padding here means each
       toggle column (stats:total / stats:detail) contributes a clean
       additive width with no off-by-padding rounding errors. */
    padding: 4px 0;
    text-align: center;
    /* No right border — the rail column next door already gives the
       frozen pane its hard right edge, and the line here read as a
       redundant divider between the flag chips and the rail. */
    border-right: 0;
    white-space: nowrap;
    background: var(--color-surface);
}
/* The body cell inherits ``.review-row > td { padding: 14px }`` at a
   higher specificity than ``.review-table__col-flags`` alone, which
   would offset every body chip 10 px right of its header letter and
   break the column alignment. Forcing both header and body cells to
   the same explicit padding here keeps the AI/T/P/A/N/G/D/U chips
   centred at identical x-positions. Specificity (0,2,1) beats the
   ``.review-row > td`` rule. */
.review-table thead th.review-table__col-flags,
.review-table tbody td.review-table__col-flags,
.review-row > td.review-table__col-flags {
    padding: 4px 0;
}
/* AI header padding matches the body so the AI label centres exactly
   above its score value. */
.review-table thead th.review-table__col-stat {
    padding: 4px 6px;
    background: var(--color-surface);
}
/* No explicit background on the body AI cell — let the row-tone rule
   (.review-row--success/neutral/error > td) paint it. Hard-coding a
   bg here would win on specificity (0,2,2 vs 0,1,1) and clobber the
   row tone, so the AI cell would render as a white rectangle in the
   middle of a tinted rejected row. */
/* AI header pill — primary-blue tint flags it as the headline stat.
   Uses inline-block with fixed dims so it aligns with the score
   below it. The flag chips next door now live inside ``.flag-strip``
   and don't share this class. */
.review-table thead th.review-table__col-stat--ai .stat-head-pill {
    display: inline-block;
    width: 26px;
    padding: 1px 0;
    font-family: var(--font-mono);
    font-size: 11px;
    font-weight: 500;
    line-height: 1.45;
    text-align: center;
    background: var(--color-primary-75);
    color: var(--color-primary-700);
    border-radius: 4px;
    box-sizing: border-box;
}

/* AI confidence pill — amber under the 70 % threshold, green at-or-
   above. Sits inside the cell as a compact rounded chip so it reads
   as a metric value rather than plain text. */
.ai-score-pill {
    display: inline-block;
    padding: 1px 6px;
    border-radius: 999px;
    font-weight: 600;
    font-size: 11px;
    line-height: 1.45;
}
.ai-score-pill--good {
    background: var(--color-success-50,  #ecfdf5);
    color:      var(--color-success-700, #047857);
}
.ai-score-pill--weak {
    background: var(--color-warning-50,  #fffbeb);
    color:      var(--color-warning-700, #b45309);
}
/* High AI *rejection* score → red. The score is a likelihood-of-rejection,
   so 100% is bad: green (low) → amber (mid) → red (high). */
.ai-score-pill--bad {
    background: var(--color-error-50,  #fef2f2);
    color:      var(--color-error-700, #b91c1c);
}

/* Flag count strip — a single CSS Flex container that hosts the T
   aggregate + the six P/A/N/G/D/U letter cells as compartments of
   one continuous chip. The shell carries a neutral background and
   rounded corners; internal dividers come from a ``border-left`` on
   every non-first cell so adjacent compartments share a single hair-
   line. Header cells get per-flag tinted fills (matching the per-cell
   flag chip palette used elsewhere); body cells keep the shell's
   neutral fill and colour the digit only. */
.flag-strip {
    display: inline-flex;
    vertical-align: middle;
    /* Transparent shell — head strip cells each paint their own per-
       flag fill, so the shell colour was only visible behind the body
       strip's transparent cells. Making it transparent lets the row
       tone (white for accept/neutral, error-50 for reject) show
       through, so the flag column stops "punching through" tinted
       rows with a hard white rectangle. The internal dividers
       (border-left on each non-first cell) still give the strip its
       compartmented structure. */
    background: transparent;
    border-radius: 4px;
    overflow: hidden;
    font-family: var(--font-mono);
    font-weight: 500;
    font-size: 11px;
    line-height: 1.45;
    font-variant-numeric: tabular-nums;
}
.flag-strip__cell {
    flex: 0 0 22px;
    padding: 1px 0;
    text-align: center;
    color: var(--color-on-surface);
    box-sizing: border-box;
}
/* Internal dividers between compartments — ``+`` selector means the
   first DOM cell never gets one. When a toggle hides the T cell via
   ``display: none`` the next visible cell (P) inherits a left edge
   right at the strip's start; that's a single-pixel artefact we
   accept in exchange for a width formula that sums cleanly. */
.flag-strip__cell + .flag-strip__cell {
    border-left: 1px solid var(--color-outline-variant);
}
/* Totals group — TR (total responses) + TF (total flagged). 26px each so
   two-digit values fit; bold surface-on text so they read as aggregates
   distinct from the colour-coded per-flag counts. */
.flag-strip__cell--total,
.flag-strip__cell--tr,
.flag-strip__cell--tf {
    flex: 0 0 26px;
    font-weight: 700;
}
/* Group split — a 6px gap before the first detail cell (P) visually
   separates the TR/TF totals from the P·A·N·G·D·U per-flag counts. The
   gap replaces P's divider so it doesn't read as a double separator. */
.flag-strip__cell--group {
    margin-left: 6px;
    border-left: 0 !important;
}
/* Header cells — per-flag tinted background matches the response
   flag chip palette so the column letter reads as the same colour
   as the flag it counts. Background sits one step deeper than the
   matching body chip; text sits one step lighter so the letter
   reads as a soft inverse against its tint rather than competing
   with the body digits below. */
.flag-strip--head .flag-strip__cell--total,
.flag-strip--head .flag-strip__cell--tr,
.flag-strip--head .flag-strip__cell--tf           { background: var(--color-neutral-200); }
.flag-strip--head .flag-strip__cell--profanity    { background: var(--color-error-300);    color: var(--color-error-600); }
.flag-strip--head .flag-strip__cell--ai_generated { background: var(--color-info-200);     color: var(--color-info-600); }
.flag-strip--head .flag-strip__cell--non_context  { background: var(--color-neutral-400);  color: var(--color-neutral-600); }
.flag-strip--head .flag-strip__cell--gibberish    { background: var(--color-warning-300);  color: var(--color-warning-600); }
.flag-strip--head .flag-strip__cell--duplicate    { background: var(--color-tertiary-300); color: var(--color-tertiary-600); }
.flag-strip--head .flag-strip__cell--unengaged    { background: var(--color-accent-300);   color: var(--color-accent-600); }
/* Body cells — transparent fill, coloured digit only. Regular weight
   throughout (including T) so the digits read as light tabular numbers
   against the neutral shell. The bolder T treatment is kept only on the
   header letter, where it works as a column-label emphasis. */
.flag-strip--body .flag-strip__cell { font-weight: 400; }
.flag-strip--body .flag-strip__cell--profanity    { color: var(--color-error-600); }
.flag-strip--body .flag-strip__cell--ai_generated { color: var(--color-info-600); }
.flag-strip--body .flag-strip__cell--non_context  { color: var(--color-neutral-700); }
.flag-strip--body .flag-strip__cell--gibberish    { color: var(--color-warning-700); }
.flag-strip--body .flag-strip__cell--duplicate    { color: var(--color-tertiary-600); }
.flag-strip--body .flag-strip__cell--unengaged    { color: var(--color-accent-700); }

/* Centered header alignment for the first 3 columns. Specificity (0,2,2) beats the
   .review-table thead th (0,1,2) left-align rule. */
.review-table thead th.review-table__col-source,
.review-table thead th.review-table__col-2r,
.review-table thead th.review-table__col-status {
    text-align: center;
}

/* Source column thead icon — match the rest of the heading text in darkness + weight.
   Without this scoped rule, the icon would inherit .review-table__col-source > svg's
   light muted styling (which is right for tbody source indicators but wrong for the
   thead column label). */
.review-table thead th.review-table__col-source > svg {
    width: 12px;
    height: 12px;
    stroke-width: 2;
    color: var(--color-neutral-700);
}

/* Reconciliation status indicator — first item in the combined status
   cell's action row (sync · flag · chip). 8×8 inline marker; the
   modifier class drives the shape.
     --unreconciled  neutral filled dot
     --error         red "!" exclamation mark
     --reconciled    green filled dot */
.review-row__source-icon {
    flex: 0 0 8px;
    width: 8px;
    height: 8px;
    stroke-width: 3;
}
/* Unreconciled — solid neutral dot. Same shape as the reconciled
   variant, just in a quiet grey to convey "not acted on yet". */
.review-row__source-icon--unreconciled {
    display: block;
    background: var(--color-neutral-300);
    border-radius: 50%;
}
/* Reconciled — solid green dot. ``display: block`` lets the span
   render as a flat coloured circle without any glyph inside. */
.review-row__source-icon--reconciled {
    display: block;
    background: var(--color-success-500);
    border-radius: 50%;
}
/* Error — "!" character centred in the 8×8 box, in error red. The
   slightly larger font-size + heavy weight makes the bang readable
   even at the small footprint. Mono so the glyph doesn't shift width
   with the surrounding font. */
.review-row__source-icon--error {
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--color-error-500);
    font-family: var(--font-mono);
    font-size: 11px;
    font-weight: 900;
    line-height: 1;
}

/* 2R flag toggle — outline at rest, solid amber when flagged */
.flag-toggle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 26px;
    height: 26px;
    padding: 0;
    background: transparent;
    border: 0;
    border-radius: var(--radius-control);
    color: var(--color-on-surface-muted);
    cursor: pointer;
    transition: background-color 120ms, color 120ms;
}
.flag-toggle:hover { background: var(--color-neutral-100); }
.flag-toggle > svg {
    width: 14px;
    height: 14px;
    stroke-width: 1.5;
}
.flag-toggle--on {
    background: var(--color-warning-75);
    color: var(--color-warning-600);
}
.flag-toggle--on > svg { fill: var(--color-warning-300); }
.flag-toggle--on:hover { background: var(--color-warning-100); }

/* Overall-status dropdown — modern data-app pill. Rectangular (6px radius, not full
   round), borderless tone-50 fill, 6px leading dot, lowered chevron opacity. Hover
   lifts the tint one step. Pill fills the cell width for visual uniformity across
   all rows; content cluster centers within so short labels don't leave a one-sided
   gap. */
/* Review status — a narrow chip showing just the first letter
   (A / R / —) of the row's decision so the column can be slim, with a
   click-to-open menu that shows the full ``Accept`` / ``Reject`` /
   ``Select`` labels. The menu is rendered as ``position: fixed`` from
   JS so the left pane's ``overflow: hidden`` doesn't clip it. */
.review-status {
    display: inline-block;
    position: relative;       /* chip lives inside; menu uses JS-driven fixed pos */
    line-height: 1;
}

/* Closed-state chip — tone-coloured letter-only badge. Click opens the
   dropdown menu; no caret since the chip itself is the affordance.
   Keeps the .review-status--<tone> driver that the JS commit() helper
   swaps on every change. */
.review-status__chip {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 26px;
    height: 21px;
    padding: 0;
    border: 0;
    border-radius: 3px;
    background: var(--color-neutral-100);
    color: var(--color-on-surface-muted);
    font-family: var(--font-mono);
    font-size: 10px;
    font-weight: 700;
    letter-spacing: 0;
    line-height: 1;
    cursor: pointer;
    transition: background-color 120ms, color 120ms, box-shadow 120ms;
}
.review-status__chip:hover     { box-shadow: 0 0 0 1px var(--color-outline-variant); }
.review-status__chip:focus-visible {
    outline: 0;
    box-shadow: 0 0 0 3px var(--color-primary-100);
}
/* Client rejection is permanent — the chip is a static label, not a control:
   no pointer, no hover affordance, no dropdown caret. */
.review-status__chip--locked        { cursor: default; }
.review-status__chip--locked:hover  { box-shadow: none; }
.review-status__letter { display: block; }

/* Status-provenance rail — a thin vertical band pinned to the right
   edge of the status CELL (the td), spanning its full height. Tone
   reflects ``status_by``:
     qa     → sky    (human reviewer)
     ai     → cyan   (AI engine confidently set the status)
     client → red    (downstream client rejection)
     none   → neutral grey (tentative — no provenance yet)
   Positioning context is now the ROW (``tr.review-row`` in the left
   pane, ``thead tr`` in the corner pane) — so the strip lands at the
   right edge of the frozen pane regardless of which Stats columns
   are toggled on between Status and the pane's right edge. ``.review
   -status`` overrides the earlier ``position: relative`` rule so the
   strip escapes the chip wrapper. */
.review-status { position: static; display: inline-block; }
.review-status__tick {
    position: absolute;
    /* Extend ~1px past the top + bottom of the cell so the rail covers
       the 1px row-separator border that sits between consecutive cells
       — without it, the rails of adjacent rows look like a dashed line
       with 1px gaps. z-index keeps the rail above the border paint. */
    top: -1px;
    right: 0;
    bottom: -1px;
    /* Another 20% trim from the previous 20 / 6px stripe so the
       rail reads as a slimmer accent. ``width`` (the element's own
       footprint) and the ``border-right`` (the visible coloured
       stripe) move together. */
    width: 16px;
    border-right: 5px solid transparent;
    /* ``border-box`` so the 4px border lives inside the declared width
       and the rail doesn't overflow the cell. */
    box-sizing: border-box;
    pointer-events: none;
    /* Above the cell tone wash, the row border, and the chip's hover ring. */
    z-index: 2;
}
/* The check SVG inside is a holdover from the tick-mark variant; the
   strip carries all the meaning itself, so the icon is hidden. The
   element stays in the DOM so the JS commit() helper can keep using
   the same className swap to switch between qa / ai / client / none. */
.review-status__tick > svg { display: none; }
/* No gradient on the variants — just the solid right border in the
   variant colour. The rail reads as a clean coloured stripe at the
   cell's right edge. */
/* Four provenance rails, picked from four different colour families so
   any two are unambiguously distinct at a glance. QA gets the green
   (human reviewer = primary positive action); AI gets sky-blue (the
   automated/computational tone). The lighter green sits more quietly
   on white cells so a long column of QA-confirmed rows doesn't read
   as a hard stripe. */
.review-status__tick--qa {
    border-right-color: var(--color-success-400);
}
.review-status__tick--ai {
    border-right-color: var(--tw-sky-500);
}
/* Client-rejection rail — same red family as the rejection chrome
   elsewhere on the page. Surfaces "downstream client said no" at a
   glance. */
.review-status__tick--client {
    border-right-color: var(--color-error-600);
}
/* PM override — purple. Reads as "elevated by a project manager"
   without competing with the QA sky-blue. */
.review-status__tick--pm {
    border-right-color: var(--color-tertiary-600, #7c3aed);
}
/* Unconfirmed (tentative) — neutral rail so the column always reads
   as "has a provenance slot", with the strong colours reserved for
   actual qa / AI / client confirmation. */
.review-status__tick--none {
    border-right-color: var(--color-neutral-200);
}

/* When the status cell is tinted "needs reconcile" (amber wash), the
   provenance rail just needs to sit cleanly above the warm background.
   Same 5px stripe as on white cells — no widened border, no inset
   white shadow. The z-index bump is the only change, so the rail
   reliably paints over the wash without the heavier chrome that read
   as "competing" with the cell's tone. */
.review-table__col-status--needs_recon .review-status__tick {
    z-index: 5;
}

/* Header provenance rail — a real <span> child of the Status th
   (NOT a ``::after``) so the cross-browser table-cell quirk that
   keeps a pseudo-element from honouring its tr ancestor's containing
   block doesn't strand the rail at the cell's right edge. With the
   thead ``<tr>`` carrying position:relative, ``right: 0`` lands at
   the right edge of the corner pane, mirroring the body tick.
   ``bottom: -1px`` covers the 1px header ↔ body border so the rail
   joins seamlessly with the first row's. */
.review-status__head-rail {
    position: absolute;
    top: 0;
    right: 0;
    bottom: -1px;
    width: 16px;
    border-right: 5px solid var(--color-neutral-200);
    box-sizing: border-box;
    z-index: 2;
    pointer-events: none;
}

/* Tone variants — applied on the wrapper so the JS just swaps one
   modifier class; the chip child reads them via the descendant
   selector. */
.review-status--success .review-status__chip {
    background-color: var(--color-success-400);
    color: #fff;
}
.review-status--success .review-status__chip:hover {
    background-color: var(--color-success-500);
}
.review-status--error .review-status__chip {
    background-color: var(--color-error-400);
    color: #fff;
}
.review-status--error .review-status__chip:hover {
    background-color: var(--color-error-500);
}
.review-status--neutral .review-status__chip {
    background-color: var(--color-neutral-200);
    color: var(--color-neutral-500);
}
.review-status--neutral .review-status__chip:hover {
    background-color: var(--color-neutral-300);
}

/* Open-state menu. Pinned in viewport coordinates by the JS
   (``top``/``left``/``width`` inline styles) so it escapes the left
   pane's ``overflow: hidden``. Three plain text options with hover. */
.review-status__menu {
    list-style: none;
    margin: 0;
    padding: 4px;
    /* Auto-size to the widest option ("Accept"/"Reject"/"Select" are
       all short) but never shrink below a tasteful minimum, so the
       hit-targets stay comfortably wide. */
    width: max-content;
    min-width: 96px;
    background: var(--color-surface);
    border: 1px solid var(--color-outline-variant);
    border-radius: 8px;
    box-shadow: 0 8px 24px rgba(15, 23, 42, 0.12);
    z-index: 60;
    font-size: 13px;
    color: var(--color-on-surface);
}
.review-status__opt {
    padding: 6px 10px;
    border-radius: 4px;
    cursor: pointer;
    user-select: none;
    line-height: 1.2;
    text-align: center;
}
.review-status__opt:hover           { background: var(--color-neutral-100); }
.review-status__opt[aria-selected="true"] {
    background: var(--color-primary-50);
    color: var(--color-primary-700);
    font-weight: 500;
}

/* Excel-like cell selection in the body pane. The selected cell gets a
   primary ring drawn as an inset box-shadow (no layout shift), and we
   suppress the native focus outline since the ring is the indicator.
   Applies to question cells AND the reference cells (date / uuid /
   transaction_id) so they're equally first-class targets for click
   selection + keyboard navigation. */
.review-pane--body .review-table__col-q,
.review-pane--body .review-table__col-ref {
    outline: none;
    cursor: cell;
}
/* The ring is drawn as an overlay pseudo-element (not a box-shadow on the
   td itself) so it stacks ABOVE the top-left brand corner ribbon
   (.response-brand, z-index: 2) — otherwise the ribbon paints over the ring
   and clips the blue selection border at the cell's corner. */
.review-pane--body .review-table__col-q.is-selected::after,
.review-pane--body .review-table__col-ref.is-selected::after {
    content: "";
    position: absolute;
    inset: 0;
    box-shadow: inset 0 0 0 2px var(--color-primary);
    pointer-events: none;
    z-index: 3;
}

/* Response text cells. The text fills the cell; any flag icons are absolutely
   positioned in the td's top-right corner via .response-flags so they sit on
   top of the text rather than reflowing it. .review-cell is intentionally
   NOT a positioned ancestor — the flags anchor to the td (.review-table__col-q
   has position: relative) so they pin to the td's actual corner regardless of
   how the td vertically aligns .review-cell. */
.review-cell {
    display: block;
}
.review-cell__text {
    display: block;
    min-width: 0;
}
/* Flag-icon overlay — anchored to the td (.review-table__col-q has
   position: relative) so the chips sit flush in the cell's top-right corner
   regardless of the row's height or the answer text's vertical alignment. */
.response-flags {
    position: absolute;
    /* 3px inset so the cell's 2px inset selection border (the blue ring on
       focus) stays visible behind the chips instead of being clipped by
       them at the corner. */
    top: 3px;
    right: 3px;
    display: inline-flex;
    align-items: center;
    gap: 2px;
    z-index: 1;
}

/* Inline validation flags — small icons in the cell's top-right corner. The
   six canonical flags the reviewer can toggle (keyboard P / A / N / G / D / U):
   profanity, ai_generated, non_context, gibberish, duplicate, unengaged. */
.response-flag {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 18px;
    height: 18px;
    font-family: var(--font-mono);
    font-size: 11px;
    font-weight: 700;
    line-height: 1;
    text-transform: uppercase;
    user-select: none;
    /* Pointer cursor signals that the chip is interactive — the reviewer
       can hover for the AI rationale (via the .app-tooltip / data-tip
       handler on the chip) and click to act on the flag. */
    cursor: pointer;
}
.response-flag--profanity {
    background: var(--color-error-200);
    color: var(--color-error-600);
}
.response-flag--ai_generated {
    background: var(--color-info-100);
    color: var(--color-info-600);
}
.response-flag--non_context {
    background: var(--color-neutral-300);
    color: var(--color-neutral-700);
}
.response-flag--gibberish {
    background: var(--color-warning-200);
    color: var(--color-warning-700);
}
.response-flag--duplicate {
    background: var(--color-tertiary-200);
    color: var(--color-tertiary-600);
}
.response-flag--unengaged {
    background: var(--color-accent-200);
    color: var(--color-accent-700);
}
/* Brand marker — a folded "corner ribbon" filling the cell's TOP-LEFT
   corner (flag chips sit top-right), with a diagonally-set "B" on it.
   Marks a response the AI Brand Analyser judged a brand. Shown everywhere
   an answer appears: the grid, uniques popup, record view.
   ``.response-brand`` is the triangle; the inner ``.response-flag--brand``
   carries the letter. */
.response-brand {
    position: absolute;
    top: 0;
    left: 0;
    display: block;
    width: 20px;
    height: 20px;
    z-index: 2;
    background: var(--tw-cyan-200);
    /* Top-left right-triangle: top-left, top-right, bottom-left corners. */
    clip-path: polygon(0 0, 100% 0, 0 100%);
}
/* The "B" — white, tilted along the diagonal, seated in the triangle's
   mass near the corner. Overrides the square flag-chip chrome. */
.response-flag--brand {
    position: absolute;
    top: 0;
    left: 0;
    width: 12px;
    height: 12px;
    background: transparent;
    color: var(--tw-cyan-600);
    border-radius: 0;
    /* Letter hidden — the corner triangle alone marks a brand response. */
    font-size: 0;
    transform: rotate(-45deg);
}
/* The B chip overlays the top-left corner (position: absolute) exactly
   like the top-right flag chips — it must NOT reserve horizontal space,
   so the answer text keeps its usual left start whether or not a cell is
   brand-flagged. (No padding-left here by design.) */
/* Brand count — separated from the P·A·N·G·D·U strip as its own round chip
   so it reads as a distinct, positive metric rather than another flag. The
   row wrapper seats the boxed flag strip and the round chip side by side. */
.flag-strip-row {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    vertical-align: middle;
}
.flag-brand-chip {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 20px;
    height: 20px;
    border-radius: 50%;
    box-sizing: border-box;
    font-family: var(--font-mono);
    font-size: 11px;
    line-height: 1;
    font-variant-numeric: tabular-nums;
}
/* Header — filled neutral disc carrying the "B" label, matching the tone of
   the P·A·N·G·D·U totals cells. */
.flag-brand-chip--head {
    background: var(--color-neutral-200);
    color: var(--color-on-surface-variant);
    font-weight: 700;
}
/* Body — neutral-outlined circle (same gray as the flag box border) with the
   per-value brand count. */
.flag-brand-chip--body {
    border: 1px solid var(--color-outline-variant);
    color: var(--color-on-surface);
    font-weight: 400;
}
/* No brand-flagged cells for this value — keep the slot for column
   alignment but drop the ring so only real counts show a circle. */
.flag-brand-chip--empty {
    border-color: transparent;
}


/* =============================================
   Project Detail page
   - Top header: back link + project ID/name + stat chips + import/export icons
   - Body: 2-col grid (main: tabs+batches table; side: summary / rejections / reviewers)
   ============================================= */

/* Top header bar — replaces .dashboard-header for the project page because the
   stat chips need to sit inline with the title (not wrap to a filter row). */
.project-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 16px;
    margin-bottom: 20px;
    flex-wrap: wrap;
}
.project-header__left {
    display: flex;
    align-items: center;
    gap: 12px;
    min-width: 0;
}
.project-header__title {
    /* Override .page-title's margin so it aligns with the back button & stats row. */
    margin: 0;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.project-header__right {
    display: flex;
    align-items: center;
    gap: 6px;
    flex-wrap: wrap;
}
/* Project-header action pills (download / reconcile) — tighter corners than
   the table-cell variant so they read as crisp toolbar buttons, not row
   chrome. Reduced horizontal padding so the icon-only actions cluster
   together instead of drifting apart at the row's right edge. */
.project-header__right > .cell-action {
    border-radius: 4px;
    padding: 6px 6px;
}

/* Inline stat chip — icon + count. Tone modifier sets the icon colour so the
   row reads as a glanceable status row (reviewed/flagged/comments). */
.header-stat {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-size: 14px;
    font-weight: 500;
    color: var(--color-on-surface);
}
.header-stat > svg {
    width: 18px;
    height: 18px;
    stroke-width: 1.75;
}
.header-stat--success > svg { color: var(--color-success-500); }
.header-stat--warning > svg { color: var(--color-warning-500); }
.header-stat--info    > svg { color: var(--color-info-500); }

/* Status picker — clickable badge that sits next to the project title and
   opens a small popover listing every status option. The trigger reuses the
   .badge styles (so its colour tracks the badge--<tone> modifier on the
   element) and adds a caret + cursor:pointer to advertise the action. */
.status-picker {
    position: relative;
    display: inline-flex;
    align-items: center;
    margin-left: 12px;
    padding-left: 12px;
}
/* Thin vertical divider between the title and the status trigger so the
   two elements read as separate concerns. Sized smaller than the title
   line-height (1em) so it doesn't dominate the row. */
.status-picker::before {
    content: "";
    position: absolute;
    left: 0;
    top: 50%;
    transform: translateY(-50%);
    width: 1px;
    height: 18px;
    background: var(--color-outline);
}
.status-picker__trigger {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 4px 8px 4px 10px;
    /* Kill the .badge--<tone> chip background — the trigger keeps only the
       tone-coloured text so the label sits cleanly against the page. */
    background: transparent;
    border: 0;
    cursor: pointer;
    font: inherit;
    font-weight: 600;
}
.status-picker__trigger:hover { background: var(--color-neutral-75); }
.status-picker__caret {
    width: 12px;
    height: 12px;
    stroke-width: 2.25;
    opacity: 0.7;
    transition: transform 120ms ease;
}
.status-picker--open .status-picker__caret { transform: rotate(180deg); }

.status-picker__panel {
    position: absolute;
    z-index: 60;
    top: calc(100% + 6px);
    left: 0;
    min-width: 200px;
    padding: 6px;
    background: var(--color-surface);
    border: 1px solid var(--color-outline-variant);
    border-radius: var(--radius-control);
    box-shadow: 0 10px 28px rgba(15, 23, 42, 0.14);
    display: none;
    flex-direction: column;
    gap: 2px;
}
.status-picker--open .status-picker__panel { display: flex; }

.status-picker__opt {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    padding: 6px 8px;
    border: 0;
    border-radius: 6px;
    background: transparent;
    cursor: pointer;
    text-align: left;
    font: inherit;
}
.status-picker__opt:hover         { background: var(--color-neutral-100); }
.status-picker__opt--active       { background: var(--color-neutral-75); }
.status-picker__check {
    width: 14px;
    height: 14px;
    stroke-width: 2.5;
    color: var(--color-on-surface-muted);
}


/* =============================================
   Row menu — kebab popover on table-row action cells
   ============================================= */
.row-menu {
    position: relative;
    display: inline-block;
}
.row-menu__trigger { /* inherits .icon-btn / .icon-btn--ghost from the markup */ }

.row-menu__panel {
    position: absolute;
    z-index: 60;
    /* Anchor to the trigger's right edge so the panel hangs left under
       the cell; that's where there's room in a data-table row. */
    top: calc(100% + 4px);
    right: 0;
    min-width: 120px;
    padding: 4px;
    background: var(--color-surface);
    border: 1px solid var(--color-outline-variant);
    border-radius: var(--radius-control);
    box-shadow: 0 10px 28px rgba(15, 23, 42, 0.14);
    display: none;
    flex-direction: column;
}
.row-menu--open .row-menu__panel { display: flex; }

.row-menu__item {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 8px 10px;
    border: 0;
    border-radius: 6px;
    background: transparent;
    cursor: pointer;
    text-align: left;
    font: inherit;
    font-size: 13px;
    color: var(--color-on-surface);
    text-decoration: none;
}
.row-menu__item:hover            { background: var(--color-neutral-100); }
.row-menu__item > svg {
    width: 16px;
    height: 16px;
    stroke-width: 1.75;
    color: var(--color-on-surface-muted);
}
.row-menu__item--danger          { color: var(--color-error-600); }
.row-menu__item--danger > svg    { color: var(--color-error-500); }
.row-menu__item--danger:hover    { background: var(--color-error-75); }

/* Two-column body. Side rail is fixed-width so the donut & summary tiles
   don't get squashed when the batches table tries to expand. */
.project-detail-grid {
    display: grid;
    /* Side column sized to fit four short-label metric tiles
       (Records / Reviewed / Rejected / Recon). */
    grid-template-columns: minmax(0, 1fr) 360px;
    gap: 20px;
    align-items: start;
}
.project-detail-grid__main { min-width: 0; }
.project-detail-grid__side {
    display: flex;
    flex-direction: column;
    gap: 20px;
}

/* The tabs row sits flush with the card edge. The base .data-table rule
   indents the first cell by 24px (matches the .tabs row's left padding);
   in the tighter project-detail layout that indent reads as a gap, so we
   pull it back in line with the tab labels. */
.project-tabs-card .data-table th:first-child,
.project-tabs-card .data-table td:first-child {
    padding-left: 12px;
}

/* Three-tile summary card — RECORDS / REVIEWED / REJECTED.
   No internal dividers (matches the airy feel of the reference). */
.summary-card { padding: 18px 20px; }
.summary-tiles {
    /* Content-sized tiles spread edge-to-edge: the first hugs the left,
       the last (Recon) hugs the right, so there's no trailing gap after
       the final tile the way ``1fr`` columns left. */
    display: flex;
    justify-content: space-between;
    gap: 12px;
}
.summary-tile { display: flex; flex-direction: column; gap: 6px; }
.summary-tile__label {
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--color-on-surface-muted);
    white-space: nowrap;      /* keep "TO RECONCILE" on one line */
}
.summary-tile__value {
    font-size: 18px;
    font-weight: 700;
    line-height: 1;
    color: var(--color-on-surface);
}
.summary-tile__value--success { color: var(--color-success-600); }
.summary-tile__value--warning { color: var(--color-warning-600); }
/* Matches the error-500 used by .rejections-split__count below, so the rejected
   total on the metrics tile reads in the same red as the client/qa breakdown. */
.summary-tile__value--error   { color: var(--color-error-500); }
.summary-tile__value--error   { color: var(--color-error-500); }
.summary-tile__sub {
    font-size: 12px;
    color: var(--color-on-surface-muted);
    min-height: 1em;          /* keep the third row aligned even on the empty Records tile */
}

/* Reviewers list card — name+role on the left, two numeric columns on the right.
   The .reviewers-cols block lives inside .chart-card__head--with-cols; its track
   widths MUST match the list-item grid below so the column labels line up
   precisely above their values. */
.chart-card__head--with-cols {
    align-items: flex-start;
    margin-bottom: 16px;
}
.reviewers-cols {
    display: grid;
    grid-template-columns: 64px 72px;
    gap: 8px;
}
.reviewers-cols__label {
    font-size: 11px;
    font-weight: 500;
    color: var(--color-on-surface-muted);
    text-align: right;
}
.reviewers-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 14px;
}
.reviewers-list__item {
    display: grid;
    grid-template-columns: 1fr 64px 72px;
    align-items: center;
    gap: 8px;
}
.reviewers-list__name {
    font-size: 13px;
    font-weight: 400;
    color: var(--color-on-surface);
}
.reviewers-list__count {
    font-size: 13px;
    font-weight: 400;
    color: var(--color-on-surface);
    text-align: right;
    font-variant-numeric: tabular-nums;
}


/* =============================================
   Project Detail — Info tab
   Read-only field grid + sectioned layout. Section titles use the same
   small-caps tier as .chart-card__title; each section has a thin top
   divider so the page reads as distinct content blocks without heavy chrome.
   ============================================= */
.info-pane {
    position: relative;
    padding: 20px 24px 24px;
}
.info-section + .info-section {
    margin-top: 28px;
}
.info-section__title {
    margin: 0 0 14px 0;
    padding-bottom: 8px;
    border-bottom: 1px solid var(--color-outline-variant);
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    /* Medium-emphasis tier — matches the wizard subsection title treatment so
       11px uppercase labels read consistently across pages. */
    color: var(--color-on-surface-variant);
}
.info-grid {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    /* Row gap large, column gap modest. Pairing comes from the tight
       label→value gap inside .info-field (below); the larger row gap
       opens up real whitespace between pairs so the eye doesn't confuse
       which value belongs to which label. */
    gap: 26px 32px;
}
/* When two .info-grid blocks stack inside one section, separate them by the
   same vertical gap used between rows within a grid — feels continuous. */
.info-grid + .info-grid { margin-top: 26px; }
.info-field {
    display: flex;
    flex-direction: column;
    /* Label hugs its value (2px) so the pair reads as one unit; the row
       gap above does the visual separation between unrelated pairs. */
    gap: 2px;
    min-width: 0;
}
.info-field__display {
    font-size: 14px;
    color: var(--color-on-surface);
    line-height: 1.4;
    word-break: break-word;
}
/* Quiet callout used for reviewer instructions — primary-tinted background
   to mark it as "guidance copy", not generic body text. */
.info-callout {
    padding: 14px 16px;
    background: var(--color-primary-50);
    border-radius: var(--radius);
    font-size: 14px;
    line-height: 1.5;
    color: var(--color-on-surface);
}
/* Datamap picker — clickable label next to the Questions tab's "Datamap"
   heading that opens a small popover with every layout the survey reports.
   Same affordance as the status picker (caret + faint hover) so the chip
   reads as actionable rather than static text. */
.datamap-picker {
    position: relative;
    display: block;
}
.datamap-picker__trigger {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    width: 100%;
    padding: 10px 14px;
    background: var(--color-neutral-75);
    border: 1px solid var(--color-outline-variant);
    border-radius: var(--radius-control);
    cursor: pointer;
    font: inherit;
    color: var(--color-on-surface);
}
.datamap-picker__trigger:hover { background: var(--color-neutral-100); }
.datamap-picker--open .status-picker__caret { transform: rotate(180deg); }

.datamap-picker__panel {
    position: absolute;
    z-index: 60;
    top: calc(100% + 6px);
    left: 0;
    right: 0;
    width: 100%;
    max-height: 320px;
    overflow-y: auto;
    padding: 6px;
    background: var(--color-surface);
    border: 1px solid var(--color-outline-variant);
    border-radius: var(--radius-control);
    box-shadow: 0 10px 28px rgba(15, 23, 42, 0.14);
    display: none;
}
.datamap-picker--open .datamap-picker__panel { display: block; }
.datamap-picker__status {
    padding: 10px 12px;
    font-size: 13px;
    color: var(--color-on-surface-muted);
}
.datamap-picker__list {
    list-style: none;
    margin: 0;
    padding: 0;
}
.datamap-picker__opt {
    display: block;
    width: 100%;
    padding: 8px 10px;
    border: 0;
    border-radius: 6px;
    background: transparent;
    cursor: pointer;
    text-align: left;
    font: inherit;
    color: var(--color-on-surface);
}
.datamap-picker__opt:hover:not(:disabled) { background: var(--color-neutral-100); }
.datamap-picker__opt--active              { color: var(--color-on-surface-muted); cursor: default; }
.datamap-picker__opt:disabled             { cursor: default; }

/* Confirm dialog options — vertical stack of large clickable cards, each
   surfacing one branch (Clean up vs Merge). Disabled state mutes the
   colour and dims the title so the "Coming soon" pill stays the obvious
   marker without making the option invisible. */
/* Specificity bump (.modal.modal--…) so the base .modal max-width — which
   appears later in the file and would otherwise win the cascade — doesn't
   clamp this dialog back down. */
.modal.modal--datamap-confirm { max-width: 640px; }
.datamap-confirm__body {
    display: flex;
    flex-direction: column;
    gap: 10px;
}
/* Inline error chip shown above the option cards when the server
   rejects the chosen layout (e.g. missing required variables). */
.datamap-confirm__error {
    padding: 8px 12px;
    border-radius: 6px;
    background: var(--color-error-bg, rgba(220, 38, 38, 0.08));
    color: var(--color-error, #b91c1c);
    font-size: 13px;
    line-height: 1.4;
}
.datamap-confirm__option {
    display: flex;
    flex-direction: column;
    gap: 4px;
    padding: 14px 16px;
    background: var(--color-surface);
    border: 1px solid var(--color-outline-variant);
    border-radius: var(--radius-control);
    text-align: left;
    cursor: pointer;
    font: inherit;
}
.datamap-confirm__option:hover:not(:disabled) {
    border-color: var(--color-primary);
    background:   var(--color-primary-75);
}
.datamap-confirm__option-title {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 14px;
    font-weight: 600;
    color: var(--color-on-surface);
}
.datamap-confirm__option-desc {
    font-size: 12px;
    color: var(--color-on-surface-muted);
    line-height: 1.5;
}
.datamap-confirm__option--disabled {
    cursor: not-allowed;
    opacity: 0.55;
}
.datamap-confirm__pill {
    display: inline-flex;
    align-items: center;
    padding: 2px 8px;
    border-radius: 999px;
    background: var(--color-neutral-100);
    color: var(--color-on-surface-muted);
    font-size: 11px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}

/* Question-classification table sits in the info pane (not its own card),
   so kill the outer first-cell indent and run flush with the section title. */
.info-questions th:first-child,
.info-questions td:first-child {
    padding-left: 0;
}
/* Tighter vertical rhythm than the standard .data-table 11px so the long
   question list reads compactly without scrolling. */
.info-questions th,
.info-questions td {
    padding-top: 5px;
    padding-bottom: 5px;
}
/* Brand-toggle column — centred so the 36px-wide toggle visual reads as a
   self-contained on/off control rather than a left-flushed form field. */
.info-questions .col-toggle {
    text-align: center;
    width: 80px;
}
/* Question Context column (3rd) — its textarea (width:100%) fills it.
   Width kept moderate; the Description column (2nd) absorbs the remaining
   space and wraps to a second line when its text is long. */
.info-questions th:nth-child(3),
.info-questions td:nth-child(3) {
    width: 33%;
}

/* ----- Read/Edit toggle ----- */
/* Action bar floats in the top-right of the info pane (position: absolute) so
   it doesn't push the first section heading down. The first section heading
   sits at the top of the pane content — the button never participates in flow. */
.info-pane__head {
    position: absolute;
    /* Centred between the tabs divider above (y ≈ 0) and the section-heading
       underline below (y ≈ 45px) — midpoint ≈ 22, minus 16 for half the
       32px button height, gives top: 6px. */
    top: 6px;
    right: 24px;
    display: flex;
    gap: 8px;
    z-index: 1;
}
.info-pane__btn--edit > svg { width: 14px; height: 14px; }
/* By default, only the Edit button is visible. */
.info-pane .info-pane__btn--cancel,
.info-pane .info-pane__btn--save        { display: none; }
.info-pane--editing .info-pane__btn--edit { display: none; }
.info-pane--editing .info-pane__btn--cancel,
.info-pane--editing .info-pane__btn--save { display: inline-flex; }

/* Per-field display/edit visibility. .info-field__edit covers inputs, selects
   AND a textarea inside .info-field--block (instructions). */
.info-pane .info-field__edit             { display: none; }
.info-pane--editing .info-field__display { display: none; }
.info-pane--editing .info-field__edit    { display: block; }

/* Native <select> styled to match .input — appearance:none + chevron via SVG bg.
   The chevron color is on-surface-muted; can't use currentColor in url() so it's inlined. */
.select-native {
    width: 100%;
    height: 40px;
    padding: 0 36px 0 16px;
    font-family: inherit;
    font-size: 14px;
    color: var(--color-on-surface);
    background: var(--color-surface);
    border: 1px solid var(--color-outline);
    border-radius: var(--radius-control);
    cursor: pointer;
    appearance: none;
    -webkit-appearance: none;
    background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%2390929B' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'><polyline points='6 9 12 15 18 9'/></svg>");
    background-repeat: no-repeat;
    background-position: right 12px center;
    background-size: 14px 14px;
    transition: border-color 120ms;
}
.select-native:hover { border-color: var(--color-neutral-300); }
.select-native:focus {
    outline: none;
    border-color: var(--color-primary);
}
/* Inline variant for in-table editing — slimmer height, no full-width grow. */
.select-native--inline {
    width: auto;
    min-width: 120px;
    height: 32px;
    font-size: 13px;
}

/* Instructions textarea sits in a single full-width field rather than the grid. */
.info-field--block { width: 100%; }


/* =============================================
   System Settings page
   - Header: title + blurb on the left, "Save Changes" CTA on the right
   - Tabs row
   - Single card containing one or more settings sections
   - Each section: head (title + blurb) + grid of fields + optional toggle rows
   ============================================= */
.settings-header {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 16px;
    margin-bottom: 20px;
    flex-wrap: wrap;
}
.settings-header__title { min-width: 0; }
.settings-header__title .page-title { margin: 0 0 6px 0; }
.settings-header__blurb {
    margin: 0;
    font-size: 13px;
    color: var(--color-on-surface-muted);
    line-height: 1.4;
}
/* Tab bar: tab nav on the left, per-tab primary action on the right
   (Save Changes / Add Key). Sits inline so the action is paired with the
   tab it scopes to, and the card body stays clean. */
.settings-tabbar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 16px;
    margin-bottom: 16px;
}
.settings-tabs {
    padding: 0;
    border-bottom: none;       /* drop the full-width underline; active tab keeps its indicator */
}
.settings-tabbar__actions { flex: 0 0 auto; }
.settings-tabbar__actions .btn > svg { width: 14px; height: 14px; }

/* Outer card wraps all sections so they read as one unit (Save Changes
   applies tab-wide). Sections are separated by a thin divider, not their
   own card chrome. position:relative so the Save Changes button can absolute-
   position itself to the top-right corner without pushing the first section
   down. */
.settings-card {
    padding: 20px 24px;
}
.settings-section + .settings-section {
    margin-top: 24px;
    padding-top: 24px;
    border-top: 1px solid var(--color-outline-variant);
}
.settings-section__head { margin-bottom: 18px; }
/* Title on the left, a status pill (e.g. Configured) pushed to the
   top-right edge of the card header. */
.settings-section__head--with-status {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 16px;
}
.settings-section__head--with-status .ai-prompts-item__status { margin-top: 2px; }
.settings-section__title {
    margin: 0 0 4px 0;
    font-size: 15px;
    font-weight: 600;
    color: var(--color-on-surface);
}
.settings-section__blurb {
    margin: 0;
    font-size: 13px;
    color: var(--color-on-surface-muted);
    line-height: 1.4;
}

/* 2-column field grid. Use --full to span a row (e.g. long instruction input). */
.settings-grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 16px 24px;
    margin-bottom: 12px;
}
.settings-field { display: flex; flex-direction: column; gap: 8px; min-width: 0; }
.settings-field--full { grid-column: 1 / -1; }
.settings-field--tall { grid-row: span 3; }
.settings-field--tall > textarea { flex: 1; min-height: 0; }
.settings-field .field-label {
    /* Override the all-caps eyebrow style of .field-label — settings labels
       read as normal sentence-case, so we keep just the muted-tone semantics. */
    margin-bottom: 0;
    font-size: 13px;
    font-weight: 500;
    color: var(--color-on-surface);
    text-transform: none;
    letter-spacing: 0;
}

/* =============================================
   API Keys tab — one card per integration
   =============================================
   Each integration (Decipher, Marketplace) renders as its own card with:
     - Brand chip (tinted icon, top-left)
     - Title + one-line description
     - Connected / Not configured status badge (top-right)
     - Value row: masked preview that swaps to an inline input on Edit,
       with Edit / Save / Cancel buttons right-aligned

   The value preview never carries the raw key — only the last 4 chars
   prefixed by a •-mask. Edit mode opens an empty input the user pastes
   into; the raw key never lives in the DOM at all. */
.api-key-card-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 14px;
}
/* Stack on narrow viewports so the value row + buttons stay readable. */
@media (max-width: 880px) {
    .api-key-card-list { grid-template-columns: minmax(0, 1fr); }
}
.api-key-card {
    background: var(--color-surface);
    border: 1px solid var(--color-outline-variant);
    border-radius: var(--radius);
    padding: 20px 22px;
    transition: border-color 120ms, box-shadow 120ms;
}
.api-key-card:hover {
    border-color: var(--color-outline);
}
.api-key-card--editing {
    /* Subtle ring while editing so the active card stands out from its sibling. */
    border-color: var(--color-primary);
    box-shadow: 0 0 0 3px var(--color-primary-50);
}

/* --- Card head: brand chip + heading + status ---------------------------- */
.api-key-card__head {
    display: flex;
    align-items: center;
    gap: 14px;
    margin-bottom: 16px;
}
.api-key-card__icon {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 38px;
    height: 38px;
    border-radius: 10px;
}
.api-key-card__icon > svg {
    width: 20px;
    height: 20px;
}
/* Per-integration palette — info tone for Decipher (data ingress), tertiary
   tone for Marketplace (panel completions). Subtle tinted bg + saturated icon. */
.api-key-card__icon--decipher {
    background: var(--color-info-50);
    color:      var(--color-info-600);
}
.api-key-card__icon--marketplace {
    background: var(--color-tertiary-50);
    color:      var(--color-tertiary-600);
}
/* Gemini AI — warning-50 wash to read as a distinct "AI" brand chip
   alongside Decipher (info-tinted) and Marketplace (tertiary-tinted). */
.api-key-card__icon--gemini {
    background: var(--color-warning-50);
    color:      var(--color-warning-600);
}
.api-key-card__heading {
    flex: 1;
    min-width: 0;
}
.api-key-card__title {
    font-size: 15px;
    font-weight: 600;
    color: var(--color-on-surface);
    line-height: 1.3;
}
.api-key-card__desc {
    margin-top: 2px;
    font-size: 12.5px;
    color: var(--color-on-surface-muted);
    line-height: 1.4;
}
.api-key-card__status {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    gap: 6px;
    /* Slightly more breathing room than a default .badge — this is a
       single-piece-of-info pill, not a counter. */
    padding: 4px 10px;
    font-size: 11.5px;
    font-weight: 600;
    letter-spacing: 0.01em;
    text-transform: none;
}

/* Small dot inside the status pill. JS swaps the modifier on save. */
.status-dot {
    display: inline-block;
    width: 6px;
    height: 6px;
    border-radius: 999px;
    flex: 0 0 auto;
}
.status-dot--success { background: var(--color-success-600); }
.status-dot--neutral { background: var(--color-on-surface-muted); }

/* --- Value row: code-style preview; the panel IS the click target -------
   Clicking the panel (or pressing Enter/Space when focused) enters edit
   mode. The actions row below only shows in edit mode. */
.api-key-card__value {
    display: flex;
    align-items: center;
    padding: 10px 14px;
    background: var(--color-neutral-50);
    border: 1px solid var(--color-outline-variant);
    border-radius: var(--radius-control);
    cursor: text;        /* hints "this is editable text"  */
    transition: border-color 120ms, background-color 120ms;
}
/* Hover / focus affordance applies only while THIS box's input is masked
   (not being edited) — so the key box still invites a click even when the
   model is mid-edit, and stops looking clickable once its own input is
   live. */
.api-key-card__value:has(.api-key-card__input[hidden]):hover {
    background:    var(--color-surface);
    border-color:  var(--color-outline);
}
.api-key-card__value:has(.api-key-card__input[hidden]):focus-visible {
    outline:       none;
    border-color:  var(--color-primary);
    box-shadow:    0 0 0 3px var(--color-primary-50);
}
/* In view mode, the actions row is empty noise. Hide it entirely so the
   card lays out as just [head] + [value panel]. */
.api-key-card:not(.api-key-card--editing) .api-key-card__actions {
    display: none;
}
/* Active styling keys off the input actually being live, not the card's
   edit state — so a model-only edit doesn't highlight the key box. */
.api-key-card__value:has(.api-key-card__input:not([hidden])) {
    background: var(--color-surface);
    border-color: var(--color-primary);
}
.api-key-card__value-display {
    flex: 1;
    min-width: 0;
    display: flex;
}
.api-key-card__preview {
    flex: 1;
    min-width: 0;
    font-size: 13px;
    color: var(--color-on-surface);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.api-key-card__empty {
    color: var(--color-on-surface-muted);
    font-style: italic;
}
/* The edit input is styled as "text living in the value row" — no border,
   transparent bg, monospace font — rather than a normal form input. Makes
   Edit feel like in-place editing instead of "form mode". */
.api-key-card__input {
    flex: 1;
    min-width: 0;
    margin: 0;
    padding: 0;
    border: none;
    background: transparent;
    color: var(--color-on-surface);
    font-family: var(--font-mono);
    font-size: 13px;
    line-height: 1.4;
}
.api-key-card__input:focus { outline: none; }
.api-key-card__input::placeholder { color: var(--color-on-surface-muted); }

.api-key-card__actions {
    /* Sits below the value panel — right-aligned so the primary action
       (Edit, or Save in edit mode) gravitates to the natural "next step"
       corner of the card. */
    margin-top: 12px;
    display: flex;
    justify-content: flex-end;
    align-items: center;
    gap: 6px;
}
/* Compact action buttons inside the value row — slimmer than default so
   they don't dwarf the value text. */
.api-key-card__actions > .btn {
    padding: 5px 12px;
    font-size: 12.5px;
}
.api-key-card__actions > .btn > svg {
    width: 13px;
    height: 13px;
}

/* Gemini-only model picker — sits below the key value panel and shares
   the card's single edit cycle (driven by .api-key-card--editing), so it
   has no Save/Cancel of its own: view mode shows the model id in a
   bordered box; in edit mode the box chrome drops away and the <select>
   shows, committed by the card's one Save/Cancel row below. */
.api-key-card__model {
    margin-top: 14px;
    display: flex;
    flex-direction: column;
    gap: 6px;
}
.api-key-card__thinking {
    margin-top: 14px;
    display: flex;
    flex-direction: column;
    gap: 6px;
}
.api-key-card__thinking-row {
    display: flex;
    align-items: center;
    gap: 10px;
}
.api-key-card__thinking-hint {
    font-size: 12px;
    color: var(--color-on-surface-muted);
}
.api-key-card__model-value {
    display: flex;
    align-items: center;
    padding: 10px 14px;
    background: var(--color-neutral-50);
    border: 1px solid var(--color-outline-variant);
    border-radius: var(--radius-control);
    cursor: pointer;
    transition: border-color 120ms, background-color 120ms;
}
.api-key-card__model-value:has(.gemini-model-select[hidden]):hover {
    background:   var(--color-surface);
    border-color: var(--color-outline);
}
.api-key-card__model-value:has(.gemini-model-select[hidden]):focus-visible {
    outline:      none;
    border-color: var(--color-primary);
    box-shadow:   0 0 0 3px var(--color-primary-50);
}
/* When the dropdown is live, drop the panel chrome so only the select's
   own border shows (no double border). Keys off the select's own state,
   not the card's, so editing the key alone leaves this box untouched. */
.api-key-card__model-value:has(.gemini-model-select:not([hidden])) {
    padding:    0;
    border:     none;
    background: transparent;
}
.api-key-card__model-preview {
    flex: 1;
    min-width: 0;
    font-size: 13px;
    color: var(--color-on-surface);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.api-key-card__model .select-native {
    flex: 1;
    min-width: 0;
    width: 100%;
}

/* ----- Multi-key cards (Decipher: one row per server) ----------------------
   The Decipher card holds an arbitrary list of <hostname → key> rows since
   the operator may have accounts on multiple Decipher tenants. Each row is
   its own little VIEW/EDIT cycle; the card footer hosts an "Add server"
   form. Layout sits inside .api-key-card so it inherits the head + status
   chrome — only the body region changes.                                   */
.api-key-row-list {
    list-style: none;
    padding: 0;
    margin: 0;
    display: flex;
    flex-direction: column;
    gap: 8px;
}
.api-key-row {
    /* Server hostname (left) gets generous room — long Decipher hostnames
       like ``purespectrum.decipherinc.com`` are common and shouldn't
       collapse to ``purespectrum.dec…``. The mask sits in the middle
       sized to its content (28 monospace chars has a stable width).
       Actions hug the right.                                                */
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto auto;
    align-items: center;
    gap: 12px;
    padding: 10px 12px;
    border: 1px solid var(--color-outline-variant);
    border-radius: 8px;
    background: var(--color-surface-alt, var(--color-surface));
}
.api-key-row--empty {
    grid-template-columns: 1fr;
    justify-items: start;
    color: var(--color-on-surface-muted);
    font-style: italic;
}
.api-key-row__server {
    font-size: 13px;
    color: var(--color-on-surface);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.api-key-row__value {
    display: flex;
    flex: 1;
    min-width: 0;
    padding: 6px 10px;
    border: 1px solid transparent;
    border-radius: 6px;
    cursor: text;
    transition: background-color 0.12s, border-color 0.12s;
}
.api-key-row:not(.api-key-row--editing) .api-key-row__value:hover {
    background: var(--color-surface);
    border-color: var(--color-outline-variant);
}
.api-key-row:not(.api-key-row--editing) .api-key-row__value:focus-visible {
    outline: 2px solid var(--color-primary);
    outline-offset: 1px;
}
.api-key-row--editing .api-key-row__value {
    background: var(--color-surface);
    border-color: var(--color-primary);
}
.api-key-row__preview {
    flex: 1;
    min-width: 0;
    font-size: 13px;
    color: var(--color-on-surface);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.api-key-row__input {
    flex: 1;
    min-width: 0;
    margin: 0;
    padding: 0;
    border: none;
    background: transparent;
    color: var(--color-on-surface);
    font-family: var(--font-mono);
    font-size: 13px;
    line-height: 1.4;
}
.api-key-row__input:focus { outline: none; }
.api-key-row__input::placeholder { color: var(--color-on-surface-muted); }
.api-key-row__actions {
    display: flex;
    align-items: center;
    gap: 6px;
}
.api-key-row__actions > .btn {
    padding: 5px 10px;
    font-size: 12.5px;
}
.api-key-row__actions > .btn > svg {
    width: 13px;
    height: 13px;
}

/* Add-server footer on the multi-key card.
   The toggle is the default state; clicking it reveals the form, which
   lays out as [server input | key input | Cancel | Save] in a single row
   and wraps on narrow viewports.                                          */
.api-key-card__add {
    margin-top: 12px;
    display: flex;
    flex-direction: column;
    gap: 8px;
}
.api-key-card__add > .btn {
    align-self: flex-start;
    padding: 5px 12px;
    font-size: 12.5px;
}
.api-key-card__add > .btn > svg {
    width: 13px;
    height: 13px;
}
/* The add form reads as a single "compose new server" panel — two stacked
   labeled inputs and a right-aligned action footer. Full-width inputs
   mean even a long hostname has room to type, and the visual frame
   distinguishes "I'm adding a new entry" from "I'm editing an existing
   row". The panel is slightly inset (margin-left) so it hangs off the
   "Add server" toggle naturally.                                          */
.api-key-add-form {
    display: flex;
    flex-direction: column;
    gap: 10px;
    padding: 14px;
    border: 1px solid var(--color-outline-variant);
    border-radius: 8px;
    background: var(--color-surface);
}
.api-key-add-form__field {
    display: flex;
    flex-direction: column;
    gap: 4px;
}
.api-key-add-form__label {
    font-size: 11.5px;
    font-weight: 500;
    letter-spacing: 0.02em;
    text-transform: uppercase;
    color: var(--color-on-surface-muted);
}
.api-key-add-form .input {
    width: 100%;
    font-family: var(--font-mono);
    font-size: 13px;
}
.api-key-add-form__actions {
    display: flex;
    justify-content: flex-end;
    align-items: center;
    gap: 6px;
    margin-top: 2px;
}
.api-key-add-form__actions > .btn {
    padding: 5px 12px;
    font-size: 12.5px;
}
.api-key-add-form__actions > .btn > svg {
    width: 13px;
    height: 13px;
}
@media (max-width: 720px) {
    .api-key-row {
        grid-template-columns: minmax(0, 1fr);
    }
    .api-key-row__value    { order: 2; }
    .api-key-row__actions  { order: 3; justify-content: flex-end; }
}

/* =============================================
   Profile screen — read-only identity + inline password change
   ─────────────────────────────────────────────────────────────
   The page only carries identity details + one action (change password),
   so the whole layout is capped at a reading width — full app-main
   creates a giant card with whitespace in the middle.
   ============================================= */
.profile-page {
    max-width: 640px;     /* reading column; everything inside lives here */
}

.profile-card {
    padding: 0;
    margin-bottom: 16px;
}
.profile-card > * + * {
    border-top: 1px solid var(--color-outline-variant);
}

/* ----- 1. Identity strip --------------------------------------------
   Layout: avatar | main info column | status chips (own sibling), so
   the chips sit at the strip's vertical centre rather than getting
   pushed around by row-level justify-content.
*/
.profile-strip {
    display: flex;
    align-items: center;
    gap: 14px;
    padding: 14px 16px;
}
.profile-strip__avatar {
    width: 40px;
    height: 40px;
    font-size: 13px;
    flex: 0 0 auto;
}
.profile-strip__main {
    min-width: 0;
    flex: 1 1 auto;
    display: flex;
    flex-direction: column;
    gap: 2px;
}
.profile-strip__name {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: 15px;
    font-weight: 600;
    color: var(--color-on-surface);
    line-height: 1.3;
    min-width: 0;
}
.profile-strip__name > span:first-child {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
/* Small icon-only admin marker. Keeps "Admin" the role distinct from
   the user's display name (which may itself be the string "Admin"), and
   stays out of the way for the common non-admin case. */
.profile-strip__admin {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 16px;
    height: 16px;
    color: var(--color-info, #2563eb);
    flex: 0 0 auto;
}
.profile-strip__admin > svg { width: 14px; height: 14px; }

.profile-strip__email {
    font-size: 12.5px;
    color: var(--color-on-surface);
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.profile-strip__quiet {
    display: flex;
    align-items: center;
    gap: 5px;
    font-size: 11.5px;
    color: var(--color-on-surface-muted);
    flex-wrap: wrap;
}
.profile-strip__sep {
    color: var(--color-outline);
    user-select: none;
}
.profile-strip__meta {
    display: flex;
    align-items: center;
    gap: 6px;
    flex: 0 0 auto;
    flex-wrap: wrap;
    justify-content: flex-end;
}

/* ----- 2. Password row ---------------------------------------------- */
.profile-row {
    padding: 14px 16px;
}
.profile-row__lead { min-width: 0; flex: 1 1 auto; }
.profile-row__title {
    font-size: 13px;
    font-weight: 600;
    color: var(--color-on-surface);
}
.profile-row__sub {
    margin-top: 1px;
    font-size: 11.5px;
    color: var(--color-on-surface-muted);
}

.profile-password__view {
    display: flex;
    align-items: center;
    gap: 12px;
}

.profile-password__edit {
    display: flex;
    flex-direction: column;
    gap: 10px;
}
.profile-password__inputs {
    display: flex;
    gap: 8px;
    min-width: 0;
}
.profile-password__inputs > .input {
    flex: 1 1 0;
    min-width: 0;
    font-family: var(--font-mono);
    font-size: 12.5px;
}
.profile-password__actions {
    display: flex;
    justify-content: flex-end;
    align-items: center;
    gap: 6px;
}
.profile-password__error {
    padding: 6px 10px;
    border-radius: 6px;
    background: var(--color-error-bg, rgba(220, 38, 38, 0.08));
    color: var(--color-error, #b91c1c);
    font-size: 11.5px;
}

@media (max-width: 540px) {
    .profile-strip { flex-wrap: wrap; }
    .profile-strip__meta { width: 100%; justify-content: flex-start; }
    .profile-password__inputs { flex-wrap: wrap; }
    .profile-password__inputs > .input { flex: 1 1 100%; }
}

/* =============================================
   Survey Feed admin screen
   ─────────────────────────────────────────────────────────────
   Header blurb sits in the actions slot so the page header still uses
   .dashboard-header chrome but doesn't need a button on the right. A
   tight KPI strip above the filters surfaces the counts the admin
   should react to (errored / stuck) with a tinted "alert" tile so a
   bad number isn't lost in the row.
   ============================================= */
.surveyfeed-blurb {
    margin: 0;
    font-size: 12.5px;
    color: var(--color-on-surface-muted);
    line-height: 1.4;
    max-width: 520px;
    text-align: right;
}

/* ----- Auto-mode strip -------------------------------------------------
   Now lives inside the status panel's header row (see
   `.surveyfeed-status__header` below), not as a standalone card.
   It's just an inline cluster: dot · label · pills, all hugging the
   left so the user's eye lands on the control where the label is.
   A coloured dot encodes the current mode at a glance (green/amber/
   red); per-mode descriptions hide in each pill's title= tooltip. */
.surveyfeed-automode {
    display: inline-flex;
    align-items: center;
    gap: 10px;
    min-width: 0;
}
.surveyfeed-automode__dot {
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: var(--color-on-surface-muted);
    flex: 0 0 auto;
    cursor: help;
    /* Soft halo around the dot in its current tone — gives the dot
       presence without making it loud. The colour comes from currentColor
       so each --mode modifier can set it via the `color` property. */
    box-shadow: 0 0 0 4px color-mix(in srgb, currentColor 14%, transparent);
    color: var(--color-on-surface-muted);
}
.surveyfeed-automode__dot--on {
    background: var(--color-success-500);
    color: var(--color-success-500);
}
.surveyfeed-automode__dot--manual_only {
    background: var(--color-warning-500);
    color: var(--color-warning-500);
}
.surveyfeed-automode__dot--off {
    background: var(--color-error-600);
    color: var(--color-error-600);
}
.surveyfeed-automode__title {
    font-size: 13px;
    font-weight: 600;
    color: var(--color-on-surface);
    letter-spacing: -0.005em;
}
.surveyfeed-automode__choices {
    display: inline-flex;
    background: var(--color-neutral-100, #f3f4f6);
    border-radius: 8px;
    padding: 2px;
    gap: 2px;
    flex: 0 0 auto;
}
.surveyfeed-automode__option {
    appearance: none;
    background: transparent;
    border: 0;
    padding: 5px 14px;
    font: inherit;
    font-size: 12px;
    font-weight: 500;
    color: var(--color-on-surface-muted);
    cursor: pointer;
    border-radius: 6px;
    transition: background-color 120ms, color 120ms, box-shadow 120ms;
}
.surveyfeed-automode__option:hover:not(:disabled) {
    color: var(--color-on-surface);
}
.surveyfeed-automode__option--active,
.surveyfeed-automode__option--active:hover {
    background: var(--color-surface);
    color: var(--color-on-surface);
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.08);
}
.surveyfeed-automode__option:focus-visible {
    outline: 2px solid var(--color-primary);
    outline-offset: 2px;
}
.surveyfeed-automode__option:disabled {
    cursor: progress;
    opacity: 0.6;
}

/* ----- Status panel ---------------------------------------------------
   Single unified card with two semantic zones:
     * Pipeline (top)  — 4 calm informational metrics, typography-first,
                         no per-tile chrome, hairline column dividers.
     * Attention (bot) — only non-zero alerts render as tinted pill
                         chips. When all alerts are zero the strip
                         collapses to a quiet "All feeds healthy" check.
   This makes the bottom strip a binary signal: presence of any chip
   means something needs operator attention. */
.surveyfeed-status {
    background: var(--color-surface);
    border: 1px solid var(--color-outline-variant);
    border-radius: 12px;
    margin-bottom: 16px;
    overflow: hidden;
}

/* Header row of the status panel: auto-mode cluster on the left,
   live caption on the right. Sits on a subtle tinted strip so the
   header reads as separate-but-attached to the pipeline below. */
.surveyfeed-status__header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 16px;
    padding: 6px 22px;
    background: color-mix(in srgb, var(--color-on-surface) 1.8%, var(--color-surface));
    border-bottom: 1px solid var(--color-outline-variant);
}
.surveyfeed-status__caption {
    font-size: 12px;
    color: var(--color-on-surface-muted);
    text-align: right;
    flex: 0 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.surveyfeed-status__caption em {
    font-style: normal;
    font-weight: 500;
    color: var(--color-on-surface);
}

.surveyfeed-status__pipeline {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    padding: 22px 4px;
}
/* Two-group layout: Surveys group on the left, Runs group on the
   right, divided by a vertical hairline. Each group has a small
   uppercase label + a row of metric cells. The right group always
   has more metrics (4 vs 3) so the grid template gives it more room. */
.surveyfeed-status__pipeline--grouped {
    display: grid;
    grid-template-columns: minmax(0, 3fr) minmax(0, 4fr);
    padding: 0;
    gap: 0;
}
.surveyfeed-status__group {
    padding: 10px 8px 10px 22px;
}
.surveyfeed-status__group--runs {
    border-left: 1px solid var(--color-outline-variant);
    padding-left: 22px;
}
.surveyfeed-status__group-label {
    font-size: 10.5px;
    font-weight: 600;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--color-on-surface-muted);
    margin-bottom: 6px;
}
.surveyfeed-status__group-row {
    display: grid;
    grid-auto-flow: column;
    grid-auto-columns: minmax(0, 1fr);
    gap: 4px;
}
.surveyfeed-status__metric {
    padding: 0 14px;
    border-right: 1px solid var(--color-outline-variant);
    min-width: 0;
    position: relative;
}
.surveyfeed-status__group-row .surveyfeed-status__metric:last-child { border-right: 0; }
.surveyfeed-status__num {
    font-size: 22px;
    font-weight: 600;
    color: var(--color-on-surface);
    font-variant-numeric: tabular-nums;
    line-height: 1.1;
    letter-spacing: -0.02em;
}
.surveyfeed-status__lab {
    margin-top: 6px;
    font-size: 10.5px;
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--color-on-surface-muted);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
/* Per-metric alert states — used by metrics inside the Runs group when
   their count is non-zero. Subtle accent strip on the left + tinted
   number, same visual language as the run-log popover's line tones. */
.surveyfeed-status__metric--alert::before,
.surveyfeed-status__metric--warn::before {
    content: "";
    position: absolute;
    left: 0;
    top: 4px;
    bottom: 4px;
    width: 3px;
    border-radius: 0 2px 2px 0;
}
.surveyfeed-status__metric--alert::before { background: var(--color-error-600, #b91c1c); }
.surveyfeed-status__metric--warn::before  { background: var(--color-warning-500, #d97706); }
.surveyfeed-status__metric--alert .surveyfeed-status__num { color: var(--color-error-600); }
.surveyfeed-status__metric--warn  .surveyfeed-status__num { color: var(--color-warning-600); }

.surveyfeed-status__attention {
    display: flex;
    align-items: center;
    gap: 14px;
    padding: 10px 22px;
    border-top: 1px solid var(--color-outline-variant);
    background: color-mix(in srgb, var(--color-on-surface) 2.5%, var(--color-surface));
    min-height: 44px;
}
.surveyfeed-status__attention--clear {
    background: color-mix(in srgb, var(--color-success-500) 5%, var(--color-surface));
    border-top-color: color-mix(in srgb, var(--color-success-500) 18%, var(--color-outline-variant));
}
.surveyfeed-status__alabel {
    font-size: 10.5px;
    font-weight: 600;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--color-on-surface-muted);
    flex: 0 0 auto;
}
.surveyfeed-status__chips {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    flex: 1 1 auto;
    min-width: 0;
}
.surveyfeed-status__chip {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 4px 10px 4px 8px;
    font-size: 12px;
    font-weight: 500;
    border-radius: 999px;
    border: 1px solid transparent;
    line-height: 1.2;
}
.surveyfeed-status__chip strong {
    font-weight: 700;
    font-variant-numeric: tabular-nums;
}
.surveyfeed-status__chip-icon {
    width: 13px;
    height: 13px;
    stroke-width: 2;
    flex: 0 0 auto;
}
.surveyfeed-status__chip--error {
    background: var(--color-error-75);
    color: var(--color-error-600);
    border-color: color-mix(in srgb, var(--color-error-600) 20%, transparent);
}
.surveyfeed-status__chip--warn {
    background: var(--color-warning-75);
    color: var(--color-warning-600);
    border-color: color-mix(in srgb, var(--color-warning-600) 20%, transparent);
}

.surveyfeed-status__clear {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 12.5px;
    color: var(--color-success-600);
    font-weight: 500;
}
.surveyfeed-status__clear-icon {
    width: 14px;
    height: 14px;
    stroke-width: 2;
}

@media (max-width: 880px) {
    /* Hide the live caption first — it's nice-to-have. The auto-mode
       cluster keeps its full width without competing for space. */
    .surveyfeed-status__caption { display: none; }
}
@media (max-width: 720px) {
    .surveyfeed-status__pipeline {
        grid-template-columns: repeat(2, minmax(0, 1fr));
        row-gap: 18px;
        padding: 18px 4px;
    }
    .surveyfeed-status__metric:nth-child(2n) { border-right: 0; }
    .surveyfeed-status__header {
        padding: 10px 16px;
    }
    .surveyfeed-automode {
        flex-wrap: wrap;
        gap: 8px;
    }
    .surveyfeed-blurb { text-align: left; max-width: none; }
}

/* ----- Row chrome --------------------------------------------------- */
.surveyfeed-row__pid {
    color: var(--color-primary);
    text-decoration: none;
}
.surveyfeed-row__pid:hover { text-decoration: underline; }

.surveyfeed-row__survey > div:first-child {
    font-weight: 500;
    color: var(--color-on-surface);
}
.surveyfeed-row__path {
    margin-top: 2px;
    font-size: 11.5px;
    color: var(--color-on-surface-muted);
}
.surveyfeed-row__server {
    max-width: 180px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    color: var(--color-on-surface-muted);
}
.surveyfeed-row__time {
    font-size: 12.5px;
    color: var(--color-on-surface);
    cursor: help;       /* tooltip carries the absolute ISO timestamp */
}
.surveyfeed-row__stuck { margin-left: 4px; }

/* Priority chip is a button — same look as a .badge, but clickable and
   focus-visible on keyboard nav. Inherits tone via .badge--{tone}. */
.surveyfeed-priority-toggle {
    border: none;
    cursor: pointer;
    font: inherit;
}
.surveyfeed-priority-toggle:focus-visible {
    outline: 2px solid var(--color-primary);
    outline-offset: 2px;
}
.surveyfeed-priority-toggle[disabled] { opacity: 0.5; cursor: progress; }

/* ----- Run-log modal ---------------------------------------------------
   Pop-out for "View runs" / diagnostic Fetch-now follow-up. Reuses the
   shared .modal / .modal-backdrop chrome (animation, scrim, blur) but
   overrides the inner padding + width so a wide tabular layout fits. */
/* Two-class selector (.modal.surveyfeed-runs__modal) — specificity
   has to beat the generic .modal rule that's declared later in the
   file and sets max-width: 420px. Without bumping specificity the
   modal stays at the small 420px-wide chrome regardless of any
   width override here. */
.modal.surveyfeed-runs__modal {
    /* Fixed at 70% × 70% of the viewport — wide enough to read long
       Decipher error payloads on a single line, tall enough that the
       modal always reads as a substantial overlay rather than
       shrinking to fit the content (which made short logs look
       cut-off / floating). The min() floors keep the modal usable on
       small windows where the percentages would be too cramped.

       Flex column layout: header takes its natural height at the top,
       body claims the remaining space and scrolls internally. That's
       what gives the modal a *constant* size regardless of how many
       diagnostic lines a run produced. */
    width: max(640px, 70vw);
    max-width: calc(100vw - 32px);
    height: max(420px, 70vh);
    max-height: calc(100vh - 32px);
    padding: 0;                 /* the header/body sections own padding */
    display: flex;
    flex-direction: column;
}
.surveyfeed-runs__header {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 12px;
    padding: 18px 22px 14px;
    border-bottom: 1px solid var(--color-outline-variant);
}
.surveyfeed-runs__title {
    margin: 0;
    font-size: 15px;
    font-weight: 600;
    color: var(--color-on-surface);
    letter-spacing: -0.005em;
}
.surveyfeed-runs__sub {
    margin-top: 4px;
    font-size: 12px;
    color: var(--color-on-surface-muted);
}
.surveyfeed-runs__sub code {
    font-family: var(--font-mono, ui-monospace, SFMono-Regular, monospace);
    font-size: 11.5px;
    padding: 1px 5px;
    border-radius: 4px;
    background: var(--color-neutral-100);
    color: var(--color-on-surface);
}
.surveyfeed-runs__close {
    flex: 0 0 auto;
    margin-right: -6px;         /* nudge to align optical edge */
}
.surveyfeed-runs__body {
    /* Claim ALL remaining vertical space after the header. The modal
       is now a fixed-height flex column (see .surveyfeed-runs__modal),
       so the body's height tracks the modal — short content has
       breathing room below, long content scrolls inside this box.
       `min-height: 0` is the standard flex-child unlock so a tall
       inner table doesn't push the body beyond its allotted space. */
    flex: 1 1 0;
    min-height: 0;
    overflow-y: auto;
}
.surveyfeed-runs__loading {
    padding: 36px 22px;
    text-align: center;
    color: var(--color-on-surface-muted);
    font-size: 13px;
}
.surveyfeed-runs__table {
    width: 100%;
    border-collapse: collapse;
    font-size: 12.5px;
}
.surveyfeed-runs__table thead th {
    text-align: left;
    padding: 10px 16px;
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--color-on-surface-muted);
    background: color-mix(in srgb, var(--color-on-surface) 2%, var(--color-surface));
    border-bottom: 1px solid var(--color-outline-variant);
    position: sticky;
    top: 0;
    z-index: 1;
}
.surveyfeed-runs__table th.num,
.surveyfeed-runs__table td.num {
    text-align: right;
    font-variant-numeric: tabular-nums;
}
.surveyfeed-runs__table tbody tr {
    border-bottom: 1px solid var(--color-outline-variant);
}
.surveyfeed-runs__table tbody tr:last-child {
    border-bottom: 0;
}
.surveyfeed-runs__table tbody td {
    padding: 9px 16px;
    color: var(--color-on-surface);
    vertical-align: middle;
}
.surveyfeed-runs__dupes {
    font-size: 11px;
    color: var(--color-on-surface-muted);
    margin-left: 4px;
}
.surveyfeed-runs__err {
    display: inline-block;
    max-width: 280px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    color: var(--color-error-600);
    font-size: 12px;
    cursor: help;
}
.surveyfeed-runs__err code {
    font-family: var(--font-mono, ui-monospace, SFMono-Regular, monospace);
    font-size: 11px;
    padding: 1px 4px;
    border-radius: 3px;
    background: var(--color-error-75);
    margin-right: 4px;
}
/* ----- Expandable run rows + diagnostic lines -------------------------- */
.surveyfeed-runs__caret-col {
    width: 28px;
    padding: 0 !important;
}
.surveyfeed-runs__toggle {
    appearance: none;
    background: transparent;
    border: 0;
    width: 28px;
    height: 28px;
    margin-left: 6px;
    padding: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    color: var(--color-on-surface-muted);
    cursor: pointer;
    border-radius: 6px;
    transition: background-color 120ms, color 120ms;
}
.surveyfeed-runs__toggle:hover {
    background: var(--color-neutral-100);
    color: var(--color-on-surface);
}
.surveyfeed-runs__caret {
    width: 14px;
    height: 14px;
    stroke-width: 2;
    transition: transform 150ms ease;
}
.surveyfeed-runs__row--open .surveyfeed-runs__caret {
    transform: rotate(90deg);
}
.surveyfeed-runs__row--open > td {
    background: color-mix(in srgb, var(--color-on-surface) 2.5%, var(--color-surface));
    border-bottom: 1px solid transparent;          /* visually merge with detail */
}

.surveyfeed-runs__detail-row > td {
    padding: 0 !important;
    background: color-mix(in srgb, var(--color-on-surface) 2%, var(--color-surface));
    border-bottom: 1px solid var(--color-outline-variant);
}
.surveyfeed-runs__detail-row:not(.surveyfeed-runs__detail-row--open) {
    display: none;
}
.surveyfeed-runs__detail-head {
    padding: 6px 16px 4px 50px;
    font-size: 10.5px;
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--color-on-surface-muted);
}
.surveyfeed-runs__lines {
    padding: 4px 16px 12px 50px;
    max-height: 280px;
    overflow-y: auto;
}
.surveyfeed-runs__lines-empty {
    padding: 10px 16px 14px 50px;
    font-size: 12px;
    color: var(--color-on-surface-muted);
    font-style: italic;
}
.surveyfeed-runs__line {
    display: grid;
    /* Time column shrunk to fit just HH:MM:SS — date is implicit
       (every line falls within seconds of the run start, and the run
       row already carries the full date). Saves ~50px for the message. */
    grid-template-columns: 78px 1fr;
    gap: 12px;
    padding: 3px 0;
    font-family: var(--font-mono, ui-monospace, SFMono-Regular, monospace);
    font-size: 11.5px;
    line-height: 1.5;
    border-left: 2px solid transparent;
    padding-left: 8px;
    margin-left: -8px;
}
.surveyfeed-runs__line--warn {
    border-left-color: var(--color-warning-500);
    background: color-mix(in srgb, var(--color-warning-500) 4%, transparent);
}
.surveyfeed-runs__line--error {
    border-left-color: var(--color-error-600);
    background: color-mix(in srgb, var(--color-error-600) 5%, transparent);
}
.surveyfeed-runs__line-time {
    color: var(--color-on-surface-muted);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.surveyfeed-runs__line-msg {
    color: var(--color-on-surface);
    white-space: pre-wrap;
    word-break: break-word;
}
.surveyfeed-runs__line--warn  .surveyfeed-runs__line-msg { color: var(--color-warning-600); }
.surveyfeed-runs__line--error .surveyfeed-runs__line-msg { color: var(--color-error-600); }

/* Batch column inline cell — entire cell in Geist Mono so the comma
   separator and the +N chip blend with the mono ids. The Project
   column stays mixed (mono ids + sans-serif separator) since that
   column has a different per-cell character — usually one or two
   ids, often without a +N chip. */
.recon-log-runs__batch-cell {
    font-family: var(--font-mono, ui-monospace, SFMono-Regular, monospace);
    font-size: 12px;
    letter-spacing: 0.01em;
    white-space: nowrap;
}

/* ----- Id-chip grids (project / batch detail blocks) ----------------
   Used inside the expand-on-chevron detail row. Each chip is a small
   id-mono cell; the strip wraps and caps height at ~3 rows with
   scroll so a run that touched dozens of batches stays bounded. */
.surveyfeed-runs__chips {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    padding: 4px 16px 12px 50px;
    max-height: 110px;
    overflow-y: auto;
}
.surveyfeed-runs__chip {
    display: inline-flex;
    align-items: center;
    padding: 2px 8px;
    border: 1px solid var(--color-outline-variant);
    border-radius: 4px;
    background: var(--color-neutral-25);
    font-size: 11.5px;
    line-height: 1.4;
    color: var(--color-on-surface);
    text-decoration: none;
    transition: background 80ms ease;
}
.surveyfeed-runs__chip:hover {
    background: var(--color-neutral-100);
    color: var(--color-on-surface);
}

/* ``+N`` chip inline in the Project / Batch table cell. Reads as a
   quiet footnote-style affordance — no box, dotted underline,
   tabular-nums for the digits. Hover / focus reveals a small popover
   listing every remaining id (one per line, mono font). Click still
   expands the row's chevron-detail panel for touch users where
   hover isn't available. */
.surveyfeed-runs__more {
    appearance: none;
    border: 0;
    background: transparent;
    padding: 0 2px;
    margin-left: 4px;
    font-family: inherit;
    font-size: 11.5px;
    line-height: 1.4;
    color: var(--color-on-surface-muted);
    cursor: pointer;
    text-decoration: underline dotted;
    text-decoration-thickness: 1px;
    text-underline-offset: 3px;
    font-variant-numeric: tabular-nums;
    position: relative;
    transition: color 80ms ease;
}
.surveyfeed-runs__more:hover,
.surveyfeed-runs__more:focus-visible {
    color: var(--color-on-surface);
}
.surveyfeed-runs__more:focus-visible {
    outline: 2px solid var(--color-primary-400);
    outline-offset: 1px;
    border-radius: 2px;
}

/* Hover popover — pure CSS via ``::after`` + ``::before``. Content is
   pulled from ``data-tooltip`` (newline-separated by the template);
   ``white-space: pre-line`` renders the line breaks so each id sits
   on its own row. Centered above the chip with a small arrow. */
.surveyfeed-runs__more::after {
    content: attr(data-tooltip);
    position: absolute;
    bottom: calc(100% + 8px);
    left: 50%;
    transform: translateX(-50%) translateY(2px);
    background: var(--color-neutral-900);
    color: var(--color-surface);
    padding: 6px 10px;
    border-radius: 4px;
    font-family: var(--font-mono, ui-monospace, SFMono-Regular, monospace);
    font-size: 11.5px;
    line-height: 1.55;
    white-space: pre-line;
    text-align: left;
    width: max-content;
    max-width: 320px;
    opacity: 0;
    pointer-events: none;
    transition: opacity 100ms ease, transform 100ms ease;
    z-index: 50;
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.18);
}
.surveyfeed-runs__more::before {
    content: '';
    position: absolute;
    bottom: calc(100% + 3px);
    left: 50%;
    transform: translateX(-50%);
    border: 5px solid transparent;
    border-top-color: var(--color-neutral-900);
    opacity: 0;
    pointer-events: none;
    transition: opacity 100ms ease;
    z-index: 50;
}
.surveyfeed-runs__more:hover::after,
.surveyfeed-runs__more:focus-visible::after {
    opacity: 1;
    transform: translateX(-50%) translateY(0);
}
.surveyfeed-runs__more:hover::before,
.surveyfeed-runs__more:focus-visible::before {
    opacity: 1;
}

.surveyfeed-runs__empty {
    padding: 36px 22px;
    text-align: center;
    color: var(--color-on-surface-muted);
    font-size: 13px;
}
.surveyfeed-runs__empty-icon {
    width: 28px;
    height: 28px;
    color: var(--color-on-surface-muted);
    margin-bottom: 8px;
    stroke-width: 1.5;
}
.surveyfeed-runs__empty-sub {
    margin-top: 6px;
    font-size: 12px;
}

/* Empty-state row: friendly explanation so a blank table doesn't read
   as a broken page. */
.surveyfeed-empty {
    padding: 36px 16px;
    text-align: center;
    color: var(--color-on-surface-muted);
}
.surveyfeed-empty__title {
    font-size: 14px;
    font-weight: 500;
    color: var(--color-on-surface);
}
.surveyfeed-empty__sub {
    margin-top: 4px;
    font-size: 12.5px;
}

/* =============================================
   AI Engine screen
   ─────────────────────────────────────────────────────────────
   Two tabs share the .settings-tabbar chrome:
     * Dashboard — KPI placeholders + a centred "no engine activity yet"
       card so the page reads as intentional, not broken
     * Prompts   — a list of textarea rows (strictness + per-client),
       each with a per-row Save/Cancel footer that only appears once
       the textarea is dirty
   ============================================= */

/* ----- Dashboard placeholders -------------------------------------- */
.ai-dashboard-kpis {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: 10px;
    margin-bottom: 16px;
}
.ai-dashboard-kpi {
    padding: 14px 16px;
    border: 1px solid var(--color-outline-variant);
    border-radius: 8px;
    background: var(--color-surface);
}
.ai-dashboard-kpi__label {
    font-size: 11px;
    font-weight: 500;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--color-on-surface-muted);
}
.ai-dashboard-kpi__value {
    margin-top: 6px;
    font-size: 20px;
    font-weight: 600;
    color: var(--color-on-surface);
    font-variant-numeric: tabular-nums;
}
.ai-dashboard-kpi__value--mute { color: var(--color-on-surface-muted); }

.ai-dashboard-empty {
    padding: 48px 24px;
    text-align: center;
}
.ai-dashboard-empty__icon {
    width: 36px;
    height: 36px;
    color: var(--color-on-surface-muted);
    margin: 0 auto 12px;
    display: block;
}
.ai-dashboard-empty__title {
    font-size: 15px;
    font-weight: 600;
    color: var(--color-on-surface);
}
.ai-dashboard-empty__sub {
    margin-top: 4px;
    font-size: 13px;
    color: var(--color-on-surface-muted);
    max-width: 460px;
    margin-left: auto;
    margin-right: auto;
    line-height: 1.5;
}

@media (max-width: 960px) {
    .ai-dashboard-kpis { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* ----- Prompts: two-column layout ----------------------------------- */
.ai-prompts-grid {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: 16px;
    align-items: start;
}
@media (max-width: 1080px) {
    .ai-prompts-grid { grid-template-columns: minmax(0, 1fr); }
}

.ai-prompts-card {
    padding: 0;
    margin-bottom: 0;     /* the grid gap handles spacing between cards */
}
.ai-prompts-card > .settings-section {
    padding: 20px 22px;
}
.ai-prompts-card > .settings-section + .settings-section {
    border-top: 1px solid var(--color-outline-variant);
}

/* Picker (used on both columns) — dropdown drives one shared editor. */
.ai-prompts-picker { display: flex; flex-direction: column; gap: 10px; }
.ai-prompts-picker__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
}
.ai-prompts-picker__head .field-label { margin-bottom: 0; }
.ai-prompts-picker__select { width: 100%; }
.ai-prompts-picker__textarea { min-height: 200px; }
/* Inline hint line rendered between the dropdown and the textarea.
   Updates live as the dropdown's selection changes (strictness side
   uses it to explain each level; client side leaves it empty). */
.ai-prompts-picker__hint {
    font-size: 12.5px;
    color: var(--color-on-surface-muted);
    line-height: 1.45;
    min-height: 1.45em;   /* prevent layout jump when empty */
}
.ai-prompts-list {
    list-style: none;
    padding: 0;
    margin: 0;
    display: flex;
    flex-direction: column;
    gap: 14px;
}
.ai-prompts-item {
    padding: 14px 16px;
    border: 1px solid var(--color-outline-variant);
    border-radius: 10px;
    background: var(--color-surface);
}
.ai-prompts-item__head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 12px;
    margin-bottom: 10px;
}
.ai-prompts-item__title {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
    min-width: 0;
}
.ai-prompts-item__hint {
    font-size: 12.5px;
    color: var(--color-on-surface-muted);
}
.ai-prompts-item__client {
    font-size: 14px;
    font-weight: 600;
    color: var(--color-on-surface);
}
.ai-prompts-item__status { flex: 0 0 auto; }
.ai-prompts-item__textarea {
    width: 100%;
    font-family: var(--font-mono);
    font-size: 12.5px;
    line-height: 1.5;
    resize: vertical;
    min-height: 96px;
}
.ai-prompts-item__footer {
    margin-top: 10px;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
}
.ai-prompts-item__meta {
    font-size: 11.5px;
    color: var(--color-on-surface-muted);
    font-variant-numeric: tabular-nums;
}
.ai-prompts-item__actions {
    display: flex;
    align-items: center;
    gap: 6px;
}
.ai-prompts-item__actions > .btn {
    padding: 5px 12px;
    font-size: 12.5px;
}
.ai-prompts-item__actions > .btn > svg {
    width: 13px;
    height: 13px;
}

.ai-prompts-empty {
    padding: 28px 16px;
    text-align: center;
    color: var(--color-on-surface-muted);
}
.ai-prompts-empty__title {
    font-size: 14px;
    font-weight: 500;
    color: var(--color-on-surface);
}
.ai-prompts-empty__sub {
    margin-top: 4px;
    font-size: 12.5px;
}
.ai-prompts-empty a { color: var(--color-primary); }

/* Base Prompt tab — single-card layout. Wider editor area than the
   strictness picker since this is the long-form template the engine
   uses on every run. */
.ai-base-prompt-card { max-width: 920px; }
.ai-base-prompt .ai-prompts-picker__hint code {
    font-family: var(--font-mono);
    font-size: 12px;
    background: var(--rule-soft, #F1F2F4);
    padding: 1px 6px;
    border-radius: 3px;
}

/* ----- Version history picker ---------------------------------------
   Dropdown of all saved versions of a prompt slot + action buttons.
   Layout: head (label + hint), then a row with the <select> stretching
   to fill space and the action buttons hugging the right edge. A small
   meta line below tells the operator which version they're sitting on
   and whether it's already the engine's active one. */
.ai-version-history {
    margin-top: 18px;
    padding-top: 12px;
    border-top: 1px solid var(--color-rule, #E5E7EB);
}
.ai-version-history__head {
    margin-bottom: 6px;
}
.ai-version-history__head > .field-label {
    margin-bottom: 0;
}
.ai-version-history__title {
    font-size: 13px;
    font-weight: 600;
    color: var(--color-on-surface);
}
.ai-version-history__hint {
    margin-top: 4px;
    font-size: 12px;
    color: var(--color-on-surface-muted);
    line-height: 1.45;
}
.ai-version-history__picker {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
}
.ai-version-history__select {
    flex: 1 1 240px;
    min-width: 0;
    font-family: var(--font-mono);
    font-size: 12px;
}
.ai-version-history__select:disabled {
    color: var(--color-on-surface-muted);
}
.ai-version-history__actions {
    display: flex;
    align-items: center;
    gap: 6px;
    flex: 0 0 auto;
}
.ai-version-history__actions > .btn {
    padding: 5px 12px;
    font-size: 12.5px;
}
.ai-version-history__meta {
    margin-top: 6px;
    font-size: 11.5px;
    color: var(--color-on-surface-muted);
    line-height: 1.45;
    font-variant-numeric: tabular-nums;
    min-height: 1.45em;     /* prevent layout jump when empty */
}

/* Inline variant (used inside strictness / client picker panels) —
   a touch tighter than the standalone version on the Base Prompt
   tab so it fits comfortably under the smaller textarea. */
.ai-version-history--inline {
    margin-top: 12px;
    padding-top: 10px;
}

/* Top variant (used on the Base Prompt tab) — sits above the editor
   as a header-style picker, so no border-top / top-margin. Layout:
   "Version: [trigger] [Make active]   [Configured]". The trigger
   is a compact, fixed-ish width so the row reads cleanly without
   the dropdown swallowing all the horizontal space. */
.ai-version-history--top {
    margin-top: 0;
    padding-top: 0;
    border-top: 0;
    margin-bottom: 14px;
}
.ai-version-history__label {
    margin-bottom: 0;
    flex: 0 0 auto;
}
/* Custom dropdown trigger — compact width so the row stays tidy.
   The select__label inside the trigger truncates instead of pushing
   the chevron off-screen, important for the long auto-seeded label. */
.ai-version-history__dropdown {
    flex: 0 1 360px;
    min-width: 0;
}

/* Multi-slot variant (Strictness + Client tabs) — two pickers side
   by side instead of the single-picker Base Prompt layout. Stack the
   label ABOVE each control so the controls themselves get more
   horizontal room; align everything to the bottom so the "Make active"
   button and the Configured pill sit flush with the dropdown triggers
   (and below the labels). */
.ai-multi-slot-prompt .ai-version-history__picker {
    align-items: flex-end;
    gap: 12px;
    flex-wrap: wrap;
}
.ai-version-history__field {
    display: flex;
    flex-direction: column;
    gap: 4px;
    flex: 1 1 220px;
    min-width: 0;
}
.ai-version-history__field > .field-label {
    margin-bottom: 0;
    white-space: nowrap;
}
.ai-multi-slot-prompt .ai-version-history__dropdown {
    flex: 1 1 auto;
    width: 100%;
    max-width: none;
}
.ai-version-history__trigger {
    height: 36px;
    padding: 0 12px;
    font-size: 13px;
}
.ai-version-history__trigger .select__label {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    text-align: left;
    color: var(--color-on-surface);
}

/* ----- Generic dropdown (button trigger + popover menu) -----------
   Lightweight value-picker built on top of the existing .select
   button style. Used here by the Base Prompt version picker; could
   be lifted into a shared component later if more screens want it. */
.dropdown {
    position: relative;
    display: inline-block;
}
.dropdown__menu {
    position: absolute;
    top: calc(100% + 4px);
    left: 0;
    right: 0;
    min-width: 100%;
    max-height: 280px;
    overflow-y: auto;
    z-index: 100;
    padding: 4px;
    background: var(--color-surface);
    border: 1px solid var(--color-outline-variant);
    border-radius: var(--radius-control);
    box-shadow: 0 10px 28px var(--color-shadow), 0 4px 8px var(--color-shadow);
}
.dropdown__item {
    display: block;
    width: 100%;
    padding: 7px 10px;
    border: 0;
    border-radius: 4px;
    background: transparent;
    text-align: left;
    font-family: inherit;
    font-size: 12.5px;
    line-height: 1.45;
    color: var(--color-on-surface);
    cursor: pointer;
    white-space: nowrap;
}
.dropdown__item + .dropdown__item {
    margin-top: 2px;
}
.dropdown__item:hover {
    background: var(--color-neutral-100);
}
.dropdown__item[data-active] {
    font-weight: 600;
    background: color-mix(in srgb, var(--color-primary-500) 8%, transparent);
    color: var(--color-on-surface);
}

@media (max-width: 540px) {
    .ai-prompts-item__head { flex-direction: column; }
    .ai-prompts-item__status { align-self: flex-start; }
}

/* Toggle row: copy on the left, single .toggle on the right, separated by a
   thin top border so each toggle reads as its own setting. */
.settings-toggle-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 16px;
    padding: 16px 0;
    border-top: 1px solid var(--color-outline-variant);
}
.settings-toggle-row__copy { min-width: 0; max-width: 70%; }
.settings-toggle-row__title {
    font-size: 14px;
    font-weight: 600;
    color: var(--color-on-surface);
    line-height: 1.4;
}
.settings-toggle-row__blurb {
    margin: 4px 0 0 0;
    font-size: 13px;
    color: var(--color-on-surface-muted);
    line-height: 1.4;
}

/* Empty-state for tabs that don't have content wired up yet. */
.settings-card--placeholder {
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 280px;
}
.settings-placeholder {
    margin: 0;
    color: var(--color-on-surface-muted);
    font-size: 14px;
}


/* =============================================
   Toasts — auto-dismissing notifications shown top-center via
   window.toast() in app.js.

   Subtle card style (Sonner-inspired): plain surface background, hairline
   neutral border, soft shadow. The only colored bit is a small variant-
   tinted icon on the left — no tinted background, no colored tile.
   ============================================= */
.toast-container {
    position: fixed;
    top: 20px;
    left: 50%;
    transform: translateX(-50%);
    z-index: 9999;
    display: flex;
    flex-direction: column;
    gap: 8px;
    pointer-events: none;            /* clicks pass through gaps */
    max-width: 420px;
    width: max-content;
}
.toast {
    pointer-events: auto;
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 10px 12px;
    background: var(--color-surface);
    color: var(--color-on-surface);
    border: 1px solid var(--color-outline-variant);
    border-radius: 10px;
    box-shadow: 0 6px 16px rgba(0, 0, 0, 0.05), 0 1px 2px rgba(0, 0, 0, 0.04);
    font-size: 13px;
    font-weight: 500;
    line-height: 1.4;
    opacity: 0;
    transform: translateY(-6px);
    transition: opacity 180ms ease, transform 180ms ease;
}
.toast__icon {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}
.toast__icon > svg {
    width: 16px;
    height: 16px;
    stroke-width: 2;
}
.toast__message {
    flex: 1;
    min-width: 0;
    padding-right: 4px;
}
.toast__close {
    flex: 0 0 auto;
    width: 22px;
    height: 22px;
    padding: 0;
    background: transparent;
    border: none;
    border-radius: 5px;
    cursor: pointer;
    color: var(--color-on-surface-muted);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    transition: background-color 120ms, color 120ms;
}
.toast__close:hover {
    background: var(--color-neutral-100);
    color: var(--color-on-surface);
}
.toast__close > svg {
    width: 12px;
    height: 12px;
    stroke-width: 2;
}
.toast--in  { opacity: 1; transform: translateY(0); }
.toast--out { opacity: 0; transform: translateY(-6px); }

/* ---- Variants: tint only the icon, leave the card neutral --------------- */
.toast--success .toast__icon { color: var(--color-success-600); }
.toast--error   .toast__icon { color: var(--color-error-600);   }
.toast--warning .toast__icon { color: var(--color-warning-600); }
.toast--info    .toast__icon { color: var(--color-info-600);    }


/* =============================================
   Modal dialogs — created by window.confirmDialog() in app.js.
   Centered card on a dimmed backdrop. ESC to cancel, Enter to confirm.
   ============================================= */
.modal-backdrop {
    position: fixed;
    inset: 0;
    z-index: 10000;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 16px;
    background: rgba(15, 23, 42, 0.45);
    backdrop-filter: blur(2px);
    opacity: 0;
    pointer-events: none;              /* don't block clicks while invisible */
    transition: opacity 160ms ease;
}
.modal-backdrop--in  { opacity: 1; pointer-events: auto; }
.modal-backdrop--out { opacity: 0; }

.modal {
    width: 100%;
    max-width: 420px;
    background: var(--color-surface);
    border-radius: 14px;
    box-shadow: 0 20px 50px rgba(0, 0, 0, 0.25), 0 4px 12px rgba(0, 0, 0, 0.08);
    padding: 22px;
    transform: scale(0.96);
    transition: transform 160ms ease;
}
.modal-backdrop--in .modal { transform: scale(1); }

.modal__title {
    /* Matches .settings-section__title — single "section" type heading
       used across the app's form contexts. */
    margin: 0 0 6px 0;
    font-size: 15px;
    font-weight: 600;
    color: var(--color-on-surface);
    line-height: 1.4;
}
.modal__message {
    /* Matches the "blurb" type used by settings / page headers
       (.settings-section__blurb, .settings-header__blurb). */
    margin: 0 0 20px 0;
    font-size: 13px;
    color: var(--color-on-surface-muted);
    line-height: 1.5;
}
/* Call-out below the message body — used for the "do this next" line
   so it reads as an actionable instruction rather than continuation
   of the explanation. Slight tonal surface + accent border + on-surface
   text colour pulls it forward without competing with the title. */
.modal__instruction {
    margin: 0 0 20px 0;
    padding: 10px 12px;
    font-size: 13px;
    font-weight: 400;
    color: var(--color-on-surface);
    background: color-mix(in srgb, var(--color-primary-500) 6%, var(--color-surface));
    border-radius: 6px;
    line-height: 1.45;
}
/* Breakdown list rendered inside the reconcile-confirm dialog (and
   any future confirm dialog that wants the same "here's what's about
   to happen" shape). Bulleted, compact, leans on tabular numerals
   for the leading count so the rows feel tidy. */
.modal__list {
    margin: 0 0 20px 0;
    padding: 12px 16px 12px 32px;
    background: var(--color-neutral-50);
    border: 1px solid var(--color-outline-variant);
    border-radius: 6px;
    font-size: 13px;
    color: var(--color-on-surface);
    list-style: disc outside;
}
.modal__list li {
    line-height: 1.6;
}
.modal__list strong {
    font-variant-numeric: tabular-nums;
    color: var(--color-on-surface);
}

/* Reconcile + fetch flows share the same wider modal — the running
   and done panes carry stats / log tickers that benefit from the
   breathing room. The breakpoint cap stops the dialog from looking
   lonely on a wide screen. */
.modal--reconcile,
.modal--fetch {
    width: min(717px, calc(100vw - 32px));
    max-width: min(717px, calc(100vw - 32px));
}
/* Warn-toned bullet for "last run errored" / "already running" lines
   inside the preview pane's .modal__list. */
.modal__list-warn {
    color: var(--color-warning-700, var(--color-on-surface));
}

/* Headline row on the running pane — spinner glyph on the left, the
   short status label + scope under it on the right. */
.recon-progress {
    display: flex;
    align-items: center;
    gap: 14px;
    padding: 14px 16px;
    margin: 0 0 16px 0;
    background: var(--color-primary-50);
    border: 1px solid var(--color-primary-100);
    border-radius: 8px;
}
/* Initial loading pane (shown while a run-flow modal awaits its preview).
   Centered + a stable min-height so the modal opens at a sensible size
   instead of a thin strip, then resizes once to fit the real content. */
.recon-progress--loading {
    justify-content: center;
    min-height: 120px;
    margin: 0;
}
.recon-progress__spinner {
    flex: 0 0 auto;
    width: 28px;
    height: 28px;
    color: var(--color-primary-600);
    animation: review-loading-spin 0.9s linear infinite;
    fill: none;
    stroke: currentColor;
    stroke-width: 2;
}
.recon-progress__body {
    flex: 1 1 auto;
    min-width: 0;
}
.recon-progress__headline {
    font-size: 14px;
    font-weight: 600;
    color: var(--color-on-surface);
}
.recon-progress__sub {
    font-size: 12px;
    color: var(--color-on-surface-muted);
    margin-top: 2px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Activity ticker — small monospace log inside the modal. Bounded
   height + own scroll so a long run can't push the modal off-screen. */
.recon-log {
    margin: 0 0 16px 0;
    padding: 8px 10px;
    height: 180px;
    overflow: auto;
    background: var(--color-neutral-50);
    border: 1px solid var(--color-outline-variant);
    border-radius: 6px;
    font-family: var(--font-mono);
    font-size: 12px;
    color: var(--color-on-surface);
}
.recon-log--final {
    height: 140px;       /* slightly shorter on the done pane to make
                            room for the stat strip + close button */
}
.recon-log__empty {
    padding: 18px 4px;
    text-align: center;
    color: var(--color-on-surface-muted);
}
.recon-log__line {
    display: grid;
    grid-template-columns: 70px 1fr;
    gap: 10px;
    padding: 2px 2px;
    line-height: 1.5;
}
.recon-log__ts {
    color: var(--color-on-surface-muted);
    font-variant-numeric: tabular-nums;
}
.recon-log__msg { color: var(--color-on-surface); }
.recon-log__line--warn  { background: var(--color-warning-50); }
.recon-log__line--warn  .recon-log__msg { color: var(--color-warning-700); }
.recon-log__line--error { background: var(--color-error-50); }
.recon-log__line--error .recon-log__msg { color: var(--color-error-700); }

/* Counter strip on the done pane — Rows / Pushed / In sync / Errors. */
.recon-stats {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: 8px;
    margin: 0 0 16px 0;
}
.recon-stats__cell {
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding: 8px 10px;
    background: var(--color-surface);
    border: 1px solid var(--color-outline-variant);
    border-radius: 6px;
    text-align: center;
}
.recon-stats__label {
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--color-on-surface-muted);
}
.recon-stats__value {
    font-size: 18px;
    font-weight: 600;
    color: var(--color-on-surface);
    font-variant-numeric: tabular-nums;
}
/* Text variant — same size + weight as the numeric default so a
   "Success" / "Error" cell stays at the same visual level as the
   "1,234" cell sitting next to it. The tabular-nums opt-out keeps
   word kerning natural. */
.recon-stats__value--text {
    font-variant-numeric: normal;
}
.recon-stats__cell--muted   .recon-stats__value { color: var(--color-on-surface-muted); }
.recon-stats__cell--success {
    background: var(--color-success-50, #ecfdf5);
    border-color: var(--color-success-75, #d1fae5);
}
.recon-stats__cell--success .recon-stats__value { color: var(--color-success-700); }
.recon-stats__cell--error {
    background: var(--color-error-50);
    border-color: var(--color-error-100);
}
.recon-stats__cell--error .recon-stats__value { color: var(--color-error-700); }

/* Error message banner on the done pane — only shown when the run
   carried at least one Decipher failure. */
.recon-error {
    margin: 0 0 16px 0;
    padding: 10px 12px;
    background: var(--color-error-50);
    border: 1px solid var(--color-error-100);
    border-radius: 6px;
    font-size: 12px;
    color: var(--color-error-700);
}

/* Project-level error block on the reconcile done pane. One row per
   (project, action) bucket that failed — calls out the fix-once
   issues (no API key, project not configured, auth) prominently so
   the operator doesn't have to grep the per-record log to know what
   to fix. */
.recon-bucket-error {
    margin: 0 0 16px 0;
    padding: 10px 12px;
    background: var(--color-error-50);
    border: 1px solid var(--color-error-100);
    border-radius: 6px;
}
.recon-bucket-error__title {
    font-size: 12px;
    font-weight: 600;
    color: var(--color-error-700);
    margin-bottom: 6px;
}
.recon-bucket-error__list {
    list-style: none;
    margin: 0;
    padding: 0;
}
.recon-bucket-error__item {
    display: grid;
    grid-template-columns: auto auto 1fr;
    gap: 10px;
    align-items: baseline;
    padding: 4px 0;
    font-size: 12px;
    border-top: 1px solid var(--color-error-100);
}
.recon-bucket-error__item:first-child { border-top: 0; }
.recon-bucket-error__project {
    font-family: var(--font-mono, ui-monospace, monospace);
    font-weight: 600;
    color: var(--color-on-surface);
}
.recon-bucket-error__meta {
    color: var(--color-on-surface-muted);
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}
.recon-bucket-error__message {
    color: var(--color-error-700);
}

/* Detail popup (opened by "View log") — per-record outcomes table
   plus the original activity log, stacked. Lives inside the same
   .modal--reconcile width so it shares the layout. */
.recon-detail__section-title {
    margin: 18px 0 8px 0;
    font-size: 12px;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--color-on-surface-muted);
    font-weight: 600;
}
.recon-detail__table-wrap {
    max-height: 260px;
    overflow: auto;
    border: 1px solid var(--color-outline-variant);
    border-radius: 6px;
}
.recon-detail__table {
    width: 100%;
    margin: 0;
    border-collapse: collapse;
}
.recon-detail__table th,
.recon-detail__table td {
    padding: 6px 10px;
    font-size: 12px;
}
.recon-detail__msg {
    color: var(--color-on-surface-muted);
    font-size: 11px;
}

/* Secondary backdrop — used when one modal is opened on top of
   another (reconcile done pane → "View log" detail popup). Slightly
   lighter scrim + a higher z-index than the base modal so the
   second dialog sits above the one beneath. */
.modal-backdrop--secondary {
    background: rgba(15, 23, 42, 0.25);
    z-index: 10500;
}

.modal__field {
    margin-bottom: 20px;
}
.modal__label {
    /* Matches .settings-field .field-label — same sentence-case label
       convention used by every form context in the app. */
    display: block;
    margin-bottom: 6px;
    font-size: 13px;
    font-weight: 500;
    color: var(--color-on-surface);
}
/* Row variant — label on the left, control on the right (used by the
   Edit User dialog's "Admin user" toggle). */
.modal__field--row {
    display: flex;
    align-items: center;
    justify-content: space-between;
}
.modal__field--row .modal__label { margin-bottom: 0; }
.modal__actions {
    display: flex;
    justify-content: flex-end;
    gap: 8px;
}

/* ---- Form-modal variant ------------------------------------------------- */
/* A richer layout used by formDialog(): tonal icon tile + title + subtitle
   header, fields with optional right-side hint, in-input action pill, and
   helper text under the input. */

.modal--form {
    max-width: 480px;
    padding: 0;                                   /* sections own their padding */
}
.modal__header {
    display: flex;
    align-items: flex-start;
    gap: 14px;
    padding: 22px 22px 18px;
    border-bottom: 1px solid var(--color-outline-variant);
}
.modal__head-icon {
    flex: 0 0 auto;
    width: 40px;
    height: 40px;
    border-radius: 10px;
    background: var(--color-primary-75);
    color: var(--color-primary-600);
    display: inline-flex;
    align-items: center;
    justify-content: center;
}
.modal__head-icon > svg {
    width: 20px;
    height: 20px;
    stroke-width: 1.75;
}
.modal__head-text {
    flex: 1;
    min-width: 0;
}
.modal--form .modal__title {
    margin: 0 0 4px 0;
}
.modal--form .modal__message {
    margin: 0;
}
.modal__close {
    flex: 0 0 auto;
    width: 28px;
    height: 28px;
    margin-top: -4px;
    padding: 0;
    background: transparent;
    border: none;
    border-radius: 6px;
    cursor: pointer;
    color: var(--color-on-surface-muted);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    transition: background-color 120ms, color 120ms;
}
.modal__close:hover {
    background: var(--color-neutral-100);
    color: var(--color-on-surface);
}
.modal__close > svg {
    width: 16px;
    height: 16px;
    stroke-width: 2;
}

/* Body (fields) — only direct .modal__field children get the side padding */
.modal--form > .modal__field { margin: 18px 22px; }

/* Two-column field grid inside a form modal — same idiom as .settings-grid.
   Use .modal__field--full for fields that should span both columns. */
.modal__grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 16px 18px;
    padding: 18px 22px;
}
.modal__grid > .modal__field { margin: 0; }
.modal__field--full { grid-column: 1 / -1; }

.modal__field-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 8px;
}
.modal__field-head .modal__label { margin-bottom: 0; }
.modal__hint {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-size: 12px;
    color: var(--color-on-surface-muted);
}
/* Inline hint right next to the label text (rather than aligned to the
   right side of the field). Used by the batches Assign dialog to show
   the active-reviewer count beside the "Reviewer" label. */
.modal__hint--inline {
    font-weight: 400;
    font-size: 12px;
    color: var(--color-on-surface-muted);
    margin-left: 6px;
}
.modal__hint > svg {
    width: 13px;
    height: 13px;
    stroke-width: 1.75;
}

/* Sign-in-method radio group used by the Add User modal. Two side-by-side
   radios with secondary "(set initial password)" / "(no password stored)"
   hint text in muted colour. */
.modal__radio-group {
    display: flex;
    align-items: center;
    gap: 18px;
    flex-wrap: wrap;
}
.modal__radio {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-size: 13px;
    cursor: pointer;
    color: var(--color-on-surface);
}
.modal__radio > input { margin: 0; }
.modal__radio-hint {
    color: var(--color-on-surface-muted);
    font-size: 12px;
}

/* Inline error chip rendered at the bottom of a modal form when the
   server returns a 400 (e.g. duplicate email on Add User). Shares the
   tinted-error treatment used elsewhere; hidden by default via the
   ``hidden`` attribute on the element itself. */
.modal__error {
    margin: 0 16px 12px;
    padding: 8px 12px;
    border-radius: 6px;
    background: var(--color-error-bg, rgba(220, 38, 38, 0.08));
    color: var(--color-error, #b91c1c);
    font-size: 12.5px;
    line-height: 1.4;
}

.modal__input-wrap {
    position: relative;
    display: flex;
    align-items: center;
}
.modal__input-wrap > .input { flex: 1; }
.input--mono {
    font-family: "Geist Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 13px;
}
.modal__field-action {
    position: absolute;
    right: 6px;
    top: 50%;
    transform: translateY(-50%);
    display: inline-flex;
    align-items: center;
    gap: 6px;
    height: 26px;
    padding: 0 10px;
    background: transparent;
    border: none;
    border-radius: 6px;
    color: var(--color-on-surface-muted);
    font-family: inherit;
    font-size: 12px;
    font-weight: 500;
    cursor: pointer;
    transition: background-color 120ms, color 120ms;
}
.modal__field-action:hover {
    background: var(--color-neutral-100);
    color: var(--color-on-surface);
}
.modal__field-action > svg {
    width: 13px;
    height: 13px;
    stroke-width: 1.75;
}
.modal--form .input:has(+ .modal__field-action) {
    padding-right: 80px;                          /* room for the pill */
}

.modal__helper {
    margin: 6px 2px 0 2px;
    font-size: 12px;
    color: var(--color-on-surface-muted);
    line-height: 1.5;
}

.modal--form .modal__actions {
    padding: 16px 22px 22px;
    border-top: 1px solid var(--color-outline-variant);
    margin-top: 4px;
}


/* =============================================================
   Client Rejections — paste-and-reconcile admin screen.
   Two-column layout inside a single card: textarea on the left
   (where the operator dumps the client's transaction id list),
   reason input + submit + tip on the right. The result panel
   below shows a stat strip + per-row outcome table.
   ============================================================= */
.dashboard-header__blurb {
    color: var(--color-on-surface-muted);
    font-size: 13px;
    line-height: 1.5;
    margin: 4px 0 0;
    max-width: 720px;
}

/* Inline secondary text under a field label — used to explain the
   line-format hint without stealing visual weight from the label. */
.field-hint {
    display: block;
    font-weight: 400;
    color: var(--color-on-surface-muted);
    font-size: 12px;
    margin-top: 2px;
}
.field-hint code {
    font-family: var(--font-mono);
    font-size: 11px;
    background: var(--color-neutral-100);
    padding: 1px 4px;
    border-radius: 3px;
}

/* =============================================================
   CLIENT REJECTIONS — paste + result components.

   Two render modes: paste-focused (no result) and result-focused
   (drawer hides paste). Components below are scoped to the
   ``cr-*`` namespace.
   ============================================================= */

/* ── Upload card + file dropzone ────────────────────────────── */
.cr-upload-card {
    padding: 20px;
}
/* Two-column layout: drop zone on the left (fills remaining width),
   Default reason + file row + submit CTA on the right at a fixed
   360px. 360px lands the submit button at a comfortable "primary
   action" size — wide enough for the icon + label, narrow enough
   not to dominate the row. The reason input and the file row
   inherit the same 360px column width, so the right column reads
   as a single coherent stack. */
.cr-upload-grid {
    display: flex;
    gap: 24px;
    align-items: start;
}
.cr-upload-grid__left {
    flex: 1 1 0;
    min-width: 0;
}
.cr-upload-grid__right {
    flex: 0 0 360px;
    display: flex;
    flex-direction: column;
    gap: 14px;
}
.cr-upload-grid__submit { width: 100%; }
@media (max-width: 820px) {
    .cr-upload-grid { flex-direction: column; }
    .cr-upload-grid__left,
    .cr-upload-grid__right { flex: 1 1 auto; width: 100%; }
}
/* Drag-drop / click-to-browse zone — dashed border, centered icon,
   short hint. Hover and dragging states are visually distinct so
   the operator gets clear feedback as they drag a file over the
   page. The native <input type="file"> is hidden inside via
   ``opacity: 0`` (still clickable for keyboard users) so the
   chrome stays consistent across browsers. */
.cr-upload-zone {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 8px;
    padding: 50px 24px;
    background: color-mix(in srgb, var(--color-primary-500) 3%, var(--color-surface));
    border: 2px dashed color-mix(in srgb, var(--color-primary-500) 35%, transparent);
    border-radius: 12px;
    text-align: center;
    cursor: pointer;
    transition: background-color 150ms, border-color 150ms;
    outline: none;
}
.cr-upload-zone:hover,
.cr-upload-zone:focus-visible {
    background: color-mix(in srgb, var(--color-primary-500) 6%, var(--color-surface));
    border-color: var(--color-primary-500);
}
.cr-upload-zone--dragging {
    background: color-mix(in srgb, var(--color-primary-500) 10%, var(--color-surface));
    border-color: var(--color-primary-600);
    border-style: solid;
}
.cr-upload-zone__input {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    opacity: 0;
    cursor: pointer;
}
.cr-upload-zone__icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 48px;
    height: 48px;
    border-radius: 50%;
    background: color-mix(in srgb, var(--color-primary-500) 12%, var(--color-surface));
    color: var(--color-primary-600);
    margin-bottom: 4px;
}
.cr-upload-zone__icon > svg {
    width: 22px;
    height: 22px;
    stroke-width: 2;
}
.cr-upload-zone__title {
    font-size: 14px;
    font-weight: 600;
    color: var(--color-on-surface);
}
.cr-upload-zone__hint {
    font-size: 12.5px;
    color: var(--color-on-surface-muted);
}

/* ── File summary row (after a successful parse) ────────────── */
.cr-file-row {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 10px 14px;
    background: var(--color-neutral-50);
    border: 1px solid var(--color-outline-variant);
    border-radius: 8px;
}
.cr-file-row__lead {
    display: inline-flex;
    align-items: center;
    gap: 10px;
    flex: 1 1 auto;
    min-width: 0;
}
.cr-file-row__icon {
    width: 18px;
    height: 18px;
    color: var(--color-primary-600);
    flex: 0 0 auto;
}
.cr-file-row__name {
    font-weight: 500;
    color: var(--color-on-surface);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.cr-file-row__count {
    font-family: var(--font-mono);
    font-size: 11.5px;
    padding: 2px 8px;
    border-radius: 999px;
    background: color-mix(in srgb, var(--color-primary-500) 10%, var(--color-surface));
    color: var(--color-primary-700);
    flex: 0 0 auto;
}
/* File-row icon buttons — view (eye) + remove (x). Share the same
   ghost-icon-button chrome the rest of the file row uses; hover
   tints are different so the destructive action stays distinguishable. */
.cr-file-row__view,
.cr-file-row__remove {
    appearance: none;
    background: transparent;
    border: 0;
    width: 28px;
    height: 28px;
    border-radius: 6px;
    cursor: pointer;
    color: var(--color-on-surface-muted);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
}
.cr-file-row__view:hover   { background: var(--color-neutral-100); color: var(--color-primary-700); }
.cr-file-row__remove:hover { background: var(--color-neutral-100); color: var(--color-error-600); }
.cr-file-row__view > svg,
.cr-file-row__remove > svg { width: 14px; height: 14px; }

/* Preview-modal footer — split into a meta caption on the left
   ("N rows" / "Showing first 100 of N") and a right-aligned action
   row. JS swaps the action buttons between Cancel/Accept (confirm
   mode) and Close (view mode). Inherits the rest of the modal
   chrome from .cr-file-modal* (shared with the history popup). */
.cr-file-modal__foot {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 12px 20px;
    border-top: 1px solid var(--color-outline-variant);
    background: var(--color-neutral-50);
}
.cr-file-modal__foot-meta {
    font-size: 12px;
    color: var(--color-on-surface-muted);
}
.cr-file-modal__foot-actions {
    display: inline-flex;
    gap: 8px;
}

/* ── Default reason group (inside the upload card's right column) ── */
/* These three classes are kept on the markup for the Default reason
   block; only the parent ``.cr-bottom-row`` grid is gone (replaced by
   ``.cr-upload-grid__right``'s flex column). */
.cr-bottom-row__reason { display: flex; flex-direction: column; gap: 6px; }
.cr-bottom-row__label {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 2px;
    font-size: 13px;
    font-weight: 500;
    color: var(--color-on-surface);
}
.cr-bottom-row__hint {
    font-size: 12px;
    font-weight: 400;
    color: var(--color-on-surface-muted);
}

/* ── Result card head ───────────────────────────────────────── */
.cr-result {
    padding: 18px 20px;
    margin-top: 16px;
}
.cr-result__head {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 12px;
    margin-bottom: 14px;
}
.cr-result__title {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    font-size: 15px;
    font-weight: 600;
    color: var(--color-on-surface);
}
.cr-result__title > svg { width: 18px; height: 18px; color: var(--color-success-600); }
.cr-result__file {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    margin-left: auto;
    padding: 4px 10px;
    border-radius: 999px;
    background: var(--color-neutral-50);
    border: 1px solid var(--color-outline-variant);
    font-size: 12.5px;
    color: var(--color-on-surface-muted);
}
.cr-result__file > svg { width: 13px; height: 13px; }

/* ── KPI tiles ──────────────────────────────────────────────── */
/* 4-up grid of equal-weight tiles for the row-level buckets.
   Each tile carries a label, big tabular count, a thin
   share-of-total bar (filled relative to total requested), and a
   one-line hint explaining what the bucket means. Tones tint the
   tile's accent rail + bar fill so the eye picks up problems
   without parsing the label. */
.cr-kpis {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: 12px;
    margin-bottom: 14px;
}
.cr-kpi {
    position: relative;
    display: flex;
    flex-direction: column;
    gap: 8px;
    padding: 16px 16px 14px;
    background: var(--color-surface);
    border: 1px solid var(--color-outline-variant);
    border-radius: 10px;
    overflow: hidden;
}
/* Tonal accent rail on the left edge — width 3px, full height. */
.cr-kpi::before {
    content: "";
    position: absolute;
    left: 0; top: 0; bottom: 0;
    width: 3px;
    background: var(--color-outline-variant);
}
.cr-kpi__label {
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    font-weight: 600;
    color: var(--color-on-surface-muted);
}
.cr-kpi__value {
    font-family: var(--font-mono);
    font-size: 32px;
    font-weight: 600;
    line-height: 1;
    color: var(--color-on-surface);
    font-variant-numeric: tabular-nums;
}
.cr-kpi__bar {
    height: 4px;
    background: var(--color-neutral-100);
    border-radius: 999px;
    overflow: hidden;
}
.cr-kpi__fill {
    height: 100%;
    background: var(--color-neutral-300);
    border-radius: 999px;
    transition: width 200ms ease-out;
}
.cr-kpi__hint {
    font-size: 11.5px;
    color: var(--color-on-surface-muted);
    line-height: 1.4;
}
/* Tone variants — accent rail + bar fill share the tone. */
.cr-kpi--success::before  { background: var(--color-success-500); }
.cr-kpi--success .cr-kpi__fill { background: var(--color-success-500); }
.cr-kpi--success .cr-kpi__value { color: var(--color-success-700); }
.cr-kpi--warning::before  { background: var(--color-warning-500); }
.cr-kpi--warning .cr-kpi__fill { background: var(--color-warning-500); }
.cr-kpi--warning .cr-kpi__value { color: var(--color-warning-700); }
.cr-kpi--error::before    { background: var(--color-error-500); }
.cr-kpi--error .cr-kpi__fill   { background: var(--color-error-500); }
.cr-kpi--error .cr-kpi__value   { color: var(--color-error-700); }
.cr-kpi--muted::before    { background: var(--color-outline); }
@media (max-width: 900px) {
    .cr-kpis { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* ── Decipher secondary row ─────────────────────────────────── */
.cr-secondary {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 10px;
    margin-bottom: 16px;
    padding: 8px 12px;
}
.cr-secondary__label {
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    font-weight: 600;
    color: var(--color-on-surface-muted);
}
.cr-secondary__pill {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 3px 10px;
    border-radius: 999px;
    border: 1px solid;
    font-size: 12px;
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
}
.cr-secondary__pill > svg { width: 12px; height: 12px; }
.cr-secondary__pill--ok    { background: var(--color-success-50);  color: var(--color-success-700);  border-color: var(--color-success-100); }
.cr-secondary__pill--error { background: var(--color-error-50);    color: var(--color-error-700);    border-color: var(--color-error-100); }
.cr-secondary__pill--muted { background: var(--color-neutral-50);  color: var(--color-on-surface-muted); border-color: var(--color-outline-variant); }
.cr-secondary__hint {
    font-size: 11.5px;
    color: var(--color-on-surface-muted);
    margin-left: auto;
}

/* ── History table — file column + grouped summary cell ─────── */
.cr-history-table { margin: 0; }
.cr-file-link {
    appearance: none;
    border: 0;
    background: transparent;
    padding: 4px 10px;
    border-radius: 6px;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-family: inherit;
    font-size: 13px;
    color: var(--color-primary-700);
    transition: background-color 120ms;
    max-width: 280px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.cr-file-link:hover { background: color-mix(in srgb, var(--color-primary-500) 8%, var(--color-surface)); }
.cr-file-link > svg { width: 14px; height: 14px; flex: 0 0 auto; }

/* Summary cell — multiple "label : value" segments inline, sized to
   content with a small inter-segment gap. Tones tint the value
   number only, so the labels read as a quiet legend. */
.cr-history-summary {
    display: inline-flex;
    align-items: center;
    gap: 14px;
    flex-wrap: wrap;
}
.cr-history-summary__seg {
    display: inline-flex;
    align-items: baseline;
    gap: 5px;
    font-size: 12px;
}
.cr-history-summary__label {
    color: var(--color-on-surface-muted);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    font-size: 10.5px;
    font-weight: 600;
}
.cr-history-summary__value {
    font-family: var(--font-mono);
    font-size: 13px;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    color: var(--color-on-surface);
}
.cr-history-summary__seg--ok .cr-history-summary__value      { color: var(--color-success-700); }
.cr-history-summary__seg--warning .cr-history-summary__value { color: var(--color-warning-700); }
.cr-history-summary__seg--error .cr-history-summary__value   { color: var(--color-error-700); }
.cr-history-summary__seg--muted .cr-history-summary__value   { color: var(--color-on-surface-muted); }
.cr-history-summary__seg--total .cr-history-summary__value   { color: var(--color-on-surface); }

/* Numeric metric columns — used by the inline history table on the
   /client-rejections upload screen. Each metric (Requested / Rejected
   / Already / Not found / Errors / Decipher) gets its own cell so the
   table reads as a proper scannable grid (vs the inline "label: value"
   segments the standalone /history page still uses). Header labels are
   uppercase eyebrow-sized; cell numbers are tabular-nums for column
   alignment; tone helpers tint non-zero values so problem runs jump
   out. Zero values in failure columns render as a dim em-dash so the
   eye picks up problems without scanning every cell. */
.cr-history-table__num-h {
    text-align: center;
    font-size: 10.5px;
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--color-on-surface-muted);
}
.cr-history-table__num {
    text-align: center;
}
.cr-history-table__num-val {
    font-family: var(--font-mono);
    font-size: 13px;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    color: var(--color-on-surface);
}
.cr-history-table__num-val--ok      { color: var(--color-success-700); }
.cr-history-table__num-val--warning { color: var(--color-warning-700); }
.cr-history-table__num-val--error   { color: var(--color-error-700);   }
.cr-history-table__num-val--muted   { color: var(--color-on-surface-muted); }
.cr-history-table__num-val--zero    {
    color: var(--color-on-surface-muted);
    opacity: 0.4;
    font-weight: 400;
}

/* ── File preview modal (history page popup) ────────────────── */
.cr-file-modal-backdrop {
    position: fixed;
    inset: 0;
    background: rgba(15, 23, 42, 0.45);
    backdrop-filter: blur(2px);
    z-index: 80;
    display: flex;
    align-items: center;
    justify-content: center;
    opacity: 0;
    transition: opacity 180ms ease-out;
    padding: 32px;
}
.cr-file-modal-backdrop--in { opacity: 1; }
.cr-file-modal {
    width: min(720px, 100%);
    max-height: 100%;
    background: var(--color-surface);
    border: 1px solid var(--color-outline-variant);
    border-radius: 12px;
    box-shadow: 0 24px 60px rgba(15, 23, 42, 0.24);
    display: flex;
    flex-direction: column;
    transform: translateY(8px);
    transition: transform 180ms ease-out;
}
.cr-file-modal-backdrop--in .cr-file-modal { transform: translateY(0); }
.cr-file-modal__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 14px 20px;
    border-bottom: 1px solid var(--color-outline-variant);
}
.cr-file-modal__title {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    font-size: 14px;
    font-weight: 600;
    color: var(--color-on-surface);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    min-width: 0;
}
.cr-file-modal__title > svg {
    width: 16px;
    height: 16px;
    color: var(--color-primary-600);
    flex: 0 0 auto;
}
.cr-file-modal__close {
    appearance: none;
    background: transparent;
    border: 0;
    width: 32px;
    height: 32px;
    border-radius: 6px;
    cursor: pointer;
    color: var(--color-on-surface-muted);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
}
.cr-file-modal__close:hover { background: var(--color-neutral-50); color: var(--color-on-surface); }
.cr-file-modal__close > svg { width: 16px; height: 16px; }
.cr-file-modal__body {
    flex: 1;
    overflow: auto;
}
.cr-file-modal__table {
    width: 100%;
    border-collapse: collapse;
    font-size: 13px;
}
.cr-file-modal__table thead th {
    position: sticky;
    top: 0;
    background: var(--color-neutral-50);
    text-align: left;
    padding: 10px 14px;
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--color-on-surface-muted);
    border-bottom: 1px solid var(--color-outline-variant);
}
.cr-file-modal__table tbody td {
    padding: 10px 14px;
    border-bottom: 1px solid var(--color-neutral-100);
    color: var(--color-on-surface);
}
.cr-file-modal__table tbody tr:last-child td { border-bottom: 0; }
.cr-file-modal__row-no {
    width: 48px;
    text-align: right;
    color: var(--color-on-surface-muted);
    font-variant-numeric: tabular-nums;
}
.cr-file-modal__loading,
.cr-file-modal__empty {
    padding: 28px 14px;
    text-align: center;
    color: var(--color-on-surface-muted);
    font-size: 13px;
}
/* (See the .cr-file-modal__foot definition earlier in this file —
   the flex-row layout supports both pages: client-rejections uses
   __foot-meta + __foot-actions children; history uses just
   __foot-meta with no actions, which naturally left-aligns.) */

.cr-reason-cell {
    max-width: 260px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.cr-empty {
    text-align: center;
    color: var(--color-on-surface-muted);
    padding: 28px 12px;
}


/* Recent-runs strip — bottom of the paste screen. Reads as a quiet
   list, one row per recent run, with a "view all" link to the full
   history page. */
.cr-recent {
    padding: 16px 20px;
    margin-top: 16px;
}
.cr-recent__head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    margin-bottom: 8px;
}
.cr-recent__title {
    font-size: 12px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--color-on-surface-muted);
}
.cr-recent__more {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    font-size: 12px;
    color: var(--color-primary-600);
}
.cr-recent__more > svg {
    width: 12px;
    height: 12px;
}
.cr-recent__list {
    margin: 0;
    padding: 0;
    list-style: none;
    border-top: 1px solid var(--color-outline-variant);
}
.cr-recent__row {
    /* Flex (not grid) + no growing track between actor and counts —
       the prior ``1fr`` middle column pushed the chips to the far
       right edge of the card, creating an awkward white space gap
       when the actor name is short ("Admin"). Now date, actor and
       chips cluster left-to-right with a fixed 24px gap; the right
       side of the row stays empty, which reads as deliberate
       whitespace rather than a layout hole. */
    display: flex;
    align-items: center;
    gap: 24px;
    padding: 8px 0;
    border-bottom: 1px solid var(--color-outline-variant);
    font-size: 13px;
}
.cr-recent__row:last-child { border-bottom: none; }
.cr-recent__when   {
    /* Fixed width keeps timestamps vertically aligned across rows
       even though their content varies ("Yesterday" vs "2 days ago"). */
    flex: 0 0 140px;
    font-size: 12px;
    color: var(--color-on-surface);
}
.cr-recent__actor  {
    flex: 0 0 auto;
    color: var(--color-on-surface-muted);
    font-size: 12px;
}
.cr-recent__counts { display: flex; gap: 6px; flex: 0 0 auto; }
.cr-recent__chip {
    font-family: var(--font-mono);
    font-size: 11px;
    padding: 2px 8px;
    border-radius: 4px;
    border: 1px solid;
}
.cr-recent__chip--success { background: var(--color-success-50); color: var(--color-success-700); border-color: var(--color-success-100); }
.cr-recent__chip--warning { background: var(--color-warning-50); color: var(--color-warning-700); border-color: var(--color-warning-100); }
.cr-recent__chip--error   { background: var(--color-error-50);   color: var(--color-error-700);   border-color: var(--color-error-100); }

/* History page empty-state card. */
.cr-empty-state {
    padding: 32px;
    text-align: center;
    color: var(--color-on-surface-muted);
}

/* ---------- Recon Log tab (/settings/recon-log) ---------- */
/* The whole tab lives inside a single card. The filter strip sits
   flush with the card's top edge; the table flows below it without
   internal padding so the header row reaches the card edges. */
.recon-log-card {
    padding: 0;
    overflow: hidden;
}

/* Filter strip — same pill row used on the projects / batches lists.
   Padding + a faint divider line mark it off from the table below; the
   inner `.filter-row` does the actual flex layout. */
.recon-log-filters {
    padding: 12px 16px;
    border-bottom: 1px solid var(--color-outline-variant);
    background: var(--color-neutral-50);
    margin-bottom: 0;          /* override .filter-row's 16px outer margin */
}
.recon-log-filters__hint {
    padding: 6px 16px 10px;
    font-size: 11px;
    color: var(--color-on-surface-muted);
    font-style: italic;
    background: var(--color-neutral-50);
    border-bottom: 1px solid var(--color-outline-variant);
}

/* Input variant of a .filter-pill — wraps a native <input> so the
   project search and date pickers visually match the dropdown pills.
   The pill itself owns the border / hover; the inner <input> goes
   borderless and takes its width from its content. */
.filter-pill--input {
    cursor: text;
    padding: 4px 10px;
    gap: 6px;
}
.filter-pill--input:focus-within {
    border-color: var(--color-primary-400);
    background: var(--color-surface);
}
.filter-pill__leading-icon {
    width: 14px;
    height: 14px;
    color: var(--color-on-surface-muted);
    flex: 0 0 auto;
}
.filter-pill__prefix {
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--color-on-surface-muted);
    font-weight: 600;
}
.filter-pill__input {
    border: 0;
    outline: none;
    background: transparent;
    font: inherit;
    font-size: 13px;
    color: var(--color-on-surface);
    padding: 0;
    min-width: 0;
    width: 140px;
}
.filter-pill__input::placeholder { color: var(--color-on-surface-muted); }
.filter-pill__input--date {
    width: 130px;
    color-scheme: light;
}
/* Clear-filters pill — outline + subtle danger tone so it reads as an
   action distinct from the value pills sitting next to it. */
.filter-pill--clear {
    text-decoration: none;
    color: var(--color-on-surface-muted);
    border-style: dashed;
}
.filter-pill--clear:hover {
    color: var(--color-error-600, var(--color-on-surface));
    border-color: var(--color-error-300, var(--color-outline));
    background: var(--color-error-50, var(--color-neutral-100));
}
.filter-pill--clear > svg {
    width: 12px;
    height: 12px;
}

/* Runs table — tighter than the default data-table--compact so a
   page of 50 runs fits comfortably. Row-tone tints error / partial
   rows so problems jump out at a scan. */
.recon-log-table { border-collapse: collapse; width: 100%; }
.recon-log-table th { font-size: 11px; text-transform: uppercase; letter-spacing: 0.04em; }
.recon-log-table th, .recon-log-table td { padding: 8px 14px; }
.recon-log-table tbody tr.recon-log-row--error    > td { background: var(--color-error-50); }
.recon-log-table tbody tr.recon-log-row--partial  > td { background: var(--color-warning-50); }
.recon-log-table tbody tr:hover > td { background: var(--color-neutral-50); }
.recon-log-table tbody tr.recon-log-row--error:hover    > td { background: var(--color-error-100); }
.recon-log-table tbody tr.recon-log-row--partial:hover  > td { background: var(--color-warning-100); }
.recon-log-table__summary { max-width: 320px; }
.recon-log-table__error {
    font-size: 11px;
    color: var(--color-error-700);
    margin-top: 2px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    max-width: 320px;
}
.recon-log-col-took    { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
.recon-log-col-records { white-space: nowrap; }

/* Records pill cluster in the list view — three small numeric pills:
   requested / ok / failed. Compact enough that the column never wraps. */
.recon-log-records {
    font-variant-numeric: tabular-nums;
}
.recon-log-records__pill {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 22px;
    padding: 1px 6px;
    border-radius: 4px;
    background: var(--color-neutral-100);
    color: var(--color-on-surface);
    font-size: 11px;
    font-weight: 600;
}
.recon-log-records__pill--ok      { background: var(--color-success-100); color: var(--color-success-700); }
.recon-log-records__pill--fail    { background: var(--color-error-100);   color: var(--color-error-700); }
.recon-log-records__pill--neutral { background: var(--color-neutral-100); color: var(--color-on-surface-muted); }
.recon-log-records__sep { color: var(--color-on-surface-muted); font-size: 11px; padding: 0 1px; }

/* Empty-state row — lives inline inside the table body so the header
   columns stay visible. Two stacked lines (title + hint) keep the
   intent clear without a giant placeholder icon dwarfing the page. */
.recon-log-empty-row > td { padding: 48px 24px; background: var(--color-surface); }
.recon-log-empty {
    text-align: center;
    color: var(--color-on-surface-muted);
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 6px;
}
.recon-log-empty__title { font-size: 14px; font-weight: 600; color: var(--color-on-surface); }
.recon-log-empty__hint  { font-size: 12px; max-width: 520px; }

/* Detail popup — wider variant. Reuses .cr-file-modal* chrome. */
.cr-file-modal-backdrop[data-role="recon-log-modal"] .cr-file-modal {
    width: min(1080px, 100%);
    max-height: calc(100% - 32px);
}
.recon-log-detail-stats {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    padding: 12px 20px;
    border-bottom: 1px solid var(--color-outline-variant);
    background: var(--color-neutral-50);
}
.recon-log-detail-stat {
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding: 6px 10px;
    background: var(--color-surface);
    border: 1px solid var(--color-outline-variant);
    border-radius: 6px;
    min-width: 80px;
}
.recon-log-detail-stat__label {
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--color-on-surface-muted);
}
.recon-log-detail-stat__value {
    font-size: 14px;
    color: var(--color-on-surface);
    font-weight: 600;
    font-variant-numeric: tabular-nums;
}

/* Tab nav inside the popup — three tabs (Records / Log / Context).
   Counts riding alongside the tab label tell the operator what's in
   each before they switch. */
.recon-log-tabs {
    display: flex;
    gap: 4px;
    padding: 0 20px;
    border-bottom: 1px solid var(--color-outline-variant);
    background: var(--color-surface);
}
.recon-log-tabs__tab {
    appearance: none;
    background: transparent;
    border: 0;
    border-bottom: 2px solid transparent;
    padding: 10px 14px;
    font-size: 13px;
    color: var(--color-on-surface-muted);
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    gap: 6px;
}
.recon-log-tabs__tab:hover { color: var(--color-on-surface); }
.recon-log-tabs__tab--active {
    color: var(--color-primary-700);
    border-bottom-color: var(--color-primary-500);
    font-weight: 600;
}
.recon-log-tabs__count {
    background: var(--color-neutral-100);
    color: var(--color-on-surface-muted);
    border-radius: 999px;
    padding: 1px 7px;
    font-size: 11px;
    font-weight: 600;
}
.recon-log-tabs__tab--active .recon-log-tabs__count {
    background: var(--color-primary-100);
    color: var(--color-primary-700);
}

/* Pane visibility — toggled by JS via .recon-log-pane--active. */
.recon-log-detail-body { padding: 0; }
.recon-log-pane                  { display: none; }
.recon-log-pane--active          { display: block; }
.recon-log-pane__empty {
    padding: 28px 14px;
    text-align: center;
    color: var(--color-on-surface-muted);
    font-size: 13px;
}

/* Records table inside the Records pane — same look as the main list
   but with row-status tints per record. */
.recon-log-records-table { width: 100%; border-collapse: collapse; font-size: 13px; }
.recon-log-records-table thead th {
    position: sticky; top: 0;
    background: var(--color-neutral-50);
    text-align: left;
    padding: 8px 14px;
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--color-on-surface-muted);
    border-bottom: 1px solid var(--color-outline-variant);
}
.recon-log-records-table tbody td {
    padding: 8px 14px;
    border-bottom: 1px solid var(--color-neutral-100);
    color: var(--color-on-surface);
}
.recon-log-records-table tbody tr:last-child td { border-bottom: 0; }
.recon-log-records-table__row-no {
    width: 40px;
    text-align: right;
    color: var(--color-on-surface-muted);
    font-variant-numeric: tabular-nums;
}
.recon-log-records-row--error    > td { background: var(--color-error-50); }
.recon-log-records-row--not_found > td { background: var(--color-warning-50); }

/* Free-form context block — one row per (key, value). */
.recon-log-detail-context {
    padding: 10px 20px;
    background: var(--color-surface);
    font-size: 12px;
}
.recon-log-detail-context__row {
    display: flex;
    gap: 12px;
    align-items: baseline;
    padding: 4px 0;
    border-bottom: 1px solid var(--color-neutral-100);
}
.recon-log-detail-context__row:last-child { border-bottom: 0; }
.recon-log-detail-context__key {
    flex: 0 0 140px;
    color: var(--color-on-surface-muted);
    text-transform: uppercase;
    font-size: 10px;
    letter-spacing: 0.06em;
}
.recon-log-detail-context__val {
    color: var(--color-on-surface);
    font-family: var(--font-mono);
    font-size: 12px;
    word-break: break-all;
}

/* Detail popup — reuses .cr-file-modal* chrome with a wider variant. */
.cr-file-modal-backdrop[data-role="recon-log-modal"] .cr-file-modal {
    width: min(900px, 100%);
}
.recon-log-detail-stats {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    padding: 12px 20px;
    border-bottom: 1px solid var(--color-outline-variant);
    background: var(--color-neutral-50);
}
.recon-log-detail-stat {
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding: 6px 10px;
    background: var(--color-surface);
    border: 1px solid var(--color-outline-variant);
    border-radius: 6px;
    min-width: 80px;
}
.recon-log-detail-stat__label {
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--color-on-surface-muted);
}
.recon-log-detail-stat__value {
    font-size: 14px;
    color: var(--color-on-surface);
    font-weight: 600;
    font-variant-numeric: tabular-nums;
}

/* Free-form context block — one row per (key, value). Hidden when the
   run had no context. */
.recon-log-detail-context {
    padding: 10px 20px;
    border-bottom: 1px solid var(--color-outline-variant);
    background: var(--color-surface);
    font-size: 12px;
}
.recon-log-detail-context__row {
    display: flex;
    gap: 12px;
    align-items: baseline;
    padding: 2px 0;
}
.recon-log-detail-context__key {
    flex: 0 0 140px;
    color: var(--color-on-surface-muted);
    text-transform: uppercase;
    font-size: 10px;
    letter-spacing: 0.06em;
}
.recon-log-detail-context__val {
    color: var(--color-on-surface);
    font-family: var(--font-mono);
    font-size: 12px;
    word-break: break-all;
}

/* Log-line list inside the Log tab — monospace, colour-coded per level. */
.recon-log-detail__lines { padding: 8px 0; }
.recon-log-detail__line {
    display: grid;
    grid-template-columns: 160px 60px 1fr;
    gap: 12px;
    padding: 4px 20px;
    font-family: var(--font-mono);
    font-size: 12px;
    line-height: 1.5;
    border-bottom: 1px solid var(--color-neutral-100);
}
.recon-log-detail__line:last-child { border-bottom: 0; }
.recon-log-detail__line-ts    { color: var(--color-on-surface-muted); }
.recon-log-detail__line-level { font-weight: 600; }
.recon-log-detail__line-msg   { color: var(--color-on-surface); word-break: break-word; }
.recon-log-detail__line--info  .recon-log-detail__line-level { color: var(--color-on-surface-muted); }
.recon-log-detail__line--warn                                { background: var(--color-warning-50); }
.recon-log-detail__line--warn  .recon-log-detail__line-level { color: var(--color-warning-700); }
.recon-log-detail__line--error                               { background: var(--color-error-50); }
.recon-log-detail__line--error .recon-log-detail__line-level { color: var(--color-error-700); }

/* ---------- Run-detail mode for the shared preview modal ----------
   The same .cr-file-modal serves three popups — file confirm, file
   view, and run-detail. Run-detail needs a wider modal (7 columns)
   plus a counter strip and default-reason banner above the table. */
.cr-file-modal-backdrop[data-mode="run-detail"] .cr-file-modal {
    width: min(1100px, 100%);
}
.cr-file-modal__title-wrap {
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
    flex: 1 1 auto;
}
.cr-file-modal__subtitle {
    font-size: 12px;
    color: var(--color-on-surface-muted);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.cr-detail-stats {
    display: grid;
    grid-template-columns: repeat(7, minmax(0, 1fr));
    gap: 8px;
    padding: 14px 20px;
    border-bottom: 1px solid var(--color-outline-variant);
    background: var(--color-neutral-50);
}
.cr-detail-stat {
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding: 8px 10px;
    background: var(--color-surface);
    border: 1px solid var(--color-outline-variant);
    border-radius: 6px;
    min-width: 0;
}
.cr-detail-stat__label {
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--color-on-surface-muted);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.cr-detail-stat__value {
    font-size: 18px;
    font-weight: 600;
    color: var(--color-on-surface);
    font-variant-numeric: tabular-nums;
}
.cr-detail-stat--ok      { border-color: var(--color-success-100); }
.cr-detail-stat--ok      .cr-detail-stat__value { color: var(--color-success-700); }
.cr-detail-stat--muted   .cr-detail-stat__value { color: var(--color-on-surface-muted); }
.cr-detail-stat--warning { background: var(--color-warning-50); border-color: var(--color-warning-100); }
.cr-detail-stat--warning .cr-detail-stat__value { color: var(--color-warning-700); }
.cr-detail-stat--error   { background: var(--color-error-50); border-color: var(--color-error-100); }
.cr-detail-stat--error   .cr-detail-stat__value { color: var(--color-error-700); }

.cr-detail-reason {
    display: flex;
    align-items: baseline;
    gap: 12px;
    padding: 10px 20px;
    border-bottom: 1px solid var(--color-outline-variant);
    background: var(--color-surface);
    font-size: 13px;
}
.cr-detail-reason__label {
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--color-on-surface-muted);
}
.cr-detail-reason__value {
    color: var(--color-on-surface);
    font-family: var(--font-mono);
    font-size: 12px;
}

/* Validation banner in the confirm popup — pending / ready / rejected. */
.cr-validate {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    padding: 12px 20px;
    border-bottom: 1px solid var(--color-outline-variant);
    font-size: 13px;
    line-height: 1.45;
}
.cr-validate > svg {
    flex: 0 0 auto;
    width: 18px;
    height: 18px;
    margin-top: 1px;
}
.cr-validate--pending {
    background: var(--color-neutral-50);
    color: var(--color-on-surface-muted);
}
.cr-validate--ok {
    background: var(--color-success-50);
    color: var(--color-success-700);
}
.cr-validate--bad {
    background: var(--color-error-50);
    color: var(--color-error-700);
}
.cr-validate .id-mono { font-family: var(--font-mono); }
.cr-validate__list {
    margin: 4px 0 0;
    padding-left: 18px;
}
.cr-validate__list li { margin: 2px 0; }
.cr-validate__spin { animation: cr-validate-spin 0.8s linear infinite; }
@keyframes cr-validate-spin { to { transform: rotate(360deg); } }

/* Note under the reason field explaining the auto-validate workflow. */
.cr-upload-grid__note {
    margin: 6px 0 0;
    font-size: 12px;
    line-height: 1.5;
    color: var(--color-on-surface-muted);
}

/* History table — the run id is now a button (opens the popup
   instead of linking to a detail page) so reset the native button
   chrome to look the same as the old anchor it replaced. */
.cr-history-run-link {
    appearance: none;
    background: transparent;
    border: 0;
    padding: 0;
    cursor: pointer;
    color: var(--color-primary-600);
    text-align: left;
}
.cr-history-run-link:hover { text-decoration: underline; }
