/* =========================================================================
   TOKENS (TEL-47)
   The televiewer keeps its short local names, but every surface / ink / border
   value now ALIASES the shared palette in /graiph-tokens.css. That is what makes
   the light theme work: themeManager.js puts `light-theme` on <html>, the shared
   tokens flip, and this whole file follows — there is no second palette here to
   keep in sync.

   Only the televiewer-specific tokens below (--tv-*) carry their own values, and
   only those need a light-theme override further down.
   ========================================================================= */
:root {
    /* Surfaces */
    --bg-primary: var(--graiph-bg-primary);
    --bg-secondary: var(--graiph-bg-secondary);
    --bg-panel: var(--graiph-bg-surface);
    --bg-control: var(--graiph-bg-elevated);
    --bg-hover: var(--graiph-bg-hover);
    /* One step off --bg-panel in BOTH directions — lighter than the panel in dark,
       a shade darker than white in light. (This name used to be referenced only as
       `var(--bg-elevated, rgba(255,255,255,.03))`: undefined, so the white-on-white
       fallback always applied and would have vanished in light mode.) */
    --bg-elevated: var(--graiph-bg-hover);

    /* Accent — active / validated state only (design system) */
    --accent-primary: var(--graiph-accent);
    --accent-dim: var(--graiph-accent-hover);
    --accent-muted: var(--graiph-accent-muted);
    /* Ink ON an accent fill — the shared --graiph-accent-ink token (promoted from
       here by TEL-53) carries the value and the reasoning. */
    --accent-ink: var(--graiph-accent-ink);
    /* The accent used AS TEXT — the shared token darkens it for the light theme
       (TEL-50); the FILL stays --accent-primary in both. */
    --accent-text: var(--graiph-accent-text);

    /* Ink + borders */
    --text-primary: var(--graiph-text-primary);
    --text-secondary: var(--graiph-text-secondary);
    --text-dim: var(--graiph-text-muted);
    --gray-dark: var(--graiph-gray-dark);
    --gray-light: var(--graiph-gray-light);
    --border: var(--graiph-border);

    /* Alerts */
    --danger: var(--graiph-alert-error);
    --warning: var(--graiph-alert-warning);
    --info: var(--graiph-alert-info);

    --font: var(--graiph-font);

    /* Alpha tints of the semantic colours. One value is correct in both themes:
       they composite over whatever surface is underneath. */
    --danger-muted: rgba(229, 57, 53, 0.15);
    --warning-muted: rgba(245, 166, 35, 0.15);
    --warning-border: rgba(245, 166, 35, 0.35);
    --info-muted: rgba(33, 150, 243, 0.15);
    --info-tint: rgba(33, 150, 243, 0.1);
    --info-tint-weak: rgba(33, 150, 243, 0.05);

    /* --- Televiewer source modality: OTV optical / ATV acoustic ---------------
       Data-bearing, so the hue is fixed and only darkened for the light theme,
       where the saturated versions fall under 4.5:1 as 9–11px label text. */
    --tv-otv: #2196F3;
    --tv-otv-muted: var(--info-muted);
    --tv-otv-border: rgba(33, 150, 243, 0.3);
    --tv-atv: #F5A623;
    --tv-atv-muted: var(--warning-muted);
    --tv-atv-border: rgba(245, 166, 35, 0.3);

    /* Selected structure. Also read by the canvas renderers via
       core/theme-colors.js, so the 2D strip, the tray rows and the table row all
       highlight in one colour. */
    --tv-selected: #4ecdc4;
    --tv-selected-muted: rgba(78, 205, 196, 0.15);
    --tv-selected-border: rgba(78, 205, 196, 0.3);
    /* Outer glow ring on the selected structure in the 3D scene. */
    --tv-selected-halo: #00ffff;

    /* Default structure colour — mirrors CONFIG.STRUCTURE_TYPES.fracture, which
       stays the source of truth for the canvases (data colour, never themed). */
    --tv-fracture: #E06060;
    --tv-fracture-muted: rgba(224, 96, 96, 0.15);

    /* Floating panels over the 3D scene sit on a canvas, not on a CSS surface, so
       they carry their own scrim. */
    --tv-overlay-panel: rgba(0, 0, 30, 0.9);

    /* --- Canvas-only chrome (read through core/theme-colors.js) -------------- */
    /* Backdrop behind a televiewer strip / tray row image. */
    --tv-strip-backdrop: #111114;
    /* Stereonet + depth-scale rule work: two weights of grid, one ink for labels. */
    --tv-grid-strong: var(--graiph-border-hover);
    --tv-grid-weak: var(--graiph-border);
    --tv-axis-ink: var(--graiph-text-secondary);
    /* 3D scene: backdrop, ground plane, the two GridHelper weights, tube end caps
       and the hole/depth label sprites. */
    --tv-scene-bg: #00001e;
    --tv-scene-ground: #0a0a2e;
    --tv-scene-grid-strong: #444466;
    --tv-scene-grid-weak: #333355;
    --tv-scene-cap: #444444;
    /* Depth reference planes, rings and their labels. */
    --tv-scene-depth: #2196F3;
    --tv-scene-label-bg: rgba(0, 0, 30, 0.8);
    --tv-scene-label-ink: #ffffff;
}

/* =========================================================================
   LIGHT THEME — only the televiewer's own tokens. Everything else flips via
   graiph-tokens.css, exactly like the other interfaces.
   ========================================================================= */
html.light-theme {
    /* The accent + alert inks used to be darkened here. They now darken in
       graiph-tokens.css for every interface (TEL-50), so --accent-text / --warning /
       --info / --danger follow from the :root aliases above with nothing to restate.

       The --tv-* data hues below stay local ON PURPOSE: they are decoupled from the
       alert tokens so a future change to --info cannot silently change what OTV means.
       Same hues, dark enough to stay readable as small text on white. */
    --tv-otv: #1565C0;
    --tv-atv: #A05F00;
    --tv-fracture: #C0392B;
    --tv-selected: #10796F;
    --tv-selected-halo: #0E6A62;

    /* Panels floating over a now-light 3D scene invert their scrim. */
    --tv-overlay-panel: rgba(255, 255, 255, 0.93);

    --tv-strip-backdrop: #E4E5E9;
    --tv-scene-bg: #E9EAEE;
    --tv-scene-ground: #C7CAD2;
    --tv-scene-grid-strong: #A8AAB4;
    --tv-scene-grid-weak: #C4C6CE;
    --tv-scene-cap: #A8AAB0;
    --tv-scene-depth: #1565C0;
    --tv-scene-label-bg: rgba(255, 255, 255, 0.88);
    --tv-scene-label-ink: #1a1a1e;
}

* { margin: 0; padding: 0; box-sizing: border-box; }

body {
    font-family: var(--font);
    background: var(--bg-primary);
    color: var(--text-primary);
    overflow: hidden;
    height: 100vh;
    display: flex;
    flex-direction: column;
}

/* =========================================================================
   HEADER (TEL-46)
   The bar itself is the shared .graiph-header from /shared/header.css, and the
   selectors are the shared combobox component — nothing to restyle here. What
   remains is televiewer-only: the view tabs, the rich hole rows inside the
   drillhole dropdown, and the one responsive override the tabs need.
   ========================================================================= */

/* combobox.css floors `.gh-center .combobox-container` at 140px and, being loaded
   after header.css, wins the specificity TIE against the shared responsive ladder
   (which tries min-width:0 below 1000px). The other interfaces have a wide centre
   zone so it never bites; here the zone shares the bar with the view tabs, so the
   two selectors overflowed and painted over the first tab. Let them shrink — the
   ladder's max-width still caps them. */
.tv-header .gh-center .combobox-container {
    min-width: 0;
}

/* The hole rows carry more than a name (depth + status icon), and the shared
   responsive ladder squeezes the input down to 140px at ≤1000px — which would
   clip them. The panel keeps its own floor, as the old dropdown did. */
#drillholeCombobox .combobox-dropdown {
    min-width: 260px;
    right: auto;
}

/* Hole rows in the drillhole combobox dropdown: name (+ OTV/ATV tags) on the
   left, depth + status icon on the right. Built by renderDrillholeOption(). */
.combobox-option.tv-dh-option {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 10px;
}

.tv-dh-name { font-size: 12px; font-weight: 500; }

.tv-dh-meta {
    display: flex;
    align-items: center;
    gap: 5px;
    font-size: 11px;
    color: var(--text-dim);
    flex-shrink: 0;
}

.tv-dh-depth { color: var(--text-secondary); }
.dh-source-tag {
    display: inline-block;
    padding: 0 3px;
    border-radius: 2px;
    font-size: 9px;
    font-weight: 600;
    background: var(--tv-otv-muted);
    color: var(--tv-otv);
    vertical-align: middle;
    margin-left: 2px;
}
.dh-source-tag[data-source="ATV"] {
    background: var(--tv-atv-muted);
    color: var(--tv-atv);
}

/* Buttons */
.btn {
    font-family: var(--font);
    font-size: 12px;
    font-weight: 500;
    padding: 6px 12px;
    border: 1px solid var(--border);
    border-radius: 4px;
    cursor: pointer;
    background: var(--bg-control);
    color: var(--text-primary);
    transition: background 0.15s;
}
.btn:hover { background: var(--bg-hover); }
.btn:disabled { opacity: 0.4; cursor: not-allowed; }

.btn-accent {
    background: var(--accent-primary);
    color: var(--accent-ink);
    border-color: var(--accent-primary);
    font-weight: 700;
}
.btn-accent:hover { background: var(--accent-dim); }

.btn-secondary { background: transparent; }

.btn-danger { border-color: var(--danger); color: var(--danger); background: transparent; }
.btn-danger:hover { background: var(--danger-muted); }

.btn-sm { padding: 4px 8px; font-size: 11px; }

/* Main Layout */
#app-main {
    flex: 1;
    display: flex;
    overflow: hidden;
}

/* Form Controls (used by upload modal) */
.control-group {
    margin-bottom: 8px;
}

.control-group label {
    display: block;
    font-size: 11px;
    color: var(--text-secondary);
    margin-bottom: 3px;
}

input[type="number"], select {
    width: 100%;
    padding: 4px 8px;
    background: var(--bg-control);
    border: 1px solid var(--border);
    border-radius: 4px;
    color: var(--text-primary);
    font-family: var(--font);
    font-size: 12px;
}

select {
    cursor: pointer;
}

.modal select {
    margin-bottom: 8px;
}

/* Modal */
.modal {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background: var(--graiph-overlay);
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 1000;
}

.modal.hidden { display: none; }

.modal-content {
    background: var(--bg-panel);
    border: 1px solid var(--border);
    border-radius: 8px;
    width: 560px;
    max-height: 80vh;
    overflow-y: auto;
}
.modal-content-wide {
    width: 900px;
    max-width: 95vw;
}

.modal-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 16px;
    border-bottom: 1px solid var(--border);
}

.modal-header h3 { font-size: 14px; }

.modal-close {
    background: none;
    border: none;
    color: var(--text-secondary);
    font-size: 20px;
    cursor: pointer;
}

.modal-body { padding: 16px; }

/* Upload Zone */
.upload-zone {
    border: 2px dashed var(--border);
    border-radius: 8px;
    padding: 40px;
    text-align: center;
    cursor: pointer;
    color: var(--text-secondary);
    margin-bottom: 16px;
    transition: border-color 0.2s;
}

.upload-zone:hover,
.upload-zone.dragover {
    border-color: var(--accent-primary);
    color: var(--text-primary);
}

/* Compact variant for the extraction sidebar (TEL-48): a 220px column cannot spend
   ~120px on a drop target that is used once per document while the PDF library under it
   is used constantly. One row, same drag-and-drop and click-to-browse behaviour.
   Scoped to the sidebar — the upload MODAL keeps the full-size zone. */
.upload-zone.upload-zone-compact {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    padding: 10px 8px;
    margin-bottom: 12px;
    font-size: 11px;
    line-height: 1.2;
    text-align: left;
}
.upload-zone.upload-zone-compact p { margin: 0; }
.upload-zone.upload-zone-compact svg { flex: 0 0 auto; }

.upload-options {
    display: grid;
    grid-template-columns: 1fr 1fr 1fr;
    gap: 12px;
    margin-bottom: 16px;
}

.upload-hint {
    font-size: 11px;
    color: var(--text-dim);
    text-align: center;
    margin: 8px 0;
}

#upload-status {
    margin-top: 12px;
    font-size: 12px;
    color: var(--text-secondary);
}

#crop-preview-canvas {
    width: 100%;
    border: 1px solid var(--border);
    border-radius: 4px;
    cursor: crosshair;
}

/* 3-page crop editor */
.crop-editor.hidden { display: none; }
.crop-page-panels {
    display: grid;
    grid-template-columns: 1fr 1fr 1fr;
    gap: 12px;
    margin-bottom: 12px;
}
.crop-page-panel {
    border: 1px solid var(--border);
    border-radius: 6px;
    padding: 8px;
}
.crop-page-panel h4 {
    font-size: 11px;
    font-weight: 600;
    color: var(--text-secondary);
    margin: 0 0 6px;
    text-transform: uppercase;
    letter-spacing: 0.5px;
}
.crop-canvas-wrap {
    position: relative;
    margin-bottom: 8px;
}
.crop-canvas {
    width: 100%;
    border: 1px solid var(--border);
    border-radius: 4px;
    cursor: crosshair;
}
.crop-page-fields {
    display: flex;
    flex-direction: column;
    gap: 6px;
}
.crop-page-fields .control-group {
    display: flex;
    align-items: center;
    gap: 6px;
}
.crop-page-fields .control-group label {
    font-size: 10px;
    min-width: 70px;
    color: var(--text-dim);
}
.crop-page-fields .control-group input {
    flex: 1;
    min-width: 0;
}

/* Header Nav — the 5 view tabs, a segmented control inside the header's
   .gh-actions slot. Sized off the shared tokens so it sits on the same grid as
   the .gh-btn controls beside it. */
.header-nav {
    display: flex;
    gap: 2px;
    background: var(--graiph-bg-primary, var(--bg-primary));
    border: 1px solid var(--graiph-border, var(--border));
    border-radius: var(--graiph-radius-md, 6px);
    padding: 2px;
    flex-shrink: 0;
}

.nav-tab {
    font-family: var(--graiph-font, var(--font));
    font-size: var(--graiph-font-sm, 12px);
    font-weight: 500;
    padding: 4px 12px;
    border: none;
    border-radius: var(--graiph-radius-sm, 4px);
    cursor: pointer;
    background: transparent;
    color: var(--graiph-text-secondary, var(--text-secondary));
    white-space: nowrap;
    transition: background 0.15s, color 0.15s;
}
.nav-tab:hover {
    color: var(--graiph-text-primary, var(--text-primary));
    background: var(--graiph-bg-hover, var(--bg-hover));
}
/* Active view = the raised surface, and nothing else. No accent underline: which view
   you are looking at is already the loudest fact on screen, so the tab only has to say
   which one it is — the accent is reserved for state the user has to notice. */
.nav-tab.active {
    color: var(--graiph-text-primary, var(--text-primary));
    background: var(--graiph-bg-elevated, var(--bg-control));
}

/* View containers */
.view-container {
    flex: 1;
    display: flex;
    overflow: hidden;
}
.view-container.hidden { display: none !important; }

/* Overview Dashboard */
.overview-dashboard {
    flex: 1;
    overflow-y: auto;
    padding: 20px 28px;
}

.dashboard-header {
    margin-bottom: 20px;
}

.dashboard-header h2 {
    font-size: 18px;
    font-weight: 700;
    margin-bottom: 2px;
}

.dashboard-sub {
    font-size: 12px;
    color: var(--text-dim);
}

/* Summary Cards */
.stats-cards {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: 12px;
    margin-bottom: 24px;
}

.stat-card {
    background: var(--bg-secondary);
    border: 1px solid var(--border);
    border-radius: 6px;
    padding: 14px 16px;
}
.stat-card-dim {
    opacity: 0.35;
}

.stat-value {
    font-size: 28px;
    font-weight: 700;
    color: var(--accent-text);
    line-height: 1;
}

.stat-unit {
    font-size: 14px;
    font-weight: 500;
    color: var(--text-secondary);
}

.stat-label {
    font-size: 11px;
    color: var(--text-secondary);
    margin-top: 4px;
    text-transform: uppercase;
    letter-spacing: 0.5px;
}

.stat-sub {
    font-size: 10px;
    color: var(--text-dim);
    margin-top: 2px;
}

/* Stats Grid */
.stats-grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 20px;
}

.stats-col {
    display: flex;
    flex-direction: column;
    gap: 20px;
}

.stats-section {
    background: var(--bg-secondary);
    border: 1px solid var(--border);
    border-radius: 6px;
    padding: 14px 16px;
}

.stats-title {
    font-size: 12px;
    font-weight: 600;
    color: var(--text-secondary);
    margin-bottom: 10px;
    text-transform: uppercase;
    letter-spacing: 0.5px;
}

.stats-empty {
    font-size: 12px;
    color: var(--text-dim);
    text-align: center;
    padding: 16px 0;
}

.stats-loading {
    display: flex;
    align-items: center;
    justify-content: center;
    height: 200px;
    color: var(--text-dim);
    font-size: 13px;
}

.stats-error {
    display: flex;
    align-items: center;
    justify-content: center;
    height: 200px;
    color: var(--danger);
    font-size: 13px;
}

/* Distribution Bars */
.dist-row {
    display: grid;
    grid-template-columns: 90px 1fr 40px 42px;
    align-items: center;
    gap: 8px;
    margin-bottom: 5px;
}

.dist-label {
    font-size: 11px;
    color: var(--text-primary);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.dist-bar-track {
    height: 14px;
    background: var(--bg-primary);
    border-radius: 2px;
    overflow: hidden;
}

.dist-bar-fill {
    height: 100%;
    border-radius: 2px;
    min-width: 2px;
    transition: width 0.3s ease;
}

.dist-count {
    font-size: 11px;
    color: var(--text-primary);
    text-align: right;
    font-weight: 600;
}

.dist-pct {
    font-size: 10px;
    color: var(--text-dim);
    text-align: right;
}

/* Depth Coverage */
.depth-scale {
    display: flex;
    justify-content: space-between;
    font-size: 9px;
    color: var(--text-dim);
    margin-bottom: 6px;
    padding: 0 96px 0 0;
    margin-left: 90px;
}

.depth-row {
    display: grid;
    grid-template-columns: 86px 1fr 48px;
    align-items: center;
    gap: 8px;
    margin-bottom: 3px;
}

.depth-name {
    font-size: 11px;
    color: var(--text-primary);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.depth-bar-track {
    height: 10px;
    background: var(--bg-primary);
    border-radius: 2px;
    position: relative;
}

.depth-bar-fill {
    position: absolute;
    top: 0;
    height: 100%;
    background: var(--info);
    border-radius: 2px;
    opacity: 0.7;
}

.depth-value {
    font-size: 10px;
    color: var(--text-dim);
    text-align: right;
}

/* Orientation Scatter */
.orientation-scatter {
    display: block;
    margin: 0 auto;
}

/* Depth Histogram */
.hist-chart {
    display: flex;
    align-items: flex-end;
    gap: 3px;
    height: 100px;
    padding: 0 4px;
}

.hist-col {
    flex: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
}

.hist-bar-wrap {
    width: 100%;
    height: 80px;
    display: flex;
    align-items: flex-end;
}

.hist-bar {
    width: 100%;
    background: var(--accent-primary);
    border-radius: 2px 2px 0 0;
    opacity: 0.75;
    min-height: 2px;
}

.hist-label {
    font-size: 8px;
    color: var(--text-dim);
    margin-top: 3px;
    white-space: nowrap;
}

.hist-axis-label {
    text-align: center;
    font-size: 10px;
    color: var(--text-dim);
    margin-top: 4px;
}

/* Correlation Matrix */
.matrix-table {
    width: 100%;
    border-collapse: collapse;
    font-size: 11px;
}

.matrix-table th {
    padding: 4px 6px;
    font-size: 10px;
    font-weight: 500;
    text-transform: capitalize;
    border-bottom: 1px solid var(--border);
}

.matrix-rock {
    padding: 4px 6px;
    text-align: left;
    white-space: nowrap;
    display: flex;
    align-items: center;
    gap: 5px;
}

.matrix-cell {
    text-align: center;
    padding: 4px;
    font-size: 11px;
    color: var(--text-primary);
    border-radius: 2px;
}

.matrix-total {
    text-align: center;
    padding: 4px;
    font-weight: 600;
    color: var(--text-secondary);
    border-left: 1px solid var(--border);
}

.matrix-density {
    text-align: center;
    padding: 4px;
    font-weight: 600;
    color: var(--accent-text);
    font-size: 11px;
    white-space: nowrap;
}

.matrix-meters {
    text-align: center;
    padding: 4px;
    font-size: 10px;
    color: var(--text-dim);
    white-space: nowrap;
}

.rock-dot {
    display: inline-block;
    width: 8px;
    height: 8px;
    border-radius: 50%;
    flex-shrink: 0;
}

/* Lithology Strips */
.lith-strips-container {
    display: flex;
    gap: 8px;
    padding: 8px 4px;
    justify-content: space-evenly;
}

.lith-column {
    display: flex;
    flex-direction: column;
    align-items: center;
    flex: 1;
    max-width: 60px;
}

.lith-strip {
    position: relative;
    width: 100%;
    border: 1px solid var(--border);
    border-radius: 2px;
    overflow: hidden;
}

.lith-interval {
    position: absolute;
    left: 0;
    right: 0;
}

.lith-name {
    font-size: 9px;
    color: var(--text-dim);
    margin-top: 4px;
    max-width: 80px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.lith-scale {
    display: flex;
    justify-content: space-between;
    font-size: 9px;
    color: var(--text-dim);
    padding: 0 4px;
    margin-bottom: 4px;
}

.lith-legend {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    margin-top: 8px;
    padding: 0 4px;
}

.lith-legend-item {
    display: flex;
    align-items: center;
    gap: 4px;
    font-size: 10px;
    color: var(--text-secondary);
}

/* Detail Panel */
.detail-panel {
    width: 520px;
    background: var(--bg-secondary);
    border-left: 1px solid var(--border);
    display: flex;
    flex-direction: column;
    flex-shrink: 0;
    /* The panel is a fixed-height column; scrolling belongs to .detail-content, so the
       header and the tab bar stay put. It used to be `overflow-y: auto`, which let the
       whole panel grow to its content — the televiewer strip is tens of thousands of
       pixels tall, so the panel scrolled instead of the strip (TEL-51). */
    overflow: hidden;
}
.detail-panel.hidden { display: none; }

.detail-header {
    padding: 14px 16px;
    border-bottom: 1px solid var(--border);
}

.detail-title {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 6px;
}

.detail-title h3 {
    font-size: 15px;
    font-weight: 700;
}

.detail-meta {
    font-size: 11px;
    color: var(--text-secondary);
    margin-top: 3px;
    display: flex;
    gap: 12px;
}

.detail-sources {
    flex-wrap: wrap;
    gap: 4px;
}

.tv-source-badge {
    display: inline-block;
    padding: 1px 6px;
    border-radius: 3px;
    font-size: 10px;
    font-weight: 500;
    background: var(--tv-otv-muted);
    color: var(--tv-otv);
    border: 1px solid var(--tv-otv-border);
}
.tv-source-badge[data-source="ATV"] {
    background: var(--tv-atv-muted);
    color: var(--tv-atv);
    border-color: var(--tv-atv-border);
}

.detail-error {
    padding: 20px;
    color: var(--danger);
    font-size: 12px;
    text-align: center;
}

/* Status badge */
.status-badge {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    font-size: 11px;
    font-weight: 600;
}

/* Tabs */
.detail-tabs {
    display: flex;
    border-bottom: 1px solid var(--border);
    padding: 0 12px;
}

.tab-btn {
    font-family: var(--font);
    font-size: 11px;
    font-weight: 500;
    padding: 8px 12px;
    border: none;
    border-bottom: 2px solid transparent;
    cursor: pointer;
    background: transparent;
    color: var(--text-secondary);
    transition: all 0.15s;
}
.tab-btn:hover { color: var(--text-primary); }
.tab-btn.active {
    color: var(--accent-text);
    border-bottom-color: var(--accent-primary);
}

.detail-content {
    flex: 1;
    /* A flex item's min-height is `auto`, i.e. it refuses to shrink below its content.
       Without this the whole chain down to the strip grows to the strip's full image
       height and `flex: 1` never bounds anything (TEL-51). */
    min-height: 0;
    overflow-y: auto;
    display: flex;
    flex-direction: column;
}

.tab-content { display: none; padding: 12px 16px; }
.tab-content.active { display: flex; flex-direction: column; }
.tab-content[data-tab="televiewer"] { padding: 8px; }
/* The televiewer tab is the one tab that must FIT the panel rather than extend it: the
   strip scrolls inside its own window, beside a depth ruler drawn for that window. The
   table tabs keep their natural height and scroll .detail-content as before. */
.tab-content[data-tab="televiewer"].active { flex: 1; min-height: 0; }

.tab-empty {
    padding: 24px;
    text-align: center;
    color: var(--text-dim);
    font-size: 12px;
}

/* Data tables */
.data-table {
    width: 100%;
    border-collapse: collapse;
    font-size: 11px;
}

.data-table th {
    text-align: left;
    padding: 6px 8px;
    font-weight: 600;
    color: var(--text-secondary);
    border-bottom: 1px solid var(--border);
    white-space: nowrap;
    position: sticky;
    top: 0;
    background: var(--bg-secondary);
}

.data-table td {
    padding: 5px 8px;
    border-bottom: var(--graiph-border-subtle);
    color: var(--text-primary);
}

.data-table tr:hover td { background: var(--bg-hover); cursor: pointer; }

.data-table tr.frac-row.selected td {
    background: var(--tv-selected-muted);
    border-bottom-color: var(--tv-selected-border);
}

/* Fracture Editor */
.frac-editor {
    background: var(--bg-control);
    border: 1px solid var(--accent-primary);
    border-radius: 6px;
    padding: 10px 12px;
    margin-bottom: 10px;
}
.frac-editor.hidden { display: none; }

.frac-editor-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 8px;
    font-size: 12px;
    font-weight: 600;
    color: var(--accent-text);
}

.frac-slider-group {
    margin-bottom: 6px;
}

.frac-slider-group label {
    display: flex;
    justify-content: space-between;
    font-size: 11px;
    color: var(--text-secondary);
    margin-bottom: 2px;
}

.frac-slider-group input[type="range"] {
    width: 100%;
    height: 4px;
    -webkit-appearance: none;
    background: var(--bg-primary);
    border-radius: 2px;
    outline: none;
}
/* (0,2,1) out-specifies the shared /shared/focus.css fallback (0,1,1), so the
   keyboard ring must be restored at the same specificity (TEL-55). */
.frac-slider-group input[type="range"]:focus-visible {
    outline: 2px solid var(--graiph-border-focus, #33CC66);
    outline-offset: 2px;
}

.frac-slider-group input[type="range"]::-webkit-slider-thumb {
    -webkit-appearance: none;
    width: 14px;
    height: 14px;
    border-radius: 50%;
    background: var(--accent-primary);
    cursor: pointer;
}

.frac-editor-status {
    font-size: 10px;
    color: var(--text-dim);
    margin-top: 4px;
}

.frac-editor-header .btn {
    font-size: 10px;
    padding: 2px 8px;
}

.type-dot {
    display: inline-block;
    width: 8px;
    height: 8px;
    border-radius: 50%;
    margin-right: 4px;
    vertical-align: middle;
}

/* Fracture toggle */
.tv-fracture-toggle {
    font-family: var(--font);
    font-size: 11px;
    color: var(--accent-text);
    background: transparent;
    border: 1px solid var(--accent-primary);
    border-radius: 3px;
    padding: 2px 8px;
    cursor: pointer;
    transition: all 0.15s;
}
.tv-fracture-toggle:hover {
    background: var(--accent-muted);
}
.tv-fracture-toggle:not(.active) {
    color: var(--text-dim);
    border-color: var(--border);
}

/* Inline fracture editor — matches .frac-editor visual language */
.tv-inline-editor {
    position: absolute;
    right: 6px;
    width: 196px;
    background: var(--bg-control);
    border: 1px solid var(--accent-primary);
    border-radius: 4px;
    padding: 8px 10px;
    z-index: 10;
    box-shadow: var(--graiph-shadow-lg);
}
.tv-inline-editor.hidden { display: none; }

.tv-ie-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 6px;
}
.tv-ie-title {
    font-size: 11px;
    font-weight: 600;
    color: var(--accent-text);
}
.tv-ie-actions {
    display: flex;
    gap: 4px;
    flex-shrink: 0;
}

.tv-ie-row {
    display: flex;
    flex-direction: column;
    gap: 5px;
}
.tv-ie-field {
    display: flex;
    align-items: center;
    gap: 5px;
}
.tv-ie-field label {
    font-size: 11px;
    color: var(--text-secondary);
    width: 34px;
    flex-shrink: 0;
}
.tv-ie-field input[type="number"] {
    width: 52px;
    background: var(--bg-primary);
    border: 1px solid var(--border);
    border-radius: 3px;
    color: var(--text-primary);
    font-size: 11px;
    font-family: var(--font);
    padding: 3px 5px;
}
.tv-ie-field input[type="number"]:focus {
    border-color: var(--accent-primary);
    outline: none;
}
.tv-ie-field span {
    font-size: 10px;
    color: var(--text-dim);
    flex-shrink: 0;
}
.tv-ie-range {
    flex: 1;
    min-width: 40px;
    height: 4px;
    -webkit-appearance: none;
    appearance: none;
    background: var(--bg-primary);
    border-radius: 2px;
    outline: none;
}
.tv-ie-range::-webkit-slider-thumb {
    -webkit-appearance: none;
    width: 12px;
    height: 12px;
    border-radius: 50%;
    background: var(--accent-primary);
    cursor: pointer;
}
.tv-ie-range::-moz-range-thumb {
    width: 12px;
    height: 12px;
    border-radius: 50%;
    background: var(--accent-primary);
    border: none;
    cursor: pointer;
}
.tv-ie-status {
    font-size: 10px;
    color: var(--text-dim);
    margin-top: 4px;
    min-height: 12px;
}

/* 2D Televiewer Strip */
.tv-strip-info {
    display: flex;
    justify-content: space-between;
    font-size: 11px;
    color: var(--text-secondary);
    padding: 4px 0 8px;
    border-bottom: 1px solid var(--border);
    margin-bottom: 8px;
}

.tv-strip-container {
    display: flex;
    flex: 1;
    /* `min-height: 300px` here used to be the only floor AND the only ceiling: with an
       auto-height parent, `flex: 1` just grew to the image. The parent is bounded now,
       so this is a floor again — and it must be able to shrink under it on a short
       viewport, or the panel overflows once more (TEL-51). */
    min-height: min(300px, 100%);
    border: 1px solid var(--border);
    border-radius: 4px;
    overflow: hidden;
}

.tv-strip-scale {
    flex-shrink: 0;
    background: var(--bg-primary);
    border-right: 1px solid var(--border);
}

.tv-strip-image-wrap {
    flex: 1;
    overflow-y: auto;
    overflow-x: hidden;
    /* Also painted onto the composed strip canvas in map/drillhole-mesh.js —
       both read --tv-strip-backdrop so the seam is invisible in either theme. */
    background: var(--tv-strip-backdrop);
    position: relative;
    display: flex;
    flex-direction: column;
}

.tv-strip-image {
    width: 100%;
    display: block;
    image-rendering: auto;
    flex-shrink: 0;
}

.tv-strip-divider {
    position: relative;
    height: 0;
    border-top: 1px solid var(--tv-otv);
    z-index: 2;
    overflow: visible;
}
.tv-strip-divider[data-source="ATV"] {
    border-top-color: var(--tv-atv);
}

.tv-strip-divider-label {
    position: absolute;
    top: -8px;
    left: 50%;
    transform: translateX(-50%);
    font-size: 9px;
    color: inherit;
    text-transform: uppercase;
    letter-spacing: 0.5px;
    background: var(--bg-primary);
    padding: 0 6px;
    white-space: nowrap;
}
.tv-strip-divider[data-source="OTV"] { color: var(--tv-otv); }
.tv-strip-divider[data-source="ATV"] { color: var(--tv-atv); }

.tv-strip-overlay {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    pointer-events: auto;
    cursor: crosshair;
}

/* Lithology strip (detail panel only — overview strips use overview-stats.js rules) */
.tab-content .lith-strip {
    display: flex;
    flex-direction: column;
    min-height: 400px;
    border: 1px solid var(--border);
    border-radius: 4px;
    overflow: hidden;
}

.lith-interval {
    display: flex;
    align-items: center;
    min-height: 40px;
    padding: 6px 10px;
    position: relative;
}

.lith-info {
    position: relative;
    z-index: 1;
}

/* These three sit ON a lithology band, whose colour is DATA (arbitrary rock-type hue,
   identical in both themes). White ink over a dark text-shadow is therefore the right
   answer in light mode too — deliberately not themed. */
.lith-depth {
    font-size: 10px;
    font-weight: 700;
    color: #fff;
    text-shadow: 0 1px 3px rgba(0,0,0,0.6);
}

.lith-rock {
    font-size: 12px;
    font-weight: 600;
    color: #fff;
    text-shadow: 0 1px 3px rgba(0,0,0,0.6);
}

.lith-alt, .lith-hard {
    font-size: 10px;
    color: rgba(255,255,255,0.8);
    text-shadow: 0 1px 3px rgba(0,0,0,0.6);
}

/* 3D Geo Scene */
#map-container {
    flex: 1;
    min-height: 400px;
    position: relative;
}

#map-container canvas {
    width: 100% !important;
    height: 100% !important;
    display: block;
}

#geo-controls {
    position: absolute;
    top: 12px;
    left: 12px;
    z-index: 10;
    display: flex;
    flex-direction: column;
    gap: 4px;
}

.geo-controls-row {
    display: flex;
    align-items: center;
    gap: 6px;
}

.geo-controls-group {
    display: flex;
    align-items: center;
    gap: 4px;
    background: var(--tv-overlay-panel);
    border: 1px solid var(--border);
    border-radius: 4px;
    padding: 4px 8px;
}

.geo-controls-label {
    font-size: 10px;
    color: var(--text-dim);
    margin-right: 4px;
    text-transform: uppercase;
    letter-spacing: 0.5px;
}

.geo-btn {
    font-family: var(--font);
    font-size: 11px;
    font-weight: 500;
    padding: 3px 8px;
    border: 1px solid var(--border);
    border-radius: 3px;
    cursor: pointer;
    background: transparent;
    color: var(--text-secondary);
    transition: all 0.12s;
}
.geo-btn:hover { background: var(--bg-hover); color: var(--text-primary); }
.geo-btn.active { background: var(--bg-control); color: var(--accent-text); border-color: var(--accent-primary); }

.geo-btn-data {
    font-family: var(--font);
    font-size: 11px;
    font-weight: 500;
    padding: 3px 8px;
    border: 1px solid var(--border);
    border-radius: 3px;
    cursor: pointer;
    background: transparent;
    color: var(--text-secondary);
    transition: all 0.12s;
}
.geo-btn-data:hover { background: var(--bg-hover); color: var(--text-primary); }
.geo-btn-data.active { background: var(--bg-control); color: var(--info); border-color: var(--info); }

.geo-btn-toggle {
    font-family: var(--font);
    font-size: 11px;
    font-weight: 500;
    padding: 3px 8px;
    border: 1px solid var(--border);
    border-radius: 3px;
    cursor: pointer;
    background: transparent;
    color: var(--text-dim);
    transition: all 0.12s;
}
.geo-btn-toggle:hover { color: var(--text-primary); }
.geo-btn-toggle.active { background: var(--tv-fracture-muted); color: var(--tv-fracture); border-color: var(--tv-fracture); }

.geo-btn-frac-mode {
    font-family: var(--font);
    font-size: 10px;
    font-weight: 500;
    padding: 2px 6px;
    border: 1px solid var(--border);
    border-radius: 3px;
    cursor: pointer;
    background: transparent;
    color: var(--text-dim);
    transition: all 0.12s;
    margin-left: 2px;
}
.geo-btn-frac-mode:hover { color: var(--text-primary); }
.geo-btn-frac-mode.active { background: var(--info-muted); color: var(--info); border-color: var(--info); }

#frac-debug-tooltip {
    display: none;
    position: absolute;
    z-index: 1000;
    background: var(--tv-overlay-panel);
    color: var(--text-secondary);
    font-family: 'Courier New', monospace;
    font-size: 11px;
    line-height: 1.5;
    padding: 6px 10px;
    border: 1px solid var(--border);
    border-radius: 4px;
    pointer-events: none;
    white-space: nowrap;
}
#frac-debug-tooltip b { color: var(--tv-selected); }

.geo-north-arrow {
    display: flex;
    align-items: center;
    gap: 4px;
    background: var(--tv-overlay-panel);
    border: 1px solid var(--border);
    border-radius: 4px;
    padding: 4px 8px;
    margin-left: 8px;
}

.geo-north-arrow span {
    font-size: 10px;
    font-weight: 700;
    color: var(--text-secondary);
    margin-top: 2px;
}

/* ==================== Extraction View ==================== */

.extraction-layout {
    display: grid;
    /* The settings column holds a 4-value crop matrix; at 260px each cell got 33px and a
       6-character page fraction (0.0472) rendered as an empty box (TEL-41). 320px is the
       width at which the numbers are actually readable, and the canvas still keeps ~970px
       on a 1512px viewport. */
    grid-template-columns: 220px 1fr 320px;
    flex: 1;
    min-height: 0;
    gap: 1px;
    background: var(--border);
}

.extraction-sidebar,
.extraction-meta-panel {
    background: var(--bg-panel);
    padding: 10px;
    overflow-y: auto;
    min-height: 0;
}

.sidebar-title {
    font-size: 12px;
    font-weight: 600;
    color: var(--text-secondary);
    text-transform: uppercase;
    letter-spacing: 0.5px;
    margin: 0 0 12px;
    padding-bottom: 8px;
    border-bottom: 1px solid var(--border);
}

/* Extraction: selected-hole context (target for uploads) */
.ext-hole-context {
    font-size: 12px;
    font-weight: 600;
    color: var(--accent-text);
    padding: 6px 8px;
    margin-bottom: 10px;
    background: var(--bg-elevated);
    border: 1px solid var(--border);
    border-radius: 4px;
    word-break: break-word;
}
.ext-hole-context-empty {
    color: var(--text-secondary);
    font-weight: 500;
}

/* Extraction: PDF library (browse uploaded PDFs, re-cut) */
.ext-library { margin-top: 14px; }
.ext-library-header {
    font-size: 11px;
    font-weight: 700;
    color: var(--text-secondary);
    text-transform: uppercase;
    letter-spacing: 0.5px;
    margin-bottom: 8px;
    padding-bottom: 6px;
    border-bottom: 1px solid var(--border);
}
.ext-library-empty {
    font-size: 12px;
    color: var(--text-secondary);
    padding: 6px 2px;
}
/* A borehole is the CONTAINER of its files, so it gets the visual weight of one: a
   full-width header row, not a 10px caption that reads smaller than its own contents
   (TEL-48). It sticks while its files scroll, so the answer to "whose file is this?"
   is always on screen. */
.ext-library-group { margin-bottom: 8px; }
.ext-library-group-title {
    position: sticky;
    top: -10px;                     /* cancels .extraction-sidebar's 10px padding */
    z-index: 2;
    font-size: 12px;
    font-weight: 600;
    color: var(--text-primary);
    margin: 0 0 4px;
    padding: 5px 4px;
    display: flex;
    align-items: center;
    gap: 6px;
    background: var(--bg-panel);
    border-bottom: 1px solid var(--border);
}
.ext-library-count {
    flex: 0 0 auto;
    color: var(--text-secondary);
    background: var(--bg-elevated);
    border-radius: 8px;
    padding: 0 6px;
    font-size: 10px;
}
/* Containment cue: the files hang off a rail under their hole. */
.ext-library-group-body {
    margin-left: 5px;
    padding-left: 6px;
    border-left: 1px solid var(--border);
}
.ext-library-item {
    display: flex;
    align-items: center;
    gap: 5px;
    padding: 4px 5px;
    border-radius: 4px;
    font-size: 12px;
    border: 1px solid transparent;
}
/* The filename is the point of the row — it takes the leftover width, and every other
   child stays at its natural size. Without min-width:0 a flex child refuses to shrink
   past its content and the name collapsed to one character (TEL-48). */
.ext-library-item .ext-library-name {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.ext-library-item.cuttable,
.ext-library-item.reference { cursor: pointer; }
.ext-library-item.cuttable:hover,
.ext-library-item.reference:hover {
    background: var(--bg-elevated);
    border-color: var(--border);
}
.ext-library-item.selected {
    border-color: var(--accent-primary);
    background: var(--accent-muted);
}
/* Reference PDFs open read-only (TEL-37) — muted, but clickable like a cuttable one. */
.ext-library-item.reference {
    color: var(--text-secondary);
}
/* Cut state: a mark, not a word. Fixed width so the filenames stay aligned down the
   column whether or not a row has one. */
.ext-library-cut {
    flex: 0 0 10px;
    font-size: 10px;
    line-height: 1;
    text-align: center;
    color: var(--text-dim);
}
.ext-library-cut.is-cut { color: var(--accent-text); }

/* Hole-first library grouping (TEL-44). Groups are boreholes, not document types — the
   type moved onto the row as a badge. */
.ext-library-group-title { cursor: pointer; user-select: none; }
/* The borehole an upload would target. The order is fixed (TEL-52), so this — not the
   position in the list — is what marks the current hole. A rail, not a fill: the group is
   context, and a filled header would compete with the selected FILE row inside it. */
.ext-library-group.is-selected .ext-library-group-title {
    box-shadow: inset 2px 0 0 var(--accent-primary);
    padding-left: 7px;
}
.ext-library-group.is-selected .ext-library-group-name { color: var(--accent-text); }
.ext-library-group-name {
    flex: 1 1 auto;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.ext-library-caret {
    flex: 0 0 auto;
    width: 9px;
    color: var(--text-secondary);
}
.ext-library-group-body.hidden { display: none; }
/* An unlinked PDF is a gap in the data, not a category: warning-coloured so it reads as
   something to close, never as a normal group. */
.ext-library-group.unlinked .ext-library-group-title { color: var(--warning); }
.ext-library-group.unlinked .ext-library-item {
    border-color: var(--warning-border);
}
/* Two-letter type chip (TV / AN / RP / TR) — the full word lives in the tooltip. */
.ext-library-type {
    flex: 0 0 auto;
    font-size: 9px;
    font-weight: 700;
    letter-spacing: 0.3px;
    color: var(--text-secondary);
    border: 1px solid var(--border);
    border-radius: 3px;
    padding: 0 3px;
    line-height: 14px;
}
/* The cuttable kind is the one this view acts on, so it reads a step brighter. */
.ext-library-type[data-type="televiewer"] { color: var(--text-primary); }
.ext-library-item.multi-hole .ext-library-name { font-style: italic; }
/* display:none, not opacity:0 — a hidden button that still occupies width was part of
   what squeezed the filename out of the row (TEL-48). */
.ext-library-link-btn {
    display: none;
    flex: 0 0 auto;
    background: none;
    border: none;
    color: var(--text-secondary);
    cursor: pointer;
    padding: 0 2px;
    font-size: 12px;
    line-height: 1;
}
.ext-library-item:hover .ext-library-link-btn,
.ext-library-item.selected .ext-library-link-btn,
.ext-library-group.unlinked .ext-library-link-btn { display: inline-block; }
.ext-library-link-btn:hover { color: var(--accent-text); }

.ext-link-editor {
    margin: 2px 0 6px;
    padding: 6px 7px;
    border: 1px solid var(--border);
    border-radius: 4px;
    background: var(--bg-elevated);
}
.ext-link-editor-title {
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: 0.4px;
    color: var(--text-secondary);
    margin-bottom: 4px;
}
.ext-link-editor-options {
    max-height: 160px;
    overflow-y: auto;
}
.ext-link-option {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: 12px;
    padding: 2px 0;
    cursor: pointer;
}
.ext-link-option span {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.ext-link-editor-actions {
    display: flex;
    justify-content: flex-end;
    gap: 6px;
    margin-top: 6px;
}
.ext-link-editor-actions button {
    font-size: 11px;
    padding: 3px 9px;
    border-radius: 3px;
    border: 1px solid var(--border);
    background: transparent;
    color: var(--text-secondary);
    cursor: pointer;
}
.ext-link-editor-actions button.primary {
    border-color: var(--accent-primary);
    color: var(--accent-text);
}

/* Extraction tree */
.ext-tree-company { margin-bottom: 8px; }
.ext-tree-header {
    font-size: 11px;
    font-weight: 600;
    color: var(--accent-text);
    padding: 4px 6px;
    cursor: pointer;
    display: flex;
    justify-content: space-between;
    align-items: center;
    border-radius: 3px;
}
.ext-tree-header:hover { background: var(--bg-hover); }
.ext-tree-count { font-weight: 400; color: var(--text-dim); font-size: 10px; }
.ext-tree-children.collapsed { display: none; }

.ext-tree-item {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 4px 6px 4px 16px;
    font-size: 11px;
    cursor: pointer;
    border-radius: 3px;
    color: var(--text-secondary);
}
.ext-tree-item:hover { background: var(--bg-hover); color: var(--text-primary); }
.ext-tree-item.ext-tree-selected {
    background: var(--info-muted);
    color: var(--info);
}
.ext-tree-dh { font-weight: 600; min-width: 60px; }
.ext-tree-pdf-name { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 10px; color: var(--text-dim); }
.ext-tree-size { font-size: 10px; color: var(--text-dim); white-space: nowrap; }

.ext-loading, .ext-error { font-size: 11px; color: var(--text-dim); padding: 12px; }
.ext-error { color: var(--danger); }

/* Read-only reference viewer (TEL-37).
   Hidden by default; the `ext-reference-mode` class on the layout swaps the crop editor
   AND the extraction settings panel out for it, so no cutting control can be on screen
   while a reference document is open. */
.ext-reference-viewer { display: none; }
.extraction-layout.ext-reference-mode { grid-template-columns: 220px 1fr; }
.extraction-layout.ext-reference-mode .extraction-canvas-area,
.extraction-layout.ext-reference-mode .extraction-meta-panel { display: none; }
.extraction-layout.ext-reference-mode .ext-reference-viewer {
    display: flex;
    flex-direction: column;
    background: var(--bg-primary);
    min-height: 0;
    overflow: hidden;
}
.ext-reference-bar {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 6px 12px;
    background: var(--bg-panel);
    border-bottom: 1px solid var(--border);
    flex-shrink: 0;
}
.ext-reference-name {
    font-size: 12px;
    font-weight: 600;
    color: var(--text-primary);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.ext-reference-tag {
    flex: 0 0 auto;
    font-size: 9px;
    text-transform: uppercase;
    letter-spacing: 0.3px;
    color: var(--text-secondary);
    border: 1px solid var(--border);
    border-radius: 3px;
    padding: 1px 5px;
}
.ext-reference-hint {
    flex: 1 1 auto;
    font-size: 11px;
    color: var(--text-dim);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.ext-reference-frame {
    flex: 1 1 auto;
    width: 100%;
    min-height: 0;
    border: 0;
    background: var(--bg-primary);
}
.ext-reference-status {
    flex: 0 0 auto;
    font-size: 11px;
    color: var(--text-dim);
    padding: 4px 12px;
}
.ext-reference-status:empty { display: none; }

/* Canvas area */
.extraction-canvas-area {
    background: var(--bg-primary);
    display: flex;
    flex-direction: column;
    position: relative;
    overflow: hidden;
    min-height: 0;
}

.extraction-toolbar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 6px 12px;
    background: var(--bg-panel);
    border-bottom: 1px solid var(--border);
    flex-shrink: 0;
    gap: 12px;
}

.extraction-page-nav {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: 11px;
    color: var(--text-secondary);
}

.extraction-page-type-tabs {
    display: flex;
    gap: 2px;
}
.ext-type-tab {
    font-family: var(--font);
    font-size: 11px;
    font-weight: 500;
    padding: 3px 10px;
    border: 1px solid var(--border);
    border-radius: 3px;
    cursor: pointer;
    background: transparent;
    color: var(--text-dim);
    transition: all 0.12s;
}
.ext-type-tab:hover { color: var(--text-primary); }
.ext-type-tab.active { background: var(--bg-control); color: var(--info); border-color: var(--info); }

.extraction-zoom-controls {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: 11px;
    color: var(--text-secondary);
}

.extraction-canvas-wrap {
    flex: 1;
    overflow: hidden;
    position: relative;
}

#extraction-canvas {
    display: block;
    width: 100%;
    height: 100%;
    cursor: crosshair;
}

.extraction-hint {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    font-size: 13px;
    color: var(--text-dim);
    pointer-events: none;
}

/* Meta panel */
.ext-meta-section {
    margin-bottom: 14px;
}
.ext-meta-section h4 {
    font-size: 10px;
    font-weight: 600;
    color: var(--text-dim);
    text-transform: uppercase;
    letter-spacing: 0.5px;
    margin: 0 0 6px;
}

.ext-meta-grid {
    display: grid;
    grid-template-columns: 60px 1fr;
    gap: 4px 8px;
    align-items: center;
}
.ext-meta-grid label {
    font-size: 10px;
    color: var(--text-dim);
}

.ext-input {
    font-family: var(--font);
    font-size: 11px;
    padding: 4px 6px;
    background: var(--bg-control);
    border: 1px solid var(--border);
    border-radius: 3px;
    color: var(--text-primary);
    width: 100%;
    box-sizing: border-box;
}
.ext-input:focus { border-color: var(--info); outline: none; }

.ext-info-block {
    font-size: 11px;
    line-height: 1.6;
    color: var(--text-secondary);
}
.ext-info-block b { color: var(--text-primary); }

/* Unit of the numbers in a section — page fractions are not px and not % (TEL-41). */
.ext-unit-hint {
    font-size: 9px;
    color: var(--text-dim);
    margin: -4px 0 4px;
}

/* Provenance of the crop below (TEL-30). This was a line in the shared status area, which
   the next status message wiped before the user could read it; a crop recovered from a
   previous cut is exactly the kind of thing that has to stay auditable on screen, so it is
   pinned to the section and cleared only when the crop stops being the last cut's. */
.ext-recipe-badge {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 4px;
    font-size: 9px;
    line-height: 1.3;
    color: var(--info);
    background: var(--bg-control);
    border-left: 2px solid var(--info);
    border-radius: 0 3px 3px 0;
    padding: 3px 5px;
    margin: 0 0 5px;
}
.ext-recipe-badge[hidden] { display: none; }
.ext-recipe-badge-detail {
    color: var(--text-dim);
    font-variant-numeric: tabular-nums;
}
.ext-recipe-badge-detail:empty { display: none; }

.ext-badge {
    font-size: 9px;
    font-weight: 600;
    padding: 1px 5px;
    border-radius: 3px;
    background: var(--bg-control);
    color: var(--info);
    border: 1px solid var(--info);
    text-transform: uppercase;
    vertical-align: middle;
    margin-left: 4px;
}

.ext-checkbox-wrap {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: 11px;
    color: var(--text-secondary);
    cursor: pointer;
}
.ext-checkbox-wrap input[type="checkbox"] {
    accent-color: var(--info);
}

.ext-meta-hint {
    font-size: 9px;
    color: var(--text-dim);
    margin-top: 4px;
    font-style: italic;
}

.ext-layout-actions {
    display: flex;
    gap: 6px;
    margin-top: 6px;
}
.ext-layout-actions .btn { flex: 1; }

.ext-new-layout-form {
    margin-top: 6px;
    padding: 8px;
    background: var(--bg-control);
    border: 1px solid var(--border);
    border-radius: 4px;
}
.ext-new-layout-form .ext-input { margin-bottom: 6px; }

/* Four buttons in the layout row now (Update / Rename / Delete / + New), so let them wrap
   rather than squeezing every label to two characters (TEL-35 part B). */
.ext-layout-actions { flex-wrap: wrap; }
.ext-layout-actions .btn { min-width: 60px; }
.ext-layout-actions .btn.btn-danger {
    border-color: var(--danger);
    color: var(--danger);
}
.ext-layout-actions .btn.btn-danger:disabled {
    border-color: var(--border);
    color: var(--text-dim);
}

/* "Uses layout: X (+ your changes)" — states the binding, never decorative. */
.ext-layout-binding {
    margin-top: 6px;
    font-size: 10px;
    color: var(--text-secondary);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.ext-layout-binding.modified { color: var(--warning); }

/* Batch bar: only present while rows are ticked. */
/* Source + Resolution 2-col row */
.ext-row-2col {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 10px;
}

/* DPI categorical buttons */
.ext-dpi-btns {
    display: flex;
    gap: 2px;
}
.ext-dpi-btn {
    flex: 1;
    font-family: var(--font);
    font-size: 10px;
    font-weight: 500;
    padding: 4px 0;
    border: 1px solid var(--border);
    border-radius: 3px;
    cursor: pointer;
    background: transparent;
    color: var(--text-dim);
    transition: all 0.12s;
}
.ext-dpi-btn:hover { color: var(--text-primary); }
.ext-dpi-btn.active { background: var(--bg-control); color: var(--info); border-color: var(--info); }

.ext-diameter-row {
    display: flex;
    align-items: center;
    gap: 6px;
}
.ext-input-narrow { width: 70px; flex-shrink: 0; }
.ext-preset-btns {
    display: flex;
    gap: 2px;
    flex-wrap: wrap;
}
.ext-preset-btn {
    padding: 2px 5px;
    font-size: 10px;
    border: 1px solid var(--border);
    border-radius: 3px;
    background: none;
    color: var(--text-dim);
    cursor: pointer;
}
.ext-preset-btn:hover { color: var(--text-primary); border-color: var(--text-secondary); }
.ext-preset-btn.active { color: var(--info); border-color: var(--info); }

/* Crop region table */
.ext-crop-table, .ext-mpp-table {
    width: 100%;
    border-collapse: collapse;
    font-size: 10px;
    /* Fixed layout so the value columns get a guaranteed share instead of being squeezed
       to nothing by the label and Skip columns (TEL-41). */
    table-layout: fixed;
}
/* Crop matrix: label (page type + its Skip box) | X1 Y1 X2 Y2 — the four value columns are
   equal and get the whole rest of the panel. A dedicated 46px Skip column left each value
   9px short of its own digits (TEL-41), so Skip now lives under its page-type name. */
.ext-crop-table col.ext-col-label { width: 44px; }
/* m/page matrix: label | top | btm | m/page */
.ext-mpp-table col.ext-col-label { width: 42px; }
.ext-crop-table th, .ext-mpp-table th {
    font-size: 9px;
    font-weight: 500;
    color: var(--text-dim);
    text-transform: uppercase;
    padding: 2px 3px;
    text-align: center;
}
.ext-crop-table td, .ext-mpp-table td {
    padding: 2px 2px;
}
.ext-crop-label {
    font-size: 10px;
    font-weight: 600;
    color: var(--text-secondary);
    white-space: nowrap;
    padding-right: 4px !important;
}
/* The label cell now stacks the page-type name over its Skip box (TEL-41). The box is
   scaled down so the longest translation ("Omitir") still fits the 44px column intact —
   the whole label stays clickable, so the target is the text, not the tick. */
.ext-crop-table .ext-crop-label { white-space: normal; line-height: 1.2; }
.ext-crop-table .ext-crop-label .ext-skip-label { margin-top: 1px; font-size: 8.5px; gap: 2px; }
.ext-crop-table .ext-crop-label .ext-skip-label input {
    width: 10px;
    height: 10px;
    margin: 0;
    flex: 0 0 auto;
}
.ext-crop-input, .ext-mpp-input {
    font-family: var(--font);
    font-size: 10px;
    width: 100%;
    min-width: 0;
    padding: 3px 2px;
    background: var(--bg-control);
    border: 1px solid var(--border);
    border-radius: 3px;
    color: var(--text-primary);
    box-sizing: border-box;
    /* Audit-readable numbers (TEL-41): the spinner arrows stole ~14px of a ~50px cell and
       clipped the value out of sight, and tabular figures make the three page-type rows
       line up digit-for-digit so a crop can be compared down the column. */
    text-align: center;
    font-variant-numeric: tabular-nums;
    appearance: textfield;
    -moz-appearance: textfield;
}
.ext-crop-input::-webkit-outer-spin-button,
.ext-crop-input::-webkit-inner-spin-button,
.ext-mpp-input::-webkit-outer-spin-button,
.ext-mpp-input::-webkit-inner-spin-button {
    -webkit-appearance: none;
    margin: 0;
}
/* An unset crop must read as a decision ("—"), not as a rendering failure. */
.ext-crop-input::placeholder { color: var(--text-dim); opacity: 0.7; }
.ext-crop-input:focus, .ext-mpp-input:focus { border-color: var(--info); outline: none; }

.ext-crop-row { cursor: pointer; }
.ext-crop-row:hover td { background: var(--info-tint-weak); }
.ext-crop-row.active td { background: var(--info-tint); }
.ext-crop-row.active .ext-crop-label { color: var(--info); }

.ext-skip-label {
    display: flex;
    align-items: center;
    gap: 3px;
    font-size: 9px;
    color: var(--text-dim);
    cursor: pointer;
    white-space: nowrap;
}
.ext-skip-label input { accent-color: var(--info); }

/* A page type that takes no part in this extraction (TEL-38): readable, clearly inert,
   and its own Skip box stays at full opacity so the state can be undone. */
.ext-row-disabled td { opacity: 0.4; }
.ext-row-disabled .ext-skip-label { opacity: 1; }
.ext-type-tab.ext-tab-disabled {
    opacity: 0.4;
    cursor: not-allowed;
}

.ext-meta-divider {
    border-top: 1px solid var(--border);
    margin: 12px 0;
}

.ext-result-preview {
    margin-top: 8px;
    max-height: 300px;
    overflow-y: auto;
    border: 1px solid var(--border);
    border-radius: 4px;
}
.ext-result-preview img {
    width: 100%;
    display: block;
}

.ext-status {
    margin-top: 8px;
    font-size: 11px;
    color: var(--text-secondary);
}

/* Pinned action footer (TEL-40). The settings panel scrolls, and Extract + its status used
   to be the last thing in that scroll — off-screen at 784px tall, so a disabled button and
   the message explaining it were both invisible. Sticky keeps the decision and its feedback
   on screen at every scroll position. */
.ext-actions {
    position: sticky;
    bottom: 0;
    z-index: 2;
    /* Cancel the panel's own padding so the footer spans the full column and its top border
       reads as a divider. */
    margin: 8px -10px -10px;
    padding: 8px 10px 10px;
    background: var(--bg-panel);
    border-top: 1px solid var(--border);
}

/* Unmet extraction preconditions — the same sentences doExtract() would have shown after a
   failed click, stated up front. Warning, not error: nothing has gone wrong yet. */
.ext-blockers {
    display: none;
    font-size: 10px;
    line-height: 1.5;
    color: var(--warning);
    margin-bottom: 6px;
}
.ext-blockers.has-items { display: block; }
.ext-blockers-title {
    display: block;
    font-size: 9px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.5px;
    color: var(--text-dim);
    margin-bottom: 2px;
}
.ext-blockers ul {
    margin: 0;
    padding-left: 14px;
}

.ext-btn-row {
    display: flex;
    gap: 8px;
    margin-top: 8px;
}
.ext-btn-row .btn {
    flex: 1;
}

/* Auth failure banner (TEL-36) — full-width, sits directly under the header */
.auth-error-banner {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 12px;
    padding: 10px 16px;
    background: var(--danger-muted);
    border-bottom: 1px solid var(--danger);
    color: var(--danger);
    font-size: 13px;
}

/* Utility */
.hidden { display: none !important; }

/* Stereonet View */
.stereonet-layout {
    display: flex;
    width: 100%;
    height: 100%;
    gap: 16px;
    padding: 16px;
    box-sizing: border-box;
}
.stereonet-main {
    flex: 1 1 0;
    position: relative;
    min-height: 0;
    min-width: 0;
    overflow: hidden;
}
#stereonet-canvas {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    background: var(--bg-primary);
    border: 1px solid var(--gray-dark);
    border-radius: 4px;
}
.stereonet-sidebar {
    width: 220px;
    flex-shrink: 0;
    display: flex;
    flex-direction: column;
    gap: 16px;
}
.stereonet-controls {
    display: flex;
    flex-direction: column;
    gap: 6px;
}
.stereonet-label {
    font-size: 11px;
    color: var(--text-dim);
    text-transform: uppercase;
    letter-spacing: 0.5px;
}
.stereonet-controls select {
    background: var(--bg-secondary);
    color: var(--text-primary);
    border: 1px solid var(--gray-dark);
    border-radius: 4px;
    padding: 4px 8px;
    font-size: 12px;
    font-family: inherit;
}
.stereonet-stats .stats-title {
    margin-bottom: 8px;
}
.stereonet-stats .stats-detail {
    font-size: 11px;
    color: var(--text-dim);
    margin-top: 8px;
}
.legend-item {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: 12px;
    padding: 2px 0;
}
.legend-dot {
    width: 8px;
    height: 8px;
    border-radius: 50%;
    flex-shrink: 0;
}
.legend-type {
    flex: 1;
    text-transform: capitalize;
    color: var(--text-secondary);
}
.legend-count {
    color: var(--text-dim);
    font-size: 11px;
}

/* ─── Tray View ─── */

#view-tray {
    display: flex;
    flex-direction: column;
    height: 100%;
}

.tray-toolbar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 8px 16px;
    background: var(--bg-secondary);
    border-bottom: 1px solid var(--border);
    flex-shrink: 0;
}

.tray-controls {
    display: flex;
    gap: 16px;
    align-items: center;
}

.tray-control-label {
    display: flex;
    align-items: center;
    gap: 6px;
    color: var(--text-secondary);
    font-size: 12px;
}

.tray-input {
    width: 56px;
    padding: 3px 6px;
    background: var(--bg-control);
    border: 1px solid var(--border);
    border-radius: 4px;
    color: var(--text-primary);
    font-size: 12px;
    text-align: center;
    font-family: var(--font);
}

.tray-input:focus {
    outline: none;
    border-color: var(--accent-primary);
}

.tray-unit {
    color: var(--text-dim);
    font-size: 11px;
}

.tray-info {
    color: var(--text-dim);
    font-size: 12px;
}

.tray-scroll-container {
    flex: 1;
    overflow-y: auto;
    padding: 16px;
}

.tray-scroll-content {
    display: flex;
    flex-direction: column;
    gap: 24px;
}

.tray-card {
    background: var(--bg-panel);
    border: 1px solid var(--border);
    border-radius: 6px;
    overflow: hidden;
}

.tray-header {
    padding: 6px 12px;
    font-size: 12px;
    font-weight: 600;
    color: var(--text-secondary);
    background: var(--bg-control);
    border-bottom: 1px solid var(--border);
    letter-spacing: 0.3px;
}

.tray-canvas {
    display: block;
}

.tray-empty {
    display: flex;
    align-items: center;
    justify-content: center;
    height: 200px;
    color: var(--text-dim);
    font-size: 14px;
}

/* Tray Fracture Editor */

.tray-frac-editor {
    position: absolute;
    width: 260px;
    background: var(--bg-control);
    border: 1px solid var(--accent-primary);
    border-radius: 4px;
    padding: 8px 10px;
    z-index: 10;
    box-shadow: var(--graiph-shadow-lg);
}
.tray-frac-editor.hidden { display: none; }

.tfe-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 8px;
}
.tfe-title {
    font-size: 11px;
    font-weight: 600;
    color: var(--accent-text);
    text-transform: capitalize;
}
.tfe-actions {
    display: flex;
    gap: 4px;
    flex-shrink: 0;
}

.tfe-fields {
    display: flex;
    flex-direction: column;
    gap: 5px;
}
.tfe-field {
    display: flex;
    align-items: center;
    gap: 5px;
}
.tfe-field label {
    font-size: 11px;
    color: var(--text-secondary);
    width: 48px;
    flex-shrink: 0;
}
.tfe-field input[type="number"] {
    width: 64px;
    background: var(--bg-primary);
    border: 1px solid var(--border);
    border-radius: 3px;
    color: var(--text-primary);
    padding: 2px 4px;
    font-size: 11px;
    font-family: var(--font);
    text-align: right;
}
.tfe-field input[type="number"]:focus {
    outline: none;
    border-color: var(--accent-primary);
}
.tfe-field span {
    font-size: 10px;
    color: var(--text-dim);
    width: 12px;
}

.tfe-range {
    flex: 1;
    min-width: 40px;
    height: 4px;
    -webkit-appearance: none;
    appearance: none;
    background: var(--bg-primary);
    border-radius: 2px;
    outline: none;
}
.tfe-range::-webkit-slider-thumb {
    -webkit-appearance: none;
    width: 12px;
    height: 12px;
    border-radius: 50%;
    background: var(--accent-primary);
    cursor: pointer;
}
.tfe-range::-moz-range-thumb {
    width: 12px;
    height: 12px;
    border-radius: 50%;
    background: var(--accent-primary);
    border: none;
    cursor: pointer;
}

.tfe-status {
    font-size: 10px;
    color: var(--text-dim);
    margin-top: 4px;
    min-height: 14px;
}

.tray-scroll-container {
    position: relative;
}

.tray-canvas {
    cursor: crosshair;
}

/* Scrollbar */
::-webkit-scrollbar { width: 6px; }
::-webkit-scrollbar-track { background: var(--bg-primary); }
::-webkit-scrollbar-thumb { background: var(--gray-dark); border-radius: 3px; }
::-webkit-scrollbar-thumb:hover { background: var(--text-dim); }

/* =========================================================================
   RESPONSIVE — the shared ladder in /shared/header.css does the work now
   (stages at 1400 / 1200 / 1000 / 800px, then the ≤640px hamburger drawer that
   headerResponsive.js fills). Only two televiewer deviations remain.
   ========================================================================= */

/* 1. The shared ≤800px stage hides .gh-actions entirely — for the other
   interfaces that slot holds save/export, but here it holds PRIMARY navigation.
   Keep it, and let the tabs wrap instead of disappearing. */
@media (max-width: 800px) {
  .graiph-header.tv-header .gh-actions {
    display: flex;
    min-width: 0;
    flex-shrink: 1;
  }
  /* Never wrap — the bar is a fixed 48px, so a second row would be invisible.
     The strip scrolls instead if the tabs ever outgrow the space. */
  .graiph-header.tv-header .header-nav {
    min-width: 0;
    overflow-x: auto;
    scrollbar-width: none;
  }
  .graiph-header.tv-header .header-nav::-webkit-scrollbar { display: none; }
  .graiph-header.tv-header .nav-tab { padding: 4px 8px; }
}

/* 2. Inside the drawer the tabs become a 2-up grid of full-width buttons. */
@media (max-width: 640px) {
  .gh-drawer .header-nav {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    width: 100%;
    background: transparent;
    border: none;
    padding: 0;
  }
  .gh-drawer .nav-tab {
    flex: 1 1 calc(50% - 6px);
    text-align: center;
    padding: 8px 12px;
    border: 1px solid var(--graiph-border, var(--border));
  }
}

/* --- Login overlay (shared auth system; same UX as the other interfaces) --- */
#loginOverlay {
    position: fixed;
    inset: 0;
    background: var(--graiph-overlay);
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 3000;
}
#loginOverlay.hidden { display: none; }
#loginOverlay .login-card {
    background: var(--bg-panel);
    padding: 40px;
    border-radius: 12px;
    border: 1px solid var(--border);
    width: 100%;
    max-width: 400px;
    box-shadow: var(--graiph-shadow-lg);
}
#loginOverlay .login-card h2 {
    color: var(--accent-text);
    margin-bottom: 24px;
    text-align: center;
}
#loginOverlay .login-form { display: flex; flex-direction: column; }
#loginOverlay .login-card input {
    width: 100%;
    padding: 12px 16px;
    margin-bottom: 16px;
    background: var(--bg-control);
    border: 1px solid var(--border);
    border-radius: 6px;
    color: var(--text-primary);
    font-size: 1rem;
    box-sizing: border-box;
}
#loginOverlay .login-card input:focus { outline: none; border-color: var(--accent-primary); }
#loginOverlay #loginBtn {
    width: 100%;
    padding: 12px;
    background: var(--accent-primary);
    color: var(--accent-ink);
    border: none;
    border-radius: 6px;
    font-size: 1rem;
    font-weight: 700;
    cursor: pointer;
    transition: background 0.2s;
}
#loginOverlay #loginBtn:hover { background: var(--accent-dim); }
#loginOverlay #loginBtn:disabled { background: var(--border); color: var(--text-dim); cursor: not-allowed; }
#loginOverlay #loginMsg { color: var(--danger); font-size: 0.85rem; text-align: center; min-height: 1.2em; }
