/* =============================================================================
   Code Mapping Comparison

   Follows TokenFlow's existing visual language rather than inventing one:
     - pure black surfaces, separated by borders instead of fills
     - 8px radii on panels and controls, 20px on pill toggles
     - 48px canvas buttons, 40px form controls, 14-16px text
     - Space Grotesk throughout, `all 0.2s` transitions
   Only the diff-severity colours are new, and they reuse the accent tokens that
   css/base.css already defines.
   ============================================================================= */

:root {
    --compare-error: var(--accent-error);
    --compare-warning: var(--accent-warning);
    --compare-info: var(--accent-primary);
    --compare-ok: var(--accent-success-bright);
    --compare-alias: var(--color-collection);
    --compare-figma: #c792ea;
    --compare-code: #7ee787;
    --compare-topbar-h: 68px;
    --compare-inspector-w: 400px;
    --compare-control-border: #232427;

    /* Severity reads as a dimmed card fill rather than an edge stroke. */
    --compare-error-bg: rgba(248, 81, 73, 0.10);
    --compare-error-line: rgba(248, 81, 73, 0.28);
    --compare-warning-bg: rgba(251, 133, 0, 0.10);
    --compare-warning-line: rgba(251, 133, 0, 0.28);
    --compare-info-bg: rgba(88, 166, 255, 0.08);
    --compare-info-line: rgba(88, 166, 255, 0.24);

    /* The same idea at lower strength, for 32px tree rows where a full-weight
       tint on every other line would be noise. */
    --compare-error-row: rgba(248, 81, 73, 0.07);
    --compare-warning-row: rgba(251, 133, 0, 0.07);
    --compare-info-row: rgba(88, 166, 255, 0.06);
}

/* The app sets no fixed-width family anywhere - tree labels and canvas node labels
   are all Space Grotesk. Compare follows that for every piece of UI text. A
   fixed-width face survives in exactly one place: the generated CSS patch, which is
   literal code the user copies into a stylesheet. */

/* The main app keeps all of its state; it is only hidden. Nothing to restore. */
body.compare-open .main-container,
body.compare-open .file-info-container,
body.compare-open .sidebar-toggle-btn {
    visibility: hidden;
}

/* --------------------------------------------------------- launch affordance */

/* Icon-only ghost button, sitting to the left of the Styles/Variables toggle.
   No border/fill until hovered - it's chrome-weight, not a labelled call to
   action, and a fixed 28px keeps it from stretching the row to match the
   toggle pill's own height. */
.compare-launch-btn {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 28px;
    height: 28px;
    padding: 0;
    font-family: inherit;
    color: var(--text-secondary);
    background: transparent;
    border: 0;
    border-radius: 6px;
    cursor: pointer;
    transition: all 0.2s;
}

.compare-launch-btn:hover {
    background: var(--bg-elevated);
    color: var(--text-primary);
}

.compare-launch-btn .glyph { font-size: 15px; }
.compare-launch-btn .count {
    position: absolute;
    top: -4px;
    right: -5px;
    min-width: 14px;
    height: 14px;
    padding: 0 3px;
    border-radius: 20px;
    background: var(--bg-interactive);
    font-size: 9px;
    font-weight: 700;
    line-height: 14px;
    text-align: center;
}

/* ------------------------------------------------------------------ shell */

.compare-workspace {
    position: fixed;
    inset: 0;
    z-index: 9500;
    display: flex;
    flex-direction: column;
    background: var(--bg-primary);
    color: var(--text-primary);
    font-family: 'Space Grotesk', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}

.compare-workspace[hidden] { display: none; }

.compare-topbar {
    display: flex;
    align-items: center;
    gap: 10px;
    height: var(--compare-topbar-h);
    padding: 0 20px;
    border-bottom: 1px solid var(--border-subtle);
    background: var(--bg-primary);
    flex: 0 0 auto;
}

/* The wordmark, stacked over a small "part of TokenFlow" caption rather than
   sitting inline with it - two very different font sizes sharing one inline
   baseline never quite reads as aligned, so this sidesteps the comparison
   instead of fighting it. */
.compare-brand {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 1px;
    white-space: nowrap;
    margin-right: 10px;
}

/* Matches the sidebar wordmark: light weight, then heavy. */
.compare-title {
    font-size: 20px;
    font-weight: 400;
    letter-spacing: 0.4px;
    line-height: 1.15;
}

.compare-title strong { font-weight: 700; }

.compare-by {
    font-size: 10.5px;
    font-weight: 500;
    line-height: 1.15;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--text-muted);
}

/* Read-only status, not a control - no border, no fill, just a coloured dot
   and the fact it stands for. Kept visually quiet on purpose: a bordered box
   reads as "click me" the same as every setting button beside it used to,
   which was exactly the ambiguity the whole topbar had.

   Ordered onto the right of the spacer, next to the settings toolbar it sits
   beside in markup - "what am I comparing" reads next to "what can I do
   about it", while the view tabs (order:0, left of the spacer) stay with
   the wordmark instead. */
.compare-chip {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    font-size: 12.5px;
    color: var(--text-secondary);
    white-space: nowrap;
    transition: color 0.2s;
    order: 1;
}

.compare-chip .dot { width: 7px; height: 7px; border-radius: 50%; flex: 0 0 auto; }
.compare-chip .dot.figma { background: var(--compare-figma); }
.compare-chip .dot.code { background: var(--compare-code); }
.compare-chip.actionable { cursor: pointer; }
.compare-chip.actionable:hover { color: var(--text-primary); }

.compare-spacer { flex: 1 1 auto; }

/* Settings sits in the icon-chrome cluster (More / Settings / Inspector /
   Close) as a plain .compare-icon-btn, same gear glyph as the main app's own
   settings button - one modal behind one icon, styled like every other
   control in that row instead of inventing its own pill/label treatment. */
.compare-topbar > .compare-icon-btn { order: 2; }

/* The pill toggle, matching .toggle-switch in css/base.css. */
.compare-viewtabs {
    display: inline-flex;
    background: transparent;
    border: 1px solid var(--border-default);
    border-radius: 20px;
    padding: 2px;
    height: 36px;
}

.compare-viewtabs button {
    appearance: none;
    border: 0;
    background: transparent;
    color: var(--text-muted);
    font-family: inherit;
    font-size: 12px;
    font-weight: 500;
    padding: 0 18px;
    border-radius: 18px;
    cursor: pointer;
    transition: all 0.3s ease;
}

.compare-viewtabs button:hover { color: var(--text-primary); }

.compare-viewtabs button.active {
    background: linear-gradient(135deg, #1e1e1e, #2f2f2f);
    color: var(--text-primary);
}

.compare-icon-btn {
    appearance: none;
    width: 40px;
    height: 40px;
    border-radius: 8px;
    border: 1px solid var(--compare-control-border);
    background: var(--bg-primary);
    color: var(--text-secondary);
    font-size: 17px;
    line-height: 1;
    cursor: pointer;
    transition: all 0.2s;
}

.compare-icon-btn:hover { background: var(--bg-elevated); border-color: var(--accent-primary); }
.compare-icon-btn.active { background: var(--bg-elevated); border-color: var(--accent-primary); color: var(--text-primary); }

.compare-btn {
    appearance: none;
    border: 1px solid var(--border-subtle);
    background: transparent;
    color: var(--text-secondary);
    font-family: inherit;
    font-size: 13px;
    font-weight: 500;
    padding: 10px 16px;
    border-radius: 8px;
    cursor: pointer;
    white-space: nowrap;
    transition: all 0.2s;
}

.compare-btn:hover { background: var(--bg-elevated); border-color: var(--accent-primary); color: var(--text-primary); }
.compare-btn.primary { background: var(--accent-button); border-color: var(--accent-button); color: #fff; }
.compare-btn.primary:hover { background: var(--accent-button-hover); border-color: var(--accent-button-hover); }
.compare-btn:disabled { opacity: 0.35; cursor: not-allowed; }
/* `hidden` has to beat the flex display these sit in. */
.compare-btn[hidden] { display: none; }

/* -------------------------------------------------------------------- body */

.compare-body { flex: 1 1 auto; display: flex; min-height: 0; }
.compare-main { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; }
.compare-view { flex: 1 1 auto; min-height: 0; overflow: hidden; display: flex; flex-direction: column; }
.compare-view[hidden] { display: none; }

/* `display: flex` beats the UA stylesheet's `[hidden] { display: none }`, so the
   hidden state has to be restated or the empty banner shows as a stray strip. */
.compare-banner[hidden] { display: none; }

.compare-banner {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 12px 20px;
    background: var(--accent-warning-10);
    border-bottom: 1px solid var(--compare-warning);
    font-size: 13px;
    color: var(--text-secondary);
}

/* The busy state's "something is actually happening" signal. Analysis freezes the
   main thread, so this is painted once before the work starts - it will stutter,
   and that is fine; a static banner told users the tab had hung. */
.compare-spinner {
    width: 14px;
    height: 14px;
    flex: none;
    border: 2px solid var(--border-subtle);
    border-top-color: var(--accent-primary);
    border-radius: 50%;
    animation: compare-spin 0.8s linear infinite;
}
@keyframes compare-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
    .compare-spinner { animation: none; }
}

/* ------------------------------------------------------------------ report */

.compare-report { overflow-y: auto; padding: 24px 28px 64px; }

/* One hero number carries the "big number" weight (match rate); errors and
   warnings ride under it as badges instead of their own headline digits.
   Everything else - scope, and what used to be footnote prose - is a plain
   ruled key/value grid beside it: one card, one real grid, one focal point
   instead of nine numbers competing for the same attention. Whatever a "sub"
   line or a whole paragraph used to spell out is one hover/focus away (see
   .compare-summary-hero-value[title] / .compare-summary-row .v[title]
   below), not gone. */
.compare-summary-bar {
    border: 1px solid var(--border-subtle);
    border-radius: 8px;
    background: var(--bg-primary);
    display: flex;
    align-items: stretch;
}

.compare-summary-hero {
    flex: 0 0 auto;
    padding: 18px 26px;
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    justify-content: center;
    gap: 8px;
    min-width: 170px;
}

.compare-summary-hero-value { font-size: 38px; font-weight: 700; line-height: 1; letter-spacing: -0.02em; cursor: help; }
.compare-summary-hero-value.ok { color: var(--compare-ok); }
.compare-summary-hero-value.warning { color: var(--compare-warning); }
.compare-summary-hero-value.error { color: var(--compare-error); }
.compare-summary-hero-label { font-size: 12px; color: var(--text-muted); }
.compare-summary-hero-badges { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 2px; }
.compare-summary-hero-badges .compare-badge { cursor: help; }

.compare-summary-div { flex: none; width: 1px; background: var(--border-subtle); margin: 16px 0; }

/* min-width: 0 overrides the flex-item default of min-width: auto, which would
   otherwise let the grid's own min-content hold this row open wider than the
   bar and force the whole card to overflow instead of shrinking to fit. */
.compare-summary-datagrid { flex: 1 1 auto; min-width: 0; display: grid; grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); align-content: center; }

.compare-summary-row {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 10px;
    padding: 9px 22px;
    border-bottom: 1px solid var(--border-subtle);
}
.compare-summary-row--bottom { border-bottom: 0; }
.compare-summary-row .k { font-size: 12.5px; color: var(--text-secondary); }
.compare-summary-row .v { font-size: 14px; font-weight: 700; color: var(--text-primary); letter-spacing: -0.01em; }
.compare-summary-row .v[title] { cursor: help; text-decoration: underline dotted; text-decoration-color: var(--border-interactive); text-underline-offset: 3px; }
.compare-summary-row .v i { font-style: normal; font-weight: 500; color: var(--text-muted); font-size: 0.8em; margin-left: 3px; }

.compare-note {
    margin: 16px 0 4px;
    padding: 14px 16px;
    border: 1px solid var(--compare-info-line);
    border-radius: 8px;
    background: var(--compare-info-bg);
    font-size: 13px;
    line-height: 1.6;
    color: var(--text-secondary);
}

.compare-note strong { color: var(--text-primary); }

.compare-hint {
    margin: 10px 2px 0;
    padding: 0 2px;
    font-size: 12px;
    color: var(--text-tertiary);
    cursor: help;
}
/* The "adjust naming rules" call-to-action sits inline in its sentence. */
.compare-hint .compare-btn { vertical-align: middle; margin-left: 2px; cursor: pointer; }

.compare-filters {
    display: flex;
    align-items: center;
    gap: 8px;
    margin: 22px 0 14px;
    flex-wrap: wrap;
}

.compare-filter-chip {
    appearance: none;
    border: 1px solid var(--border-subtle);
    background: transparent;
    color: var(--text-muted);
    font-family: inherit;
    font-size: 12px;
    font-weight: 500;
    padding: 7px 14px;
    border-radius: 20px;
    cursor: pointer;
    transition: all 0.2s;
}

.compare-filter-chip:hover { color: var(--text-primary); border-color: var(--border-interactive); }
.compare-filter-chip.active {
    background: linear-gradient(135deg, #1e1e1e, #2f2f2f);
    border-color: var(--border-interactive);
    color: var(--text-primary);
}

/* Held aside, not gone: the dashed border is the visual for "set apart on
   purpose". Toggles a review of the dismissed findings, not a filter. */
.compare-dismissed-chip { border-style: dashed; }
.compare-dismissed-chip.active { border-style: solid; }

.compare-select {
    background: transparent;
    color: var(--text-secondary);
    border: 1px solid var(--border-subtle);
    border-radius: 20px;
    padding: 7px 14px;
    font-family: inherit;
    font-size: 12px;
    cursor: pointer;
}

/* Separates the severity/type filters from the level filter - two different
   questions ("how bad" vs "where in the alias chain"), not one long list. */
.compare-filter-sep {
    width: 1px;
    height: 20px;
    background: var(--border-subtle);
    margin: 0 2px;
}

.compare-filter-label {
    font-size: 11px;
    font-weight: 500;
    color: var(--text-muted);
    text-transform: uppercase;
    letter-spacing: 0.03em;
}

/* The findings search sits at the right end of the filter row, with the shown/total
   count beside it: narrowing controls on the left, their outcome on the right. */
.compare-finding-search { margin-left: auto; }
@media (max-width: 768px) {
    .compare-finding-search { margin-left: 0; flex: 1 1 160px; }
}

/* Custom multi-select replacing a native <select> - a native picker takes over
   the whole screen on mobile once there are 20+ options, offers no search, and
   can only hold one selection at a time. This stays in-page, is searchable, and
   lets several finding types be viewed together. */
.compare-type-filter { position: relative; }

.compare-type-trigger {
    display: flex;
    align-items: center;
    gap: 6px;
    max-width: 260px;
}
/* The label truncates; the chevron never does - it is the only thing telling you
   this opens a panel, so it keeps its width no matter how long the label runs. */
.compare-type-trigger-label { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Drawn rather than typed: U+2304 renders tiny and off-baseline in most UI fonts,
   and falls back to a different face entirely on Windows. */
.compare-type-chevron {
    flex: 0 0 auto;
    width: 14px;
    height: 14px;
    transition: transform 0.2s;
    color: var(--text-muted);
}
.compare-type-trigger[aria-expanded="true"] .compare-type-chevron { transform: rotate(180deg); }

.compare-type-panel {
    display: none;
    position: absolute;
    top: calc(100% + 6px);
    left: 0;
    z-index: 20;
    width: max(280px, 100%);
    max-width: min(320px, 92vw);
    max-height: 360px;
    overflow-y: auto;
    background: var(--bg-primary);
    border: 1px solid var(--border-default);
    border-radius: 8px;
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
    padding: 8px;
}
.compare-type-panel.open { display: block; }

.compare-type-search {
    width: 100%;
    box-sizing: border-box;
    background: var(--bg-primary);
    color: var(--text-primary);
    border: 1px solid var(--border-subtle);
    border-radius: 6px;
    padding: 7px 10px;
    font-family: inherit;
    font-size: 12px;
    margin-bottom: 6px;
}
.compare-type-search:focus { outline: none; border-color: var(--border-accent); }

.compare-type-clear {
    display: block;
    width: 100%;
    text-align: left;
    background: transparent;
    color: var(--accent-primary);
    border: none;
    padding: 6px 4px;
    font-family: inherit;
    font-size: 12px;
    cursor: pointer;
}
.compare-type-clear:hover { text-decoration: underline; }

.compare-type-group-label {
    font-size: 11px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--text-muted);
    padding: 8px 4px 4px;
}

.compare-type-row {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 6px 4px;
    border-radius: 6px;
    cursor: pointer;
    font-size: 13px;
    color: var(--text-secondary);
}
.compare-type-row:hover { background: var(--bg-interactive); }
.compare-type-row.hidden { display: none; }
.compare-type-label { flex: 1; }
.compare-type-count { color: var(--text-muted); font-size: 12px; }
.compare-type-empty { color: var(--text-muted); font-size: 12px; padding: 8px 4px; }

.compare-findings { list-style: none; margin: 0; padding: 0; }

.compare-finding {
    display: flex;
    gap: 12px;
    align-items: flex-start;
    padding: 14px 16px;
    border: 1px solid var(--border-subtle);
    border-radius: 8px;
    margin-bottom: 8px;
    background: var(--bg-secondary);
    cursor: pointer;
    transition: all 0.2s;
}

/* Severity is carried by the border alone, on the same black surface as the
   rest of the workspace - a tinted fill on every row competed for attention
   with the accent colours it was meant to be framing. */
.compare-finding.sev-error { background: var(--bg-primary); border-color: var(--compare-error-line); }
.compare-finding.sev-warning { background: var(--bg-primary); border-color: var(--compare-warning-line); }
.compare-finding.sev-info { background: var(--bg-primary); border-color: var(--compare-info-line); }

.compare-finding:hover { border-color: var(--border-interactive); }
/* Rows are keyboard-focusable (tab through, Enter to select); the ring only
   shows for keyboard focus, so mouse clicks stay visually quiet. */
.compare-finding:focus-visible {
    outline: 2px solid var(--accent-primary);
    outline-offset: 1px;
}
.compare-finding.selected,
.compare-finding.sev-error.selected,
.compare-finding.sev-warning.selected,
.compare-finding.sev-info.selected {
    border-color: var(--accent-primary);
    box-shadow: 0 0 0 1px var(--accent-primary-30);
}

.compare-finding .finding-body { flex: 1 1 auto; min-width: 0; }
.compare-finding .finding-head { display: flex; align-items: center; gap: 9px; flex-wrap: wrap; }
.compare-finding .finding-name { font-size: 14px; font-weight: 600; }
/* Clamped to two lines: the list is for scanning, not reading. The full
   sentence, plus the alias chain it describes, lives in the inspector once a
   finding is selected. */
.compare-finding .finding-msg {
    font-size: 13px; color: var(--text-muted); margin-top: 6px; line-height: 1.55;
    display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.compare-finding .finding-loc { font-size: 12px; color: var(--text-tertiary); margin-top: 5px; }

/* Rows in the dismissed review: dimmed as a whole, except the one control the
   view exists for. */
.compare-finding.dismissed .finding-body > :not(.finding-head),
.compare-finding.dismissed .finding-head > :not(.compare-restore-btn) { opacity: 0.6; }
.compare-restore-btn { margin-left: auto; font-size: 11px; padding: 3px 9px; }

.compare-badge {
    font-size: 11px;
    font-weight: 600;
    padding: 3px 9px;
    border-radius: 20px;
    white-space: nowrap;
}

.compare-badge.error { background: var(--accent-error-20); color: var(--compare-error); }
.compare-badge.warning { background: var(--accent-warning-15); color: var(--compare-warning); }
.compare-badge.info { background: var(--accent-primary-20); color: var(--compare-info); }
.compare-badge.ok { background: var(--accent-success-20); color: var(--compare-ok); }

/* Match-confidence tier: still always visible, per the "follow the mapping" rule
   (the user must be able to see WHY we think two tokens are the same token), but
   as a line of text in the inspector rather than a pill on every card - see
   .compare-tier-note further down. */

.compare-swatch {
    display: inline-block;
    width: 13px;
    height: 13px;
    border-radius: 4px;
    border: 1px solid var(--border-interactive);
    vertical-align: -2px;
    margin-right: 5px;
}

.compare-value {
    font-size: 12px;
    padding: 2px 7px;
    border-radius: 6px;
    background: var(--bg-interactive);
}

/* Which mode a row of values belongs to, on a finding that covers more than
   one. Deliberately quiet: it separates the rows, it is not a third thing to
   read alongside the severity badge and the match tier. */
.compare-mode-tag {
    font-size: 10px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--text-tertiary);
    margin-right: 4px;
}

/* A rounding-tolerance difference, shown as the range it is rather than as
   two contradicting exact colours. */
.compare-range { display: flex; align-items: center; gap: 7px; flex-wrap: wrap; }

.compare-swatch-pair { display: inline-flex; align-items: center; margin-right: 2px; }
.compare-swatch-pair .compare-swatch { margin-right: 0; }
.compare-swatch-pair .compare-swatch + .compare-swatch { margin-left: -6px; }

.compare-range-note {
    font-size: 11px;
    color: var(--text-tertiary);
    font-style: italic;
}

/* ---------------------------------------------------------- view toolbars */

/* One strip above a split view, holding everything that changes WHAT the split
   shows. Scrolls horizontally rather than wrapping, so a narrow window loses
   nothing - it just needs a swipe. */
.compare-toolbar {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 10px 18px;
    background: var(--bg-primary);
    border-bottom: 1px solid var(--border-subtle);
    overflow-x: auto;
    overflow-y: visible;
    scrollbar-width: none;
}
.compare-toolbar::-webkit-scrollbar { display: none; }
.compare-toolbar .compare-btn { padding: 7px 13px; font-size: 12px; }

/* A segmented control rather than loose chips: these are mutually exclusive
   lenses on one dataset, and adjacency is what says so. */
.compare-seg {
    display: inline-flex;
    flex: 0 0 auto;
    background: var(--bg-canvas);
    border: 1px solid var(--border-subtle);
    border-radius: 8px;
    padding: 2px;
    gap: 2px;
}

.compare-seg button {
    appearance: none;
    border: 0;
    background: transparent;
    color: var(--text-muted);
    font-family: inherit;
    font-size: 12px;
    font-weight: 500;
    padding: 6px 11px;
    border-radius: 6px;
    cursor: pointer;
    white-space: nowrap;
    transition: all 0.2s;
}

.compare-seg button:hover { color: var(--text-primary); }
.compare-seg button.active {
    background: var(--bg-elevated);
    color: var(--text-primary);
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.25);
}
.compare-seg button .n { opacity: 0.65; font-variant-numeric: tabular-nums; margin-left: 3px; }
/* A lens with nothing under it is still worth showing - "Value 0" is an answer -
   but it should not read as somewhere to go. */
.compare-seg button[data-empty] { opacity: 0.45; }

.compare-toolbar-search {
    flex: 0 1 220px;
    min-width: 120px;
    background: var(--bg-canvas);
    border: 1px solid var(--border-subtle);
    border-radius: 8px;
    color: var(--text-primary);
    font-family: inherit;
    font-size: 12px;
    padding: 7px 11px;
}
.compare-toolbar-search:focus { outline: none; border-color: var(--accent-primary); }
.compare-toolbar-search::placeholder { color: var(--text-muted); }

.compare-toolbar-check {
    display: inline-flex; align-items: center; gap: 6px;
    font-size: 12px; color: var(--text-muted); white-space: nowrap; cursor: pointer;
}
.compare-toolbar-check input { accent-color: var(--accent-primary); }

.compare-toolbar-count {
    font-size: 12px; color: var(--text-muted); white-space: nowrap;
    font-variant-numeric: tabular-nums;
}
.compare-toolbar-count.narrowed { color: var(--text-secondary); }

/* ------------------------------------------------------------ canvas picker */

.compare-picker { position: relative; flex: 0 1 260px; min-width: 140px; display: flex; }
.compare-picker .compare-toolbar-search { flex: 1 1 auto; }

.compare-picker-panel {
    position: absolute;
    top: calc(100% + 6px);
    left: 0;
    width: 340px;
    max-width: 80vw;
    max-height: 320px;
    overflow-y: auto;
    z-index: 120;
    background: var(--bg-elevated);
    border: 1px solid var(--border-interactive);
    border-radius: 10px;
    padding: 6px;
    box-shadow: 0 12px 32px rgba(0, 0, 0, 0.45);
}
.compare-picker-panel[hidden] { display: none; }

.compare-picker-panel button {
    display: flex; align-items: center; gap: 8px; width: 100%;
    appearance: none; border: 0; background: transparent; cursor: pointer;
    text-align: left; padding: 7px 9px; border-radius: 6px;
    font-family: inherit; font-size: 12px; color: var(--text-secondary);
}
.compare-picker-panel button:hover { background: var(--accent-primary-10); color: var(--text-primary); }
.compare-picker-panel .pname { flex: 1 1 auto; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.compare-picker-panel .pmeta {
    flex: 0 1 auto; color: var(--text-muted); font-size: 11px;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 45%;
}
.compare-picker-panel .empty { padding: 8px 9px; font-size: 11px; color: var(--text-muted); }

/* -------------------------------------------------------------- split views */

.compare-split { flex: 1 1 auto; display: flex; min-height: 0; position: relative; }

.compare-pane {
    flex: 1 1 50%;
    min-width: 0;
    display: flex;
    flex-direction: column;
    border-right: 1px solid var(--border-subtle);
}

.compare-pane:last-child { border-right: 0; }

.compare-pane-header {
    flex: 0 0 auto;
    padding: 14px 18px;
    font-size: 13px;
    font-weight: 600;
    color: var(--text-tertiary);
    background: var(--bg-primary);
    border-bottom: 1px solid var(--border-subtle);
    display: flex;
    align-items: center;
    gap: 9px;
}

.compare-pane-header .dot { width: 8px; height: 8px; border-radius: 50%; }
.compare-pane[data-side="figma"] .compare-pane-header .dot { background: var(--compare-figma); }
.compare-pane[data-side="code"] .compare-pane-header .dot { background: var(--compare-code); }

.compare-tree { flex: 1 1 auto; overflow: auto; padding: 8px 0 48px; }

/* Both panes render the SAME merged row list, so heights match exactly and
   scroll sync is correct by construction rather than by approximation. */
.compare-row {
    display: flex;
    align-items: center;
    gap: 9px;
    height: 32px;
    padding: 0 14px;
    font-size: 12px;
    font-weight: 500;
    white-space: nowrap;
    cursor: pointer;
    transition: background 0.2s;
}

/* Same rule as the cards, at lower strength so a long run of rows stays readable. */
.compare-row.sev-error { background: var(--compare-error-row); }
.compare-row.sev-warning { background: var(--compare-warning-row); }
.compare-row.sev-info { background: var(--compare-info-row); }

.compare-row:hover { background: var(--accent-primary-05); }

/* A row the filter would have dropped, kept because it is the selection. Dashed,
   like a pinned node on the canvas: present for a reason other than the query. */
.compare-row.off-filter { outline: 1px dashed var(--border-interactive); outline-offset: -1px; }

/* The selection carries an accent edge as well as a tint, because a tint alone
   disappears against the severity rows above - the canvas gives the selected node a
   full accent outline for the same reason. */
.compare-row.selected,
.compare-row.sev-error.selected,
.compare-row.sev-warning.selected,
.compare-row.sev-info.selected {
    background: var(--accent-primary-10);
    box-shadow: inset 3px 0 0 var(--accent-primary);
}

.compare-row.group { font-weight: 600; color: var(--text-secondary); }
.compare-row.ghost { opacity: 0.3; font-style: italic; }
.compare-row .caret { width: 13px; flex: 0 0 13px; color: var(--text-muted); font-size: 9px; }
.compare-row .row-name { overflow: hidden; text-overflow: ellipsis; }

/* The Figma pane carries the token's whole path, because the name IS the mapping -
   `color/brand/500` and `brand/500` are different tokens and the row has to say which
   one it is. The groups are dimmed so the leaf still reads first, and it is the PATH
   that gives way when the pane narrows: an ellipsis over the leaf would hide the one
   part that cannot be inferred from the row above. */
.compare-row .row-name.figma { display: flex; align-items: baseline; min-width: 0; }
.compare-row .row-name .row-path {
    flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis;
    color: var(--text-muted); font-weight: 400;
}
.compare-row .row-name .row-leaf { flex: 0 0 auto; }
.compare-row.selected .row-name .row-path { color: var(--text-secondary); }
.compare-row .row-value { margin-left: auto; color: var(--text-muted); font-size: 12px; }
.compare-row .row-count {
    margin-left: auto; font-size: 11px; color: var(--text-muted);
    background: var(--bg-interactive); border-radius: 20px; padding: 2px 9px;
}

/* ------------------------------------------------------------------ canvas */

/* Two panes, one per environment, same split as the Trees view uses. */
.compare-canvas-stage {
    flex: 1 1 auto; min-height: 0; position: relative; display: flex;
    /* The graph pans; the page must not. Without this the SVG's own extent can
       hand the stage a scrollbar, which is a second, conflicting way to move the
       same picture. */
    overflow: hidden;
}

/* Sides across, modes down. Two panes is one row; four is two, and because the
   columns are the sides, the same token's two readings stay in the same column
   whichever mode you are looking at. */
.compare-canvas-panes {
    flex: 1 1 auto; min-width: 0; min-height: 0;
    display: grid;
    grid-template-columns: 1fr 1fr;
    grid-auto-rows: 1fr;
}

.compare-canvas-pane {
    min-width: 0; min-height: 0; display: flex; flex-direction: column;
    position: relative; border-right: 1px solid var(--border-subtle);
}
.compare-canvas-pane[data-side="code"] { border-right: 0; }
.compare-canvas-panes.stacked-modes .compare-canvas-pane:nth-child(-n+2) {
    border-bottom: 1px solid var(--border-subtle);
}
.compare-canvas-pane[data-side="figma"] .compare-pane-header .dot { background: var(--compare-figma); }
.compare-canvas-pane[data-side="code"] .compare-pane-header .dot { background: var(--compare-code); }

/* Which mode this pane is read at. Muted, because it is the answer to "why this
   picture" rather than part of the picture. */
.compare-canvas-pane .compare-pane-header .mode {
    margin-left: 8px; font-size: 11px; font-weight: 500; color: var(--text-muted);
    letter-spacing: 0.3px;
}

/* Same canvas surface as the main app, dot grid included (css/graph.css:48). */
.compare-canvas-stage svg {
    flex: 1 1 auto;
    display: block;
    width: 100%;
    min-height: 0;
    background-color: var(--bg-canvas);
    background-image: radial-gradient(circle at 0.5px 0.5px, rgba(255, 255, 255, 0.25) 0.5px, transparent 0);
    background-size: 15px 15px;
    cursor: grab;
}
.compare-canvas-stage svg:active { cursor: grabbing; }

.compare-canvas-controls {
    position: absolute;
    right: 16px;
    bottom: 16px;
    display: flex;
    flex-direction: column;
    gap: 8px;
    z-index: 50;
}

/* Full 48px canvas buttons, matching .canvas-btn in css/graph.css. */
.compare-canvas-controls .compare-icon-btn {
    width: 48px; height: 48px; font-size: 20.8px;
    background: var(--bg-primary); color: var(--text-secondary);
}

/* ------------------------------------------------------------- canvas nodes */

/* The card itself is the main canvas's: `.node-rect` and `.node-label` in
   css/graph.css, `.node-group` for the cursor and the hover brightness. Nothing is
   restated here, so the two canvases cannot drift apart - only the states below,
   which are Compare's own, are added on top. */

.compare-node text { pointer-events: none; user-select: none; }

/* Text that sits OUTSIDE the card, in the gutter above and below it. The card has
   no room to spare: every pixel of it is the fill and the name, exactly as on the
   main canvas. */
.compare-node .node-more { font-size: 10px; font-weight: 700; fill: var(--text-muted); }
.compare-node .node-pin { font-size: 8px; font-weight: 700; letter-spacing: 0.6px; fill: var(--accent-primary); }
.compare-node .node-lonely {
    font-size: 9.5px; font-weight: 600; fill: var(--compare-warning);
}

/* THE mark. The one hop where the two chains stop describing the same walk, and
   the only thing that recolours a card in this view - a chain where four cards out
   of four are outlined in red says everything is broken when one thing is. Same
   border the main canvas draws, in red instead of black. */
.compare-node.breaks .node-rect { stroke: var(--compare-error); }

/* Interaction state, not difference: which card the rest of the workspace is
   pointed at, and which ones are held there across a change of focus. Accent and
   a dash - never red, which belongs to the mark above and nothing else. */
.compare-node.pinned .node-rect { stroke-dasharray: 6 4; }
.compare-node.selected .node-rect { stroke: var(--accent-primary); }
.compare-node.breaks.selected .node-rect { stroke: var(--compare-error); }

/* An alias target nothing defines. Not a token at all - a hole the graph has to
   show, because the chain through it does not resolve. */
.compare-node.dangling .node-rect { stroke: var(--compare-error); stroke-dasharray: 3 3; }
.compare-node.dangling .node-label { fill: var(--compare-error); font-style: italic; }

/* ------------------------------------------------------------- canvas edges */

/* Edges are the main canvas's `.link.alias-link` (css/graph.css): a per-edge
   gradient set as a stroke ATTRIBUTE, dashed, and animated by `flowAnimation`.
   Nothing is declared here on purpose - a `stroke` rule would beat the attribute
   and flatten every gradient back to one colour. */

/* The canvas with nothing focused. It covers the whole stage, because at this
   point there is no graph to see past. */
.compare-canvas-empty {
    position: absolute;
    inset: 0;
    z-index: 60;
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    gap: 14px; padding: 32px; text-align: center;
    color: var(--text-muted);
    background: var(--bg-canvas);
}
.compare-canvas-empty[hidden] { display: none; }
.compare-canvas-empty .big { font-size: 40px; opacity: 0.4; }
.compare-canvas-empty h3 {
    margin: 0; font-size: 18px; font-weight: 600; color: var(--text-primary); letter-spacing: 0.2px;
}
.compare-canvas-empty p { margin: 0; font-size: 13px; max-width: 520px; line-height: 1.65; }
.compare-canvas-empty .hint { font-size: 11px; color: var(--text-muted); max-width: 560px; }
.compare-empty-actions { display: flex; flex-wrap: wrap; gap: 8px; justify-content: center; }

/* The mode the graph is read at. Hidden outright on a single-theme system - a
   select with one option reads as a decision the reader does not have to make. */
.compare-select {
    appearance: none;
    background: var(--bg-canvas);
    border: 1px solid var(--border-subtle);
    border-radius: 8px;
    color: var(--text-secondary);
    font-family: inherit;
    font-size: 12px;
    padding: 7px 10px;
    cursor: pointer;
}
.compare-select:focus { outline: none; border-color: var(--accent-primary); }
.compare-select[hidden] { display: none; }

/* A value gap the configured tolerance absorbed: no longer a finding, but the row
   should not read as byte-identical either. */
.compare-row.within-tolerance .row-value { border-bottom: 1px dotted var(--border-interactive); }

/* --------------------------------------------------------------- inspector */

.compare-inspector {
    position: relative;
    flex: 0 0 var(--compare-inspector-w, 400px);
    width: var(--compare-inspector-w, 400px);
    max-width: 92vw;
    border-left: 1px solid var(--border-subtle);
    background: var(--bg-primary);
    overflow-y: auto;
    padding: 22px;
}

/* Drag handle on the inspector's left edge. A hairline by default; it only
   draws attention once it has something to say (hover, or mid-drag). */
.compare-inspector-resizer {
    position: absolute;
    left: -5px;
    top: 0;
    bottom: 0;
    width: 10px;
    cursor: col-resize;
    z-index: 6;
    touch-action: none;
}

.compare-inspector-resizer::after {
    content: '';
    position: absolute;
    left: 4px;
    top: 0;
    bottom: 0;
    width: 2px;
    background: transparent;
    transition: background 0.15s;
}

.compare-inspector-resizer:hover::after,
.compare-inspector-resizer.dragging::after {
    background: var(--accent-primary);
}

.compare-inspector[hidden] { display: none; }
.compare-inspector h4 { margin: 0 0 6px; font-size: 16px; font-weight: 600; word-break: break-all; }
.compare-inspector .sub { font-size: 13px; color: var(--text-muted); margin-bottom: 18px; line-height: 1.6; }

/* The status badge (No issues / N issues) is the verdict, and the only thing here
   styled as an indicator. The tier note underneath is provenance ("matched via
   namespace") - plain, uncoloured text, because a match tier is never itself a
   problem and must never look like one. */
.compare-status-row { display: flex; align-items: center; flex-wrap: wrap; gap: 8px 10px; }
.compare-tier-note { margin-top: 4px; font-size: 12px; color: var(--text-tertiary); }
.compare-evidence { margin-top: 6px; }

.compare-section { margin-bottom: 22px; }
.compare-section > .title {
    font-size: 12px; font-weight: 600; color: var(--text-muted); margin-bottom: 9px;
}

/* The findings lead the panel, so their heading is the one thing here allowed to
   read as a heading rather than a label. */
.compare-section-lead > .title {
    font-size: 13px;
    color: var(--text-primary);
}

/* One finding, stated in full. Same severity tinting as a row in the report list,
   so "the amber one" means the same thing in both places. */
.compare-issue {
    font-size: 12px;
    line-height: 1.55;
    word-break: break-word;
    padding: 10px 12px;
    border: 1px solid var(--border-subtle);
    border-radius: 8px;
    margin-bottom: 8px;
}
.compare-issue:last-child { margin-bottom: 0; }
.compare-issue.sev-error { background: var(--bg-primary); border-color: var(--compare-error-line); }
.compare-issue.sev-warning { background: var(--bg-primary); border-color: var(--compare-warning-line); }
.compare-issue.sev-info { background: var(--bg-primary); border-color: var(--compare-info-line); }
.compare-issue.ok { color: var(--compare-ok); }
.compare-issue-msg { margin-top: 5px; color: var(--text-secondary); }

/* The act-on-it row under a finding: the two fix copies and dismiss. Small
   buttons - these are per-finding verbs, not page-level actions. */
.compare-issue-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    margin-top: 9px;
}
.compare-issue-actions .compare-btn { font-size: 11px; padding: 3px 9px; }
.compare-issue.dismissed { opacity: 0.6; }
.compare-issue.dismissed .compare-issue-actions { opacity: 1; }

/* Confirm-a-guess strip under the tier note in the inspector header. */
.compare-confirm-strip {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 8px;
    margin-top: 8px;
}
.compare-confirm-strip .compare-btn { font-size: 11px; padding: 3px 9px; }
.compare-confirm-hint { font-size: 11px; color: var(--text-muted); }

/* Reference sections: present, folded, one click from open. The marker sits on the
   right so the titles stay flush with every other section title above them. */
.compare-deep { border-top: 1px solid var(--border-subtle); padding-top: 12px; margin-bottom: 16px; }
.compare-deep > summary.title {
    cursor: pointer;
    list-style: none;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    margin-bottom: 0;
    padding: 2px 0;
    user-select: none;
}
.compare-deep > summary::-webkit-details-marker { display: none; }
.compare-deep > summary::after {
    content: '⌄';
    font-size: 13px;
    line-height: 1;
    color: var(--text-tertiary);
    transition: transform 0.15s;
}
.compare-deep[open] > summary::after { transform: rotate(180deg); }
.compare-deep > summary:hover { color: var(--text-primary); }
.compare-deep > summary:focus-visible { outline: 2px solid var(--accent-primary); outline-offset: 3px; border-radius: 4px; }
.compare-deep-body { margin-top: 11px; }

.side-row { font-size: 12px; margin-bottom: 8px; line-height: 1.55; word-break: break-word; }
.side-row:last-child { margin-bottom: 0; }
.side-row .k { color: var(--text-muted); }

/* --- resolution chains: the canvas, collapsed to one token's neighbourhood.
   Same dot-grid surface and node shape as the real Canvas tab, so this reads
   as "the canvas" rather than a row of generic tags. */
.compare-chains { display: flex; flex-direction: column; gap: 8px; }

/* One group per mode (Light, Dark, …), each holding both sides. The label is
   omitted when the file has a single mode, so a one-mode set looks exactly as
   it did before. */
.compare-chain-group { display: flex; flex-direction: column; gap: 8px; }
.compare-chain-group + .compare-chain-group { margin-top: 6px; }

.chain-mode-label {
    font-size: 10px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--text-muted);
}

.compare-chain {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    padding: 10px 12px;
    border: 1px solid var(--border-subtle);
    border-radius: 8px;
    background-color: var(--bg-canvas);
    background-image: radial-gradient(circle at 0.5px 0.5px, rgba(255, 255, 255, 0.2) 0.5px, transparent 0);
    background-size: 13px 13px;
}

.chain-side-label {
    flex: 0 0 auto;
    font-size: 10px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    padding: 3px 7px;
    border-radius: 5px;
}

.chain-side-label.figma { color: var(--compare-figma); background: color-mix(in srgb, var(--compare-figma) 14%, transparent); }
.chain-side-label.code { color: var(--compare-code); background: color-mix(in srgb, var(--compare-code) 14%, transparent); }

.compare-chain .chain-empty { font-size: 12px; color: var(--text-muted); font-style: italic; }

/* A composed chain is two variables and an operation, not one token's walk. The
   dashed edge says so at a glance: this side of the pair was assembled by the
   code rather than declared once in Figma. */
.compare-chain.composed { border-style: dashed; flex-wrap: wrap; }
.compare-chain.composed .chain-arrow { font-weight: 700; color: var(--text-secondary); }
.compare-chain .chain-note {
    flex: 1 0 100%;
    padding-left: 2px;
    font-size: 10px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--text-muted);
}

/* Wraps rather than scrolls - the default 400px rail is narrower than most
   real alias chains, and a hidden horizontal scrollbar reads as a truncated
   value rather than an inviting one. */
.chain-track { display: flex; align-items: center; gap: 6px 4px; flex-wrap: wrap; }

.chain-node {
    display: inline-flex;
    align-items: center;
    font-size: 11px;
    font-weight: 700;
    padding: 5px 10px;
    border-radius: 6px;
    background: var(--bg-elevated);
    color: var(--text-secondary);
    border: 1px solid var(--border-interactive);
}

.chain-node.value { color: var(--text-primary); border-color: var(--accent-primary); background: var(--bg-elevated); }

/* The literal values a pair actually lands on. Every other node in the chain
   is scaffolding - agreement or disagreement only matters here, so this is
   the one place severity has to be loud. A matching pair keeps the plain
   accent border above; only a real gap between the two sides earns colour,
   so a "no issues" mode and a "1 issue" mode never draw the same picture. */
.chain-arrow.mismatch { color: var(--compare-error); font-weight: 700; }
.chain-node.value.mismatch {
    color: var(--compare-error);
    border-color: var(--compare-error);
    background: var(--compare-error-bg);
    box-shadow: 0 0 0 3px var(--compare-error-bg);
}
/* A rounding artefact is not nothing, but it must read as calmer than an
   actual colour swap - info, not error. */
.chain-arrow.near { color: var(--compare-info); }
.chain-node.value.near { color: var(--compare-info); border-color: var(--compare-info); }
.chain-node.broken {
    color: var(--compare-warning);
    border: 1px dashed var(--compare-warning);
    background: var(--compare-warning-bg);
}

.chain-arrow { flex: 0 0 auto; color: var(--text-tertiary); font-size: 12px; }
.chain-arrow.broken { color: var(--compare-warning); }

/* The hop where the two walks part company.
   Solid-bordered rather than dashed, because `broken` (dashed) already means
   "this chain does not arrive anywhere" - a divergence arrives perfectly well,
   at the wrong place, and the two must not read as the same failure. The arrow
   carries the same colour: it is the STEP that went rogue, not the node. */
.chain-arrow.diverged { color: var(--compare-error); font-weight: 700; }
.chain-node.diverged {
    color: var(--compare-error);
    border-color: var(--compare-error);
    background: var(--compare-error-bg);
    box-shadow: 0 0 0 3px var(--compare-error-bg);
}

.chain-swatch {
    display: inline-block;
    width: 11px;
    height: 11px;
    border-radius: 3px;
    border: 1px solid var(--border-interactive);
    margin-right: 5px;
}

/* --- both sides, one table instead of two repeating cards --- */
.compare-sides-table td.field { color: var(--text-muted); font-size: 11px; width: 84px; white-space: nowrap; }
.compare-sides-table td.dim { color: var(--text-tertiary); }

.compare-mode-table { width: 100%; border-collapse: collapse; font-size: 12px; }
.compare-mode-table th, .compare-mode-table td {
    text-align: left; padding: 9px 10px; border-bottom: 1px solid var(--border-subtle); vertical-align: middle;
}
.compare-mode-table th { color: var(--text-muted); font-weight: 600; font-size: 11px; }
.compare-mode-table select {
    width: 100%; background: transparent; color: var(--text-primary);
    border: 1px solid var(--border-subtle); border-radius: 8px; padding: 8px 10px;
    font-family: inherit; font-size: 12px;
}

/* ------------------------------------------------------------ empty / modal */

.compare-empty {
    flex: 1 1 auto;
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    gap: 16px; color: var(--text-muted); padding: 40px; text-align: center;
}

.compare-empty .big { font-size: 44px; opacity: 0.45; }
.compare-empty h3 { margin: 0; font-size: 20px; font-weight: 600; color: var(--text-primary); letter-spacing: 0.2px; }
.compare-empty p { margin: 0; font-size: 14px; max-width: 500px; line-height: 1.65; }

.compare-dropzone {
    border: 1px dashed var(--border-interactive);
    border-radius: 8px;
    padding: 32px 48px;
    background: transparent;
    cursor: pointer;
    font-size: 14px;
    transition: all 0.2s;
}

.compare-dropzone:hover, .compare-dropzone.highlight {
    border-color: var(--accent-primary);
    background: var(--accent-primary-05);
}
.compare-dropzone:focus-visible {
    outline: 2px solid var(--accent-primary);
    outline-offset: 2px;
}

.compare-dropzone .upload-link { color: var(--accent-primary); font-weight: 500; }

.compare-filelist { list-style: none; margin: 16px 0 0; padding: 0; width: 100%; max-width: 520px; }

.compare-filelist li {
    display: flex; align-items: center; gap: 12px;
    padding: 10px 14px; margin-bottom: 7px;
    border: 1px solid var(--border-subtle); border-radius: 8px;
    background: transparent; font-size: 13px; text-align: left;
}

.compare-filelist .fname { flex: 1 1 auto; overflow: hidden; text-overflow: ellipsis; }

.compare-filelist .fmeta { color: var(--text-muted); font-size: 12px; white-space: nowrap; }
.compare-filelist .ford { display: flex; gap: 4px; }

.compare-filelist button {
    appearance: none; border: 1px solid var(--border-subtle); background: transparent;
    color: var(--text-secondary); width: 26px; height: 26px; border-radius: 6px;
    cursor: pointer; font-size: 12px; line-height: 1; padding: 0; transition: all 0.2s;
}

.compare-filelist button:hover { background: var(--bg-elevated); border-color: var(--accent-primary); color: var(--text-primary); }
.compare-filelist button:disabled { opacity: 0.3; cursor: not-allowed; }

.compare-modal-backdrop {
    position: fixed; inset: 0; z-index: 9600;
    background: var(--overlay-dark);
    display: flex; align-items: center; justify-content: center; padding: 24px;
}

.compare-modal {
    background: var(--bg-primary);
    border: 1px solid var(--border-default);
    border-radius: 8px;
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
    width: min(760px, 100%);
    max-height: 84vh;
    display: flex; flex-direction: column;
}

.compare-modal header {
    padding: 18px 22px; border-bottom: 1px solid var(--border-subtle);
    display: flex; align-items: center; gap: 12px;
}

.compare-modal header h3 { margin: 0; font-size: 16px; font-weight: 600; flex: 1 1 auto; }
.compare-modal .body { padding: 20px 22px; overflow-y: auto; }
.compare-modal footer { padding: 16px 22px; border-top: 1px solid var(--border-subtle); display: flex; justify-content: flex-end; gap: 9px; }

/* ------------------------------------------------------ tabbed settings modal */
/* One modal for Mode mapping / Naming / Tolerance / Parse report / Export,
   navigated by tabs instead of five separate dialogs. Wider than a plain
   modal - the naming tab's two-column fields and the mode-mapping table both
   want the room. Height is fixed rather than left to shrink-to-content: the
   naming tab is long and the parse-report tab can be one line, and letting
   the modal resize on every tab switch reads as the dialog jumping around
   under the cursor. .body carries the difference via its own scrollbar. */
.compare-modal--tabs {
    width: min(860px, 100%);
    height: min(680px, 84vh);
}
.compare-modal--tabs .body { flex: 1 1 auto; }

.compare-modal-tabs {
    display: flex;
    align-items: center;
    gap: 2px;
    padding: 0 14px;
    border-bottom: 1px solid var(--border-subtle);
    /* overflow-x alone makes the UA treat overflow-y as auto too (CSS
       Overflow spec), so a strip one pixel taller than the row grows a
       vertical scrollbar of its own - pin y shut explicitly. */
    overflow-x: auto;
    overflow-y: hidden;
    flex: 0 0 auto;
}

.compare-modal-tabs button {
    appearance: none;
    border: 0;
    background: transparent;
    color: var(--text-muted);
    font-family: inherit;
    font-size: 12.5px;
    font-weight: 500;
    padding: 12px 12px;
    margin-bottom: -1px;
    border-bottom: 2px solid transparent;
    cursor: pointer;
    white-space: nowrap;
    transition: all 0.2s;
}

.compare-modal-tabs button:hover { color: var(--text-primary); }
.compare-modal-tabs button.active { color: var(--text-primary); border-bottom-color: var(--accent-primary); }

/* Parse-warning count, riding its own tab instead of the entry-point button -
   it only means anything once you're looking at the tab it describes. */
.compare-modal-tab-badge {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 15px;
    height: 15px;
    padding: 0 4px;
    margin-left: 6px;
    border-radius: 20px;
    background: var(--accent-warning-15);
    color: var(--compare-warning);
    font-size: 10px;
    font-weight: 700;
    line-height: 15px;
}

.compare-modal-pane { display: none; }
.compare-modal-pane.active { display: block; }

.compare-tab-empty { font-size: 13px; color: var(--text-muted); text-align: center; padding: 32px 12px; margin: 0; }

.compare-modal-footer-actions { display: flex; gap: 9px; }
.compare-modal-footer-actions [data-footer-for][hidden] { display: none; }

.compare-field { margin-bottom: 16px; }
.compare-export-row { display: flex; flex-wrap: wrap; gap: 8px; }
.compare-field > label { display: block; font-size: 12px; color: var(--text-muted); margin-bottom: 7px; font-weight: 500; }
.compare-field input[type="text"], .compare-field input[type="number"], .compare-field select {
    width: 100%; background: transparent; color: var(--text-primary);
    border: 1px solid var(--border-subtle); border-radius: 8px; padding: 11px 14px;
    font-family: inherit; font-size: 14px;
}
.compare-field input:focus, .compare-field select:focus, .compare-mode-table select:focus {
    outline: none; border-color: var(--accent-primary); box-shadow: 0 0 0 2px var(--accent-primary-30);
}
.compare-field .hint { font-size: 12px; color: var(--text-muted); margin-top: 6px; line-height: 1.55; }

/* One override per row in Settings > Overrides: what was overruled, then the
   one control that undoes it. Stale rows dim but keep their button - the
   judgement is still on record and still revocable. */
.compare-override-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    padding: 7px 0;
    border-bottom: 1px solid var(--border-subtle);
    font-size: 12px;
}
.compare-override-row:last-of-type { border-bottom: none; }
.compare-override-row.stale .compare-override-text { opacity: 0.55; }
.compare-override-text {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 6px;
    min-width: 0;
    word-break: break-all;
}
.compare-override-arrow { color: var(--text-muted); }
.compare-override-row .compare-btn { flex-shrink: 0; font-size: 11px; padding: 3px 9px; }
.compare-check { display: flex; align-items: flex-start; gap: 10px; font-size: 13px; margin-bottom: 11px; cursor: pointer; line-height: 1.5; }
.compare-check input { margin-top: 3px; }

/* --------------------------------------------- collection / sub-collection picker */
/* Two levels of the same decision, so the nesting has to read as nesting: a rule down
   the left edge ties a collection's root groups to it, and an excluded collection
   dims as a whole rather than leaving live-looking children behind. */
.compare-scope-list { max-height: 260px; overflow-y: auto; padding-right: 4px; }
.compare-scope { margin-bottom: 4px; }
.compare-scope > .compare-check { margin-bottom: 6px; font-weight: 500; }
.compare-scope.off { opacity: 0.45; }
.compare-check.scope-group {
    margin-left: 22px; margin-bottom: 6px; padding-left: 12px;
    border-left: 1px solid var(--border-subtle);
    font-size: 12px; color: var(--text-secondary); font-weight: 400;
}
.compare-check .scope-count {
    margin-left: auto; font-size: 11px; color: var(--text-muted);
    font-variant-numeric: tabular-nums;
}

.compare-preview { margin-top: 12px; border: 1px solid var(--border-subtle); border-radius: 8px; overflow: hidden; }
.compare-preview table { width: 100%; border-collapse: collapse; font-size: 12px; }
.compare-preview td { padding: 7px 11px; border-bottom: 1px solid var(--border-subtle); }
.compare-preview td.arrow { color: var(--text-muted); width: 22px; text-align: center; }
.compare-preview td.hit { color: var(--compare-ok); }
.compare-preview td.miss { color: var(--text-muted); }

/* ------------------------------------------------------------ tolerance modal */

.compare-tolerance-presets { display: flex; flex-wrap: wrap; gap: 8px; }

/* Label, slider and reading on one line, with the explanation underneath: the
   number has to move with the thumb or the control is a guess. */
.compare-tolerance-row {
    display: grid;
    grid-template-columns: 1fr minmax(120px, 200px) 62px;
    align-items: center;
    column-gap: 12px;
    padding: 7px 0;
}
.compare-tolerance-row label { font-size: 13px; color: var(--text-secondary); }
.compare-tolerance-row input[type="range"] { width: 100%; accent-color: var(--accent-primary); }
.compare-tolerance-row output {
    font-size: 12px; color: var(--text-primary); text-align: right;
    font-variant-numeric: tabular-nums;
}
.compare-tolerance-row .hint {
    grid-column: 1 / -1; font-size: 11px; color: var(--text-muted);
    margin-top: 3px; line-height: 1.5;
}

/* Pinned to the bottom of the scrolling modal body: the whole point of these
   controls is watching this number move, and it must not scroll out of sight
   while you drag a slider further down the list. */
.compare-tolerance-effect {
    position: sticky; bottom: -1px;
    margin-top: 14px; padding: 12px 14px;
    background: var(--bg-canvas);
    border: 1px solid var(--border-subtle);
    border-radius: 8px;
    font-size: 12px; color: var(--text-secondary); line-height: 1.6;
}
.compare-tolerance-effect .tol-headline { margin-bottom: 8px; }
.compare-tolerance-effect strong { color: var(--text-primary); }
.compare-tolerance-effect .muted { color: var(--text-muted); }
.compare-tolerance-effect .tol-sample {
    display: flex; align-items: center; gap: 8px; padding: 3px 0;
    font-size: 11px; color: var(--text-muted);
}
.compare-tolerance-effect .tol-sample .mono { color: var(--text-secondary); }

.compare-toast {
    position: fixed; bottom: 28px; left: 50%; transform: translateX(-50%);
    z-index: 9700; padding: 12px 20px; border-radius: 8px;
    background: var(--bg-primary); border: 1px solid var(--accent-primary);
    color: var(--text-primary); font-size: 13px;
    font-family: 'Space Grotesk', -apple-system, sans-serif;
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.5);
}

.compare-warnlist { list-style: none; margin: 0; padding: 0; font-size: 12px; }
.compare-warnlist li { padding: 9px 12px; border-bottom: 1px solid var(--border-subtle); line-height: 1.55; }
.compare-warnlist .wcode { font-weight: 700; color: var(--compare-warning); }
.compare-warnlist .wloc { color: var(--text-muted); }

/* On desktop this wrapper is invisible to layout - the topbar lays its children
   out exactly as if it were not there. It only becomes a box on mobile. */
.compare-topbar-actions { display: contents; }

.compare-sheet-close { display: none; }
#compareMoreBtn { display: none; }

/* ------------------------------------------------------------------ mobile */

@media (max-width: 768px) {
    /* --- topbar: three rows, and the close button is always on the first one.
       Previously everything past the first chip sat off-screen behind a
       horizontal scroll, which put the only way out of Compare out of reach. */
    .compare-topbar {
        height: auto;
        flex-wrap: wrap;
        padding: 10px 12px;
        gap: 8px;
        row-gap: 10px;
    }

    .compare-brand { order: 1; flex: 1 1 auto; margin-right: 0; }
    .compare-title { font-size: 17px; }
    #compareMoreBtn { display: block; order: 2; flex: 0 0 auto; }
    #compareInspectorBtn { order: 3; flex: 0 0 auto; }
    #compareCloseBtn { order: 4; flex: 0 0 auto; }

    /* 48px outer so the buttons inside clear 44px once the pill's 2px padding and
       1px border are taken off - the tappable thing is the button, not the pill. */
    .compare-viewtabs { order: 5; width: 100%; height: 48px; }
    .compare-viewtabs button { flex: 1 1 0; padding: 0; font-size: 13px; }

    /* Secondary controls become one horizontally scrollable strip behind the ⋯
       toggle, so the default topbar is two rows rather than three. */
    .compare-topbar-actions {
        order: 6;
        display: none;
        width: 100%;
        gap: 8px;
        overflow-x: auto;
        -webkit-overflow-scrolling: touch;
        scrollbar-width: none;
        padding-bottom: 2px;
    }

    .compare-topbar.actions-open .compare-topbar-actions { display: flex; }

    .compare-topbar-actions::-webkit-scrollbar { display: none; }
    .compare-topbar-actions > * { flex: 0 0 auto; }
    .compare-chip { max-width: 190px; overflow: hidden; }
    .compare-chip span:last-child { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
    .compare-spacer { display: none; }

    /* --- touch targets. Apple and Google both land on ~44px; nothing
       interactive should be smaller. */
    .compare-btn { min-height: 44px; padding: 10px 14px; }
    .compare-icon-btn { width: 44px; height: 44px; }
    .compare-filter-chip { min-height: 40px; padding: 9px 15px; }
    .compare-select { min-height: 40px; }
    .compare-type-row { min-height: 40px; padding: 8px 6px; font-size: 14px; }

    /* The trigger lives in a horizontally-scrolling strip (.compare-topbar-actions),
       so it can sit anywhere along the x-axis when tapped - anchoring the panel to
       its position (as on desktop) sends it off the right edge of the screen. Pin
       it to the viewport as a bottom sheet instead, matching .compare-modal's own
       mobile treatment, so it is always fully visible and full width. */
    .compare-type-panel {
        position: fixed;
        top: auto;
        left: 0;
        right: 0;
        bottom: 0;
        width: 100%;
        max-width: none;
        max-height: 65vh;
        border-radius: 12px 12px 0 0;
        border-left: 0;
        border-right: 0;
        border-bottom: 0;
        box-shadow: 0 -8px 24px rgba(0, 0, 0, 0.5);
        padding: 12px 14px calc(14px + env(safe-area-inset-bottom, 0px));
        z-index: 9550;
    }
    .compare-type-search { font-size: 14px; padding: 10px 12px; }
    .compare-type-clear { font-size: 13px; padding: 8px 4px; }

    /* The view toolbars stay one horizontally-scrollable strip rather than
       wrapping into three rows - on a 667px screen those rows would cost more
       of the view than the controls are worth. */
    .compare-toolbar { padding: 8px 12px; gap: 8px; }
    .compare-toolbar .compare-btn { min-height: 40px; }
    .compare-seg button { min-height: 36px; padding: 8px 12px; font-size: 13px; }
    .compare-toolbar-search { min-height: 40px; font-size: 14px; flex-basis: 180px; }
    .compare-toolbar-check { min-height: 40px; }
    /* Anchored to the input on desktop; a viewport-width sheet on a phone, for the
       same reason the finding-type panel is (it can sit anywhere along the strip). */
    .compare-picker-panel {
        position: fixed; top: auto; left: 0; right: 0; bottom: 0;
        width: 100%; max-width: none; max-height: 60vh;
        border-radius: 12px 12px 0 0; border-left: 0; border-right: 0; border-bottom: 0;
        padding: 12px 14px calc(14px + env(safe-area-inset-bottom, 0px));
        z-index: 9550;
    }
    .compare-picker-panel button { min-height: 44px; font-size: 14px; }

    /* Reading beside the label, slider on its own full-width row: a 120px slider
       next to a label is not something a thumb can place accurately. */
    .compare-tolerance-row { grid-template-columns: 1fr 90px; row-gap: 6px; }
    .compare-tolerance-row output { grid-column: 2; grid-row: 1; }
    .compare-tolerance-row input[type="range"] { grid-column: 1 / -1; grid-row: 2; min-height: 32px; }
    .compare-canvas-empty { padding: 24px 16px; }
    .compare-canvas-empty h3 { font-size: 16px; }

    .compare-row { height: 44px; }
    .compare-finding { padding: 16px; }
    .compare-filelist button { width: 36px; height: 36px; }

    /* --- the inspector becomes a bottom sheet. As a 400px rail it covered a
       375px phone completely. */
    .compare-inspector {
        position: fixed;
        left: 0; right: 0; bottom: 0; top: auto;
        width: 100%;
        flex: none;
        height: 74vh;
        max-height: 74vh;
        border-left: 0;
        border-top: 1px solid var(--border-default);
        border-radius: 12px 12px 0 0;
        box-shadow: 0 -8px 24px rgba(0, 0, 0, 0.6);
        z-index: 9550;
        padding: 18px 16px 32px;
    }

    /* Already full width as a bottom sheet - resizing the rail means nothing
       here. */
    .compare-inspector-resizer { display: none; }

    .compare-sheet-close {
        display: block;
        position: sticky;
        top: 0;
        margin-left: auto;
        width: 40px; height: 40px;
        border-radius: 8px;
        border: 1px solid var(--compare-control-border);
        background: var(--bg-primary);
        color: var(--text-secondary);
        font-size: 16px;
        cursor: pointer;
        z-index: 2;
    }

    /* --- tree panes stack. Row heights stay identical between them, so the
       mirrored scroll sync keeps working exactly as it does side by side. */
    .compare-split { flex-direction: column; }

    .compare-pane {
        border-right: 0;
        border-bottom: 1px solid var(--border-default);
        flex: 1 1 50%;
        min-height: 0;
    }

    .compare-pane:last-child { border-bottom: 0; }
    .compare-pane-header { padding: 10px 14px; font-size: 12px; }

    /* The canvas panes stack for the same reason: half of a narrow screen is not
       wide enough for a left-to-right alias chain to be readable at all. */
    .compare-canvas-panes { grid-template-columns: 1fr; }
    .compare-canvas-pane,
    .compare-canvas-pane[data-side="code"] {
        border-right: 0;
        border-bottom: 1px solid var(--border-default);
    }
    .compare-canvas-pane:last-child { border-bottom: 0; }

    .compare-canvas-controls { right: 12px; bottom: 12px; flex-direction: row; }
    .compare-canvas-controls .compare-icon-btn { width: 44px; height: 44px; font-size: 18px; }

    /* --- report and modals */
    .compare-report { padding: 16px 14px 48px; }
    .compare-summary-bar { flex-direction: column; }
    .compare-summary-hero { padding: 16px 18px; }
    .compare-summary-div { width: auto; height: 1px; margin: 0; }
    .compare-summary-datagrid { grid-template-columns: 1fr; }
    .compare-summary-row { padding: 10px 18px; }

    .compare-note { line-height: 1.65; }
    .compare-sides-table td.field { width: 64px; }

    .compare-modal-backdrop { padding: 0; align-items: flex-end; }
    .compare-modal { width: 100%; max-height: 92vh; border-radius: 12px 12px 0 0; }
    /* The fixed desktop height fights the bottom-sheet's own dynamic sizing on
       a small screen - let it shrink-to-content here, same as any other modal. */
    .compare-modal--tabs { height: auto; }
    .compare-modal .body { padding: 16px 14px; }
    .compare-modal footer { padding: 14px; }
    .compare-modal footer .compare-btn { flex: 1 1 auto; }
    /* The mode-mapping table has four columns; let it scroll rather than crush
       them. Scoped to the modal - the inspector's values-by-mode table has three
       columns and must fit the sheet, not force it wider than the screen. */
    .compare-preview { overflow-x: auto; }
    .compare-modal .compare-preview table,
    .compare-modal .compare-mode-table { min-width: 460px; }

    .compare-inspector .compare-mode-table th,
    .compare-inspector .compare-mode-table td { padding: 9px 6px; }
    .compare-inspector .compare-mode-table { table-layout: fixed; width: 100%; }
    .compare-inspector .compare-mode-table td { word-break: break-word; }

    .compare-empty { padding: 24px 16px; gap: 12px; }
    .compare-empty h3 { font-size: 17px; }
    .compare-empty p { font-size: 13px; }
    .compare-dropzone { padding: 24px 20px; width: 100%; }
    .compare-toast { left: 12px; right: 12px; transform: none; text-align: center; }
}
