/* Tabstrip between "Control" (existing JSON-RPC UI) and "PHD2 GUI" (xpra
   iframe). Visually echoes the .equip-source-tabs pattern but with a
   filled active state since this is the primary nav inside Guide. */
.guide-tabstrip {
    display: flex;
    align-items: center;
    gap: 2px;
    margin: 6px 0 10px;
    border-bottom: 1px solid var(--border);
    padding-bottom: 0;
}
.guide-tab {
    background: transparent;
    color: var(--text-secondary);
    border: none;
    border-bottom: 2px solid transparent;
    padding: 7px 14px;
    margin-bottom: -1px;
    border-radius: 0;
    cursor: pointer;
    font-size: 13px;
    font-weight: 500;
    transition: color 0.12s, border-color 0.12s;
}
.guide-tab:hover { color: var(--text-primary); }
.guide-tab.active {
    color: var(--accent);
    border-bottom-color: var(--accent);
    font-weight: 600;
}

/* Profile-sync chip in the tabstrip. Color reflects last sync phase. */
.profile-sync-indicator {
    margin-left: auto;
    font-size: 11px;
    padding: 3px 10px;
    border-radius: 999px;
    background: rgba(255,255,255,0.05);
    color: var(--text-secondary);
}
.profile-sync-indicator.sync-ok   { background: rgba(34,197,94,0.18); color: #86efac; }
.profile-sync-indicator.sync-warn { background: rgba(245,158,11,0.18); color: #fcd34d; }
.profile-sync-indicator.sync-err  { background: rgba(239,68,68,0.18); color: #fca5a5; }
.profile-sync-indicator.sync-busy { background: rgba(99,102,241,0.18); color: #a5b4fc; }

/* Smart Calibrate + algo preset block, sits below the existing guiding
   controls in the Control tab. Spans full width for the row layout. */
.phd2-smart-block {
    background: rgba(255,255,255,0.03);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 10px 12px;
}
.phd2-smart-row {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
}

.phd2-algo-advanced summary::marker { color: var(--text-muted); }

/* PHD2 GUI tab, iframe wrapper + the 4 conditional states.
   flex: 1 + min-height: 0 lets the iframe inside grow to fill the
   remaining viewport height below the page header + guide tabstrip,
   instead of getting clamped to a fixed 600px box. */
.phd2-gui-tab {
    display: flex;
    flex-direction: column;
    gap: 10px;
    flex: 1;
    min-height: 0;
}
.phd2-gui-banner {
    padding: 14px 18px;
    border-radius: var(--radius);
    font-size: 13px;
    line-height: 1.5;
}
.phd2-gui-banner code {
    background: rgba(0,0,0,0.3);
    padding: 2px 6px;
    border-radius: 3px;
}
.phd2-gui-banner-info { background: rgba(99,102,241,0.10); border: 1px solid rgba(99,102,241,0.30); }
.phd2-gui-banner-warn { background: rgba(245,158,11,0.10); border: 1px solid rgba(245,158,11,0.30); }

.phd2-gui-empty {
    padding: 40px 20px;
    text-align: center;
    background: rgba(255,255,255,0.02);
    border: 1px dashed var(--border);
    border-radius: var(--radius);
}
.phd2-gui-empty h3 { margin: 0 0 8px; color: var(--text-primary); }
.phd2-gui-empty p  { margin: 4px 0; color: var(--text-muted); font-size: 13px; }

.phd2-gui-frame-wrap {
    display: flex;
    flex-direction: column;
    flex: 1;
    min-height: 0;
    position: relative; /* anchor for .phd2-gui-starting overlay */
}
.phd2-gui-toolbar {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 6px 10px;
    background: rgba(0,0,0,0.3);
    border: 1px solid var(--border);
    border-bottom: none;
    border-radius: var(--radius) var(--radius) 0 0;
    font-size: 12px;
}
.phd2-gui-iframe {
    flex: 1;
    width: 100%;
    /* min-height 0 + flex:1 + the chain above lets the iframe grow
       to fill the remaining tab area instead of being clamped to a
       fixed minimum that wastes space on tall viewports. The 400px
       floor is just a safety against ultra-short windows. */
    min-height: 400px;
    border: 1px solid var(--border);
    border-top: none;
    border-radius: 0 0 var(--radius) var(--radius);
    background: #000;
}

/* ==========================================================================
   VIDEO tab, planetary capture (SER) + lucky-imaging stack
   ========================================================================== */

.video-tab-panel {
    display: flex;
    flex-direction: column;
    /* min-height:0 unlocks the flex children to shrink below their
       content size; without it the .video-pane > .preview-area
       (flex:1 + min-height:200) overflows past .tab-panel's bottom
       padding when the viewport is short. */
    min-height: 0;
    overflow: hidden;
}
.video-tabstrip {
    display: flex;
    gap: 2px;
    margin: 6px 0 10px;
    border-bottom: 1px solid var(--border);
    padding-bottom: 0;
}
.video-tab {
    background: transparent;
    color: var(--text-secondary);
    border: none;
    border-bottom: 2px solid transparent;
    padding: 7px 14px;
    margin-bottom: -1px;
    border-radius: 0;
    cursor: pointer;
    font-size: 13px;
    font-weight: 500;
    transition: color 0.12s, border-color 0.12s;
}
.video-tab:hover { color: var(--text-primary); }
.video-tab.active {
    color: var(--accent);
    border-bottom-color: var(--accent);
    font-weight: 600;
}
.video-pane {
    display: flex;
    /* Desktop: floating translucent sidebar overlays the canvas (see
       .video-pane .quick-controls below). Bitmap underneath always
       gets 100% pane width; the operator can collapse the sidebar
       to clear it off the bitmap entirely. */
    flex-direction: row;
    gap: 12px;
    flex: 1;
    min-height: 0;
    overflow: hidden;
    position: relative;
}
/* VIDEO → Process: a plain scrollable vertical form (no canvas/sidebar).
   Without this it inherited .video-pane's flex-row and the control rows
   stacked horizontally in the middle of the pane. */
.video-process-pane {
    flex: 1;
    min-height: 0;
    display: flex;
    flex-direction: column;
    gap: 4px;
    overflow-y: auto;
    padding: 12px;
    align-items: stretch;
}
.video-process-pane .control-row {
    flex: 0 0 auto;
}
.video-pane .preview-area {
    flex: 1;
    min-width: 0;
    min-height: 200px;
    /* Reserve room for the floating control panel (340px) so the bitmap
       stays centred in the visible area, collapsing to the handle width
       when the panel is hidden. See LIVE/PREVIEW note above. */
    margin-right: calc(var(--video-qc-width, 340px) + 20px); /* panel + gap */
    transition: margin-right 200ms ease;
}
.video-pane.controls-collapsed .preview-area {
    margin-right: 36px;
}
/* Tap-to-hide reclaims the reserve, as in LIVE / PREVIEW. */
.video-pane.frame-controls-hidden .preview-area {
    margin-right: 0;
}
/* Floating control sidebar — same treatment as LIVE / PREVIEW
   (translucent, backdrop blur, collapsible via the toggle handle).
   340 px wide (10 px wider than LIVE/PREVIEW because VIDEO has the
   wheel-picker drums that need a bit more breathing room). */
.video-pane .quick-controls {
    position: absolute;
    right: 8px;
    top: 8px;
    bottom: 8px;
    /* User-resizable (drag the left-edge grip); persisted to localStorage. */
    width: var(--video-qc-width, 340px);
    z-index: 4;
    display: flex;
    flex-direction: column;
    gap: 10px;
    /* Reserve 44px on the left so the full-height toggle handle
       (28px wide + 8px gap) doesn't overlap content. */
    padding: 12px 12px 12px 44px;
    /* The panel itself no longer scrolls — an inner .quick-controls-scroll
       wrapper does. This keeps the absolutely-positioned toggle handle sized
       to the panel's full visible height; it used to scroll away with the
       content, so the grab bar didn't reach the bottom when the panel
       overflowed (had a scrollbar). */
    overflow: hidden;
    background: rgba(var(--panel-glass-rgb), 0.88);
    backdrop-filter: blur(10px);
    -webkit-backdrop-filter: blur(10px);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    transition: transform 200ms ease;
}
/* Inside the sidebar each .control-row becomes its own vertical
   block: the label / slider / readout still flow horizontally
   within the row but every row sits stacked top-to-bottom. The
   labels inherit the same look as elsewhere. */
.video-pane .quick-controls .control-row {
    /* Stack items vertically (same treatment as LIVE/PREVIEW). */
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 8px;
    min-width: 0;
}
.video-pane .quick-controls .control-row > label {
    display: flex;
    align-items: center;
    gap: 8px;
    width: 100%;
    min-width: 0;
    max-width: 100%;
}
.video-pane .quick-controls .control-row > label > input:not([type="checkbox"]):not([type="radio"]),
.video-pane .quick-controls .control-row > label > select {
    flex: 0 0 50%;
    margin-left: auto;
    min-width: 0;
}
.video-pane .quick-controls .control-row > .zwo-gain-presets,
.video-pane .quick-controls .control-row > .bin-presets {
    display: flex;
    gap: 6px;
    width: 100%;
}

/* FOV preset block in the VIDEO sidebar. Replaces the older
   two-row layout that used `flex: 1` button growth and clipped
   the longer rectangular labels (640×480 → 1920×1080) into each
   other once 5+ pills competed for the row width. The flow
   container uses flex-wrap with intrinsic-width buttons so pills
   that don't fit drop to the next line cleanly. */
.video-pane .quick-controls .fov-preset-block {
    display: flex;
    flex-direction: column;
    gap: 4px;
    margin: 4px 0;
}
.video-pane .quick-controls .fov-preset-label {
    font-size: 11px;
    color: var(--text-muted);
    text-transform: uppercase;
    letter-spacing: 0.4px;
}
.video-pane .quick-controls .fov-preset-flow {
    display: flex;
    flex-wrap: wrap;
    gap: 4px;
}
.video-pane .quick-controls .fov-preset-flow > .bin-preset {
    /* Intrinsic width, not flex:1 -- keeps the longest label
       (1920×1080) from forcing the rest of the row to crush.
       Padding bumped up vs the inline 3/8 default for touch. */
    flex: 0 0 auto;
    padding: 6px 10px;
    font-size: 11px;
    min-width: 0;
}
.video-pane .quick-controls .fov-preset-readout {
    font-size: 11px;
    color: var(--text-muted);
    white-space: normal;
}
.video-pane .quick-controls .control-row > .zwo-gain-presets > button,
.video-pane .quick-controls .control-row > .bin-presets > button {
    flex: 1;
    min-width: 0;
    padding: 10px 8px;
    font-size: 12px;
}
/* Compact stats / hint lines inside the sidebar shouldn't expand
   horizontally past the panel; allow wrapping. */
.video-pane .quick-controls .text-muted {
    white-space: normal;
}

/* Phone PORTRAIT: fall back to the single-column stack so the canvas and
   the controls each get the full viewport width. The sidebar becomes a
   bottom-docked block instead of a right-side column. Same shape as the
   LIVE / PREVIEW block in 02-imaging-panels.css, including putting the
   panel back into the flow: floating it with no canvas reserve left the
   frame behind it. */
@media (max-width: 900px) and (orientation: portrait) {
    .video-pane {
        flex-direction: column;
    }
    .video-pane .preview-area {
        margin-right: 0;
    }
    .video-pane .quick-controls {
        flex: 0 0 auto;
        max-height: 50vh;
        position: static;
        width: auto;
        padding-left: 12px;
        transform: none;
        background: var(--bg-secondary);
        backdrop-filter: none;
        -webkit-backdrop-filter: none;
    }
    .video-pane .quick-controls.quick-controls--collapsed {
        transform: none;
    }
    .video-pane .quick-controls > .quick-controls-toggle,
    .video-pane .quick-controls > .quick-controls-resize {
        display: none;
    }
}

/* Phone / small-tablet LANDSCAPE keeps the side-by-side layout (a ~400px
   tall viewport has no room to stack), with a narrower sidebar. */
@media (orientation: landscape) and (max-width: 1100px) {
    .video-pane {
        --video-qc-width: min(300px, 36vw);
    }
}

/* SKY tab inset showing the live camera feed while the mount slews.
   Positioned in the lower-right corner, fades in/out via x-show. */
.slew-preview-inset {
    position: absolute;
    bottom: 16px;
    right: 16px;
    z-index: 20;
    width: 260px;
    height: 200px;
    background: #000;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    overflow: hidden;
    box-shadow: 0 4px 16px rgba(0,0,0,0.6);
    /* Soft fade so the inset doesn't pop on/off jarringly. */
    transition: opacity 300ms ease;
}
.slew-preview-canvas {
    width: 100%;
    height: 100%;
    display: block;
    /* Letterbox to preserve the live camera frame's aspect ratio
       inside the SKY-tab inset, instead of squishing 4:3 / 1:1
       sensor frames into the 16:9 inset card. */
    object-fit: contain;
    object-position: center;
}
.slew-preview-label {
    position: absolute;
    top: 4px;
    left: 6px;
    font-size: 10px;
    color: #fcd34d;
    background: rgba(0,0,0,0.7);
    padding: 2px 6px;
    border-radius: 2px;
    z-index: 1;
    pointer-events: none;
}

/* REFSUG-3: Refocus-suggestion advisory callout in the LIVE tab.
   Yellow box that appears above the triggers panel when the trend
   detector flagged degrading focus. Mirrors the activity-bar chip
   so the user sees the same advisory in two places (chip
   = global, callout = LIVE-tab contextual + action buttons). */
.livestack-refocus-callout {
    margin-top: 8px;
    padding: 10px 12px;
    background: rgba(245, 158, 11, 0.10);
    border: 1px solid rgba(245, 158, 11, 0.45);
    border-radius: var(--radius);
    font-size: 12px;
    display: flex;
    flex-direction: column;
    gap: 6px;
}
.livestack-refocus-callout-head {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 10px;
}
.livestack-refocus-callout-head > span:first-child {
    font-weight: 600;
}
.livestack-refocus-callout-stats {
    font-size: 11px;
}
.livestack-refocus-callout-actions {
    display: flex;
    gap: 6px;
    flex-wrap: wrap;
}

/* Part B4: meridian-flip "Flip now" control inside the LIVE tab. */
.livestack-meridian {
    margin-top: 10px;
    padding-top: 8px;
    border-top: 1px solid var(--border, rgba(255, 255, 255, 0.08));
    display: flex;
    flex-direction: column;
    gap: 8px;
}
.livestack-meridian-row {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 8px;
    font-size: 12px;
}
.livestack-meridian-auto {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 12px;
    cursor: pointer;
}
/* Offset row: "Flip [N] min past the meridian", kept together so the
   unit label never wraps away from its input. */
.livestack-meridian-offset {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: 12px;
    padding-left: 24px; /* align under the checkbox label text */
}
/* Narrower than the default .input-sm: a meridian offset is 0-60, so a
   compact field reads cleaner inline with the unit label. */
.livestack-meridian-min {
    width: 64px;
    min-width: 64px;
    text-align: center;
}
.livestack-meridian-err {
    font-size: 11px;
}

/* Settings > Image cache: stat readouts laid out in a wrapping row. */
.cache-stats-row {
    display: flex;
    flex-wrap: wrap;
    gap: 8px 20px;
    margin-top: 8px;
}

/* LSTR-5: Live-stack triggers expandable panel inside the LIVE tab.
   Sits below the capture-buttons row. <details> open is bound to
   either enabled flag so the panel auto-expands when triggers exist. */
.livestack-triggers {
    margin-top: 8px;
    padding: 8px 10px;
    background: rgba(255,255,255,0.03);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    font-size: 12px;
}
.livestack-triggers > summary {
    cursor: pointer;
    color: var(--text-secondary);
    font-weight: 500;
    user-select: none;
}
.livestack-triggers > summary:hover { color: var(--text-primary); }

.livestack-triggers-block {
    margin: 8px 0;
    padding: 8px 10px;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: rgba(0,0,0,0.15);
}
/* Tab strip that groups the three per-frame trigger blocks (Dither /
   Re-focus / Re-center). Mirrors the .sky-subtab-pill vocabulary but
   compact enough for the narrow LIVE sidebar. */
.livestack-trig-tabs {
    display: flex;
    gap: 2px;
    margin: 8px 0 0;
    border-bottom: 1px solid var(--border);
    flex-wrap: wrap;
}
.livestack-trig-tab {
    flex: 1 1 auto;
    background: transparent;
    color: var(--text-secondary);
    border: none;
    border-bottom: 2px solid transparent;
    padding: 7px 8px;
    margin-bottom: -1px;
    border-radius: 0;
    cursor: pointer;
    font-size: 12px;
    font-weight: 500;
    white-space: nowrap;
    transition: color 0.12s, border-color 0.12s;
}
.livestack-trig-tab:hover {
    color: var(--text-primary);
}
.livestack-trig-tab.active {
    color: var(--accent);
    border-bottom-color: var(--accent);
    font-weight: 600;
}
.livestack-triggers-block > legend {
    padding: 0 6px;
    font-size: 11px;
    color: var(--text-muted);
    text-transform: uppercase;
    letter-spacing: 0.5px;
}
.livestack-triggers-block > legend .checkbox-label {
    text-transform: none;
    color: var(--text-primary);
    font-size: 12px;
    letter-spacing: 0;
}
.livestack-triggers-row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 10px 16px;
    margin-bottom: 6px;
}
/* Calibration master pickers: caption + full-width themed select, stacked,
   one per line so long master filenames have room. */
.livestack-calib-grid {
    flex-direction: column;
    align-items: stretch;
    gap: 8px;
}
.livestack-calib-field {
    display: flex !important;
    align-items: center;
    gap: 10px;
}
.livestack-calib-field > span {
    flex: 0 0 42px;
    color: var(--text-secondary);
    font-size: 12px;
}
.livestack-calib-field > select.input {
    flex: 1 1 auto;
    min-width: 0;
    width: auto;
}
.livestack-triggers-row label {
    display: inline-flex;
    align-items: center;
    gap: 4px;
}
/* Dropdowns sitting directly in a triggers row (e.g. the BGE
   Correction picker) grow to the remaining row width instead of the
   90px standalone default, which truncated "Subtraction" to
   "Subtractio…" (field report). Same fill-the-column rule the
   calibration master pickers above already use. */
.livestack-triggers-row > select.input-sm {
    flex: 1 1 auto;
    width: auto;
    min-width: 0;
}
.livestack-triggers-row label > input[type="number"] {
    margin: 0 2px;
}

/* WIFI-4: Network panel in Settings tab. Compact pill + meta row,
   no card chrome since the panel sits inside .settings-section. */
.network-section .network-card {
    padding: 12px;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--bg-secondary);
    max-width: 720px;
}
.network-status-row {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 12px;
    margin-bottom: 4px;
}
.network-mode-pill {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 3px 10px;
    border-radius: 999px;
    background: rgba(255, 255, 255, 0.06);
    border: 1px solid var(--border);
    font-size: 11px;
    color: var(--text-primary);
}
.network-mode-hotspot      { border-color: rgba(34, 197, 94, 0.6); }
.network-mode-station      { border-color: rgba(56, 132, 255, 0.6); }
.network-mode-disconnected { border-color: rgba(245, 158, 11, 0.6); }
.network-mode-unsupported,
.network-mode-unknown      { color: var(--text-muted); }
.network-ssid {
    font-weight: 600;
    color: var(--text-primary);
}
.network-ip {
    font-variant-numeric: tabular-nums;
    font-size: 12px;
}
.network-signal {
    font-size: 11px;
}

/* Touch-friendly range slider wrapper: [-] slider [+]. Injected
   automatically by app.js _augmentRangeInputs() on init() and on
   any future DOM mutations. The wrapper inherits any flex:1 the
   original slider had so it still stretches inside flex labels. */
.range-with-controls {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    min-width: 0;
}
.range-with-controls > input[type="range"] {
    flex: 1;
    min-width: 0;
}
.range-step-btn {
    flex: 0 0 auto;
    width: 28px;
    height: 28px;
    padding: 0;
    border: 1px solid var(--border);
    border-radius: 6px;
    background: rgba(255, 255, 255, 0.04);
    color: var(--text-primary);
    font-size: 15px;
    font-weight: 600;
    line-height: 1;
    cursor: pointer;
    user-select: none;
    /* Browsers add a 300ms delay before synthesizing click on touch
       unless touch-action is set; we use pointerdown anyway, but
       this also kills the double-tap-zoom that punishes power
       users tapping fast. */
    touch-action: manipulation;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    transition: background var(--transition), border-color var(--transition);
}
.range-step-btn:hover {
    background: rgba(255, 255, 255, 0.1);
    border-color: rgba(255, 255, 255, 0.18);
}
.range-step-btn:active {
    background: var(--accent);
    border-color: var(--accent);
    color: #fff;
}
.range-step-btn:disabled,
.range-with-controls > input[type="range"]:disabled ~ .range-step-btn,
.range-with-controls:has(input:disabled) .range-step-btn {
    opacity: 0.4;
    cursor: not-allowed;
}

/* Binning button group (BIN1 / BIN2 / BIN3 / BIN4). Replaces the
   undersized native <select> across the Live, Preview, Video, and
   Sequencer panels. Same visual idiom as .zwo-gain-preset (L/M/H
   pills) so the row of pills lines up consistently. */
.bin-presets {
    display: inline-flex;
    gap: 4px;
    vertical-align: middle;
}
.bin-preset {
    min-width: 40px;
    padding: 3px 8px;
    font-size: 10px;
    font-weight: 600;
    line-height: 1.2;
    letter-spacing: 0.4px;
    border-radius: 4px;
    border: 1px solid rgba(255, 255, 255, 0.18);
    background: rgba(255, 255, 255, 0.05);
    color: var(--text-primary);
    cursor: pointer;
    transition: background 0.12s ease, border-color 0.12s ease;
    touch-action: manipulation;
}
.bin-preset:hover:not(:disabled) {
    background: rgba(var(--accent-rgb), 0.18);
    border-color: rgba(var(--accent-rgb), 0.5);
}
.bin-preset[data-active="true"] {
    background: rgba(var(--accent-rgb), 0.28);
    border-color: rgba(var(--accent-rgb), 0.7);
}
.bin-preset:disabled {
    opacity: 0.45;
    cursor: not-allowed;
}

/* VIDEO Capture: click-to-recenter ROI hint. Floats above the
   preview canvas's bottom edge; dismissable via the × button so
   power users don't see it every session. */
.video-roi-hint {
    position: absolute;
    left: 50%;
    bottom: 12px;
    transform: translateX(-50%);
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 6px 10px 6px 14px;
    background: rgba(7, 13, 26, 0.82);
    backdrop-filter: blur(6px);
    -webkit-backdrop-filter: blur(6px);
    border: 1px solid rgba(var(--accent-rgb), 0.5);
    border-radius: 16px;
    font-size: 11px;
    color: var(--text-primary);
    pointer-events: auto;
    z-index: 5;
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
}
.video-roi-hint > .btn {
    padding: 0 6px;
    min-width: 22px;
    font-size: 13px;
    line-height: 1;
}

/* iOS / ASIAIR-style vertical drum picker. Looks like a numeric
   wheel where the current value sits in the middle of a tall
   column with neighboring values visible above and below. Drag
   vertically (or use scroll wheel) to change. Snaps to step on
   release. Set up in app.js via WheelPicker class auto-mounted
   on every .wheel-picker found in the DOM. */
.wheel-picker {
    position: relative;
    width: 84px;
    height: 168px;
    border: 1px solid var(--border);
    border-radius: 10px;
    background: rgba(0, 0, 0, 0.35);
    overflow: hidden;
    user-select: none;
    -webkit-user-select: none;
    touch-action: none;       /* JS handles pointer drag */
    flex-shrink: 0;
    cursor: ns-resize;
}
.wheel-picker[data-disabled="true"] {
    opacity: 0.5;
    pointer-events: none;
}
.wheel-picker-label {
    position: absolute;
    top: 4px;
    left: 0;
    right: 0;
    text-align: center;
    font-size: 9px;
    color: var(--text-muted);
    text-transform: uppercase;
    letter-spacing: 0.6px;
    pointer-events: none;
    z-index: 3;
}
.wheel-picker-list {
    position: absolute;
    left: 0;
    right: 0;
    top: 0;
    will-change: transform;
    /* Transition only when JS sets .snapping; bare drag is
       transform-only (no animation) so it feels instantaneous. */
}
.wheel-picker-list.snapping {
    transition: transform 180ms cubic-bezier(0.2, 0.7, 0.3, 1);
}
.wheel-picker-item {
    height: 28px;
    line-height: 28px;
    text-align: center;
    font-size: 13px;
    font-variant-numeric: tabular-nums;
    color: var(--text-muted);
    transition: color 120ms ease, font-size 120ms ease,
                font-weight 120ms ease;
}
/* Center item sits at index = visibleCount/2 + 1; JS pins
   .wheel-picker-item--center on it on every render. */
.wheel-picker-item--center {
    color: var(--text-primary);
    font-size: 17px;
    font-weight: 600;
}
.wheel-picker-item--adjacent {
    color: rgba(255, 255, 255, 0.55);
}
/* Center highlight band (line at top + bottom of the centre slot).
   Drawn with pseudo-elements so it doesn't move with the list. */
.wheel-picker::before,
.wheel-picker::after {
    content: "";
    position: absolute;
    left: 8px;
    right: 8px;
    height: 1px;
    background: rgba(var(--accent-rgb), 0.55);
    pointer-events: none;
    z-index: 2;
}
.wheel-picker::before { top: calc(50% - 14px); }
.wheel-picker::after  { top: calc(50% + 14px); }
/* Soft fade at top/bottom so out-of-focus values gently dim. */
.wheel-picker > .wheel-picker-fade {
    position: absolute;
    left: 0; right: 0;
    height: 60px;
    pointer-events: none;
    z-index: 1;
}
.wheel-picker > .wheel-picker-fade-top    { top: 0;
    background: linear-gradient(to bottom, rgba(0,0,0,0.6), transparent); }
.wheel-picker > .wheel-picker-fade-bottom { bottom: 0;
    background: linear-gradient(to top,    rgba(0,0,0,0.6), transparent); }

/* Row layout: wheel pickers sit side by side with a label above
   each one. The video sidebar stacks them in a 2-col grid for
   compactness. */
/* VIDEO filter row: label above, select full-width, sitting under the
   exposure/gain wheels and above the shutter. */
.video-filter-row {
    display: block;
    margin: 10px auto 0;
    max-width: 260px;
    font-size: 11px;
    color: var(--text-muted);
}
.video-filter-row select {
    display: block;
    width: 100%;
    margin-top: 3px;
}

.wheel-picker-group {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(84px, 1fr));
    gap: 10px;
    justify-items: center;
}

/* Each wheel stacked between its (-) and (+) nudge buttons, so the
   value can be stepped by tapping on small touch screens. */
.wheel-col {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 6px;
}
.wheel-nudge {
    padding: 6px 0;
    font-size: 18px;
    line-height: 1;
    font-weight: 700;
}

/* ── MFOC: Manual Focus Assist subtab ──────────────────────
   2-column layout mirroring the VIDEO Capture tab: preview
   canvas on the left, control sidebar on the right. Wraps to
   a single column under 900 px viewports. */
.manual-focus-pane {
    display: flex;
    flex-direction: row;
    gap: 12px;
    min-height: 0;
    margin-top: 10px;
}
.manual-focus-preview-area {
    flex: 1;
    min-width: 0;
    min-height: 200px;
    position: relative;
}
.manual-focus-controls {
    flex: 0 0 340px;
    display: flex;
    flex-direction: column;
    gap: 10px;
    padding: 12px;
    background: var(--bg-secondary);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    overflow-y: auto;
    max-height: 70vh;
}
.manual-focus-controls .control-row {
    display: flex;
    flex-direction: row;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px;
}
.manual-focus-controls .control-row.actions {
    border-top: 1px solid var(--border);
    padding-top: 10px;
}
/* Live metrics grid: 2 columns of HFR/FWHM/Stars/Laplacian
   pills, each pill is a compact card with a label above its
   value. Best HFR row spans both columns. */
.manual-focus-metrics {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 8px;
    margin-top: 8px;
}
.manual-focus-metric {
    background: rgba(0, 0, 0, 0.25);
    border: 1px solid var(--border);
    border-radius: 6px;
    padding: 8px 10px;
    display: flex;
    flex-direction: column;
    gap: 2px;
    transition: border-color 200ms ease;
}
.manual-focus-metric-label {
    font-size: 10px;
    color: var(--text-muted);
    text-transform: uppercase;
    letter-spacing: 0.4px;
}
.manual-focus-metric-value {
    font-size: 18px;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    color: var(--text-primary);
}
.manual-focus-metric-trend {
    font-size: 13px;
    color: var(--text-muted);
    margin-left: 6px;
}
.manual-focus-metric--good {
    border-color: rgba(34, 197, 94, 0.55);
    background: rgba(34, 197, 94, 0.08);
}
.manual-focus-metric--good .manual-focus-metric-trend {
    color: rgb(74, 222, 128);
}
.manual-focus-metric--bad {
    border-color: rgba(239, 68, 68, 0.55);
    background: rgba(239, 68, 68, 0.08);
}
.manual-focus-metric--bad .manual-focus-metric-trend {
    color: rgb(248, 113, 113);
}
.manual-focus-metric--flat {
    border-color: rgba(245, 158, 11, 0.45);
}
.manual-focus-metric--best {
    grid-column: 1 / -1;
    border-color: rgba(56, 132, 255, 0.55);
    background: rgba(56, 132, 255, 0.08);
}

/* MFOC-4: Bahtinov mask block in the manual focus sidebar. Slots
   under the live metrics, so the user sees the in-focus / offset
   readout next to HFR. The .manual-focus-metric inside picks up
   the colour-class set per offset magnitude. */
.manual-focus-bahtinov {
    margin-top: 12px;
    padding-top: 12px;
    border-top: 1px solid var(--border);
    display: flex;
    flex-direction: column;
    gap: 8px;
}
.manual-focus-toggle {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 12px;
    color: var(--text-primary);
    cursor: pointer;
}
.manual-focus-toggle input[type="checkbox"] {
    accent-color: var(--accent);
}
.manual-focus-hint {
    font-size: 11px;
    color: var(--text-muted);
    margin: 0;
    line-height: 1.35;
}

@media (max-width: 900px) and (orientation: portrait) {
    .manual-focus-pane {
        flex-direction: column;
    }
    .manual-focus-controls {
        flex: 0 0 auto;
        max-height: 50vh;
    }
}

/* Phone / small-tablet LANDSCAPE. Stacking here put the exposure inputs,
   the shutter and the HFR metrics below the fold on a viewport ~400px
   tall, which is where the operator is actually turning the focuser. Keep
   the two columns, narrow the sidebar, and let it use the full height it
   has instead of 70vh of a short viewport. */
@media (orientation: landscape) and (max-width: 1100px) {
    .manual-focus-pane {
        flex-direction: row;
        /* Cap the row at the visible area: the sidebar is taller than a
           landscape phone, and without this it stretched the row (and the
           frame beside it) past the fold. The sidebar already scrolls
           internally (overflow-y: auto). */
        max-height: 78vh;
    }
    .manual-focus-controls {
        flex: 0 0 min(320px, 40vw);
        max-height: none;
    }
    .manual-focus-preview-area {
        min-height: 160px;
    }
}

/* MFOC-2: HFR trend chart container under the pane. Reuses the
   af-chart-legend look-and-feel (legend dot + label). */
.manual-focus-chart {
    margin-top: 12px;
    background: var(--bg-secondary);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 10px 12px;
}
.manual-focus-chart .chart-container {
    position: relative;
    height: 200px;
    width: 100%;
}

/* AUTH-3: full-screen login + first-run wizard overlay. Pinned
   above EVERYTHING (status bar 50, modals ~1000, toasts ~2000) so
   nothing leaks unauthenticated UI. */
.auth-overlay {
    position: fixed;
    inset: 0;
    z-index: 99999;
    display: flex;
    align-items: center;
    justify-content: center;
    background: rgba(2, 4, 10, 0.92);
    backdrop-filter: blur(12px);
    -webkit-backdrop-filter: blur(12px);
}

/* BOOT curtain: the same overlay, held from the moment auth clears until the
   first status frame arrives.

   Lighter than the auth veil on purpose. The auth states must not leak the UI
   behind them, so they are all but opaque; here the operator is already
   through the gate and the app is already there, just empty, so the blur is
   the message: the shape of the thing is visible and settling, rather than a
   separate screen that has to be got past. Bigger radius to make sure no
   half-populated reading is legible through it. */
.auth-overlay-boot {
    background: rgba(2, 4, 10, 0.55);
    backdrop-filter: blur(18px) saturate(0.85);
    -webkit-backdrop-filter: blur(18px) saturate(0.85);
}
/* Fades out rather than cutting: the app is being revealed, not swapped in.
   Only the exit is animated. The curtain is up from the first paint, so there
   is nothing to animate in, and visibility is delayed by the length of the
   fade so the layer stops taking clicks exactly when it stops being seen. */
.auth-overlay-boot {
    opacity: 1;
    visibility: visible;
    transition: opacity 280ms ease-out, visibility 0s linear 0s;
}
.auth-overlay-boot.boot-veil-gone {
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
    transition: opacity 280ms ease-out, visibility 0s linear 280ms;
}
@media (prefers-reduced-motion: reduce) {
    .auth-overlay-boot,
    .auth-overlay-boot.boot-veil-gone { transition-duration: 1ms, 0s; }
}

/* Self-signed certificate help. Lives inside the auth modal (first run) and
   as a standalone banner + dialog once signed in. */
.auth-cert {
    margin-top: 18px;
    padding-top: 14px;
    border-top: 1px solid rgba(255, 255, 255, 0.10);
    text-align: left;
}
.auth-cert-title {
    font-size: 13px;
    font-weight: 600;
    margin-bottom: 6px;
}
.auth-cert-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    margin-top: 10px;
}
.cert-banner {
    position: fixed;
    top: var(--statusbar-height);
    left: 0;
    right: 0;
    z-index: 1000;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
    gap: 10px;
    background: var(--warn, #b8860b);
    color: #fff;
    padding: 6px 12px;
    font-size: 13px;
    font-weight: 500;
}
.cert-help-modal {
    position: fixed;
    inset: 0;
    z-index: 100000;
    display: flex;
    align-items: center;
    justify-content: center;
    background: rgba(0, 0, 0, 0.6);
    padding: 16px;
}
.cert-help-frame {
    width: min(560px, 94vw);
    max-height: 86vh;
    overflow-y: auto;
    position: relative;
}
.cert-help-guide h3 {
    font-size: 14px;
    margin: 12px 0 6px;
}
.cert-help-guide ol {
    margin: 0 0 8px 18px;
    padding: 0;
    font-size: 13px;
    line-height: 1.5;
}
.cert-help-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    margin-top: 16px;
}

.auth-modal {
    width: min(420px, 92vw);
    padding: 28px 28px 22px;
    background: rgba(20, 24, 36, 0.96);
    border: 1px solid rgba(255, 255, 255, 0.10);
    border-radius: 12px;
    box-shadow: 0 16px 48px rgba(0, 0, 0, 0.6);
    color: var(--text-primary);
    font-size: 13px;
}
.auth-modal h2 {
    margin: 0 0 12px;
    font-size: 18px;
    font-weight: 600;
}
.auth-hint {
    margin: 0 0 18px;
    color: var(--text-muted);
    font-size: 12px;
    line-height: 1.45;
}
.auth-recover {
    margin-top: 16px;
    padding-top: 14px;
    border-top: 1px solid var(--border);
    font-size: 11px;
    line-height: 1.4;
}
.auth-field {
    display: block;
    margin-bottom: 12px;
}
.auth-field > span {
    display: block;
    margin-bottom: 4px;
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 0.4px;
    color: var(--text-muted);
}
.auth-field input[type="password"] {
    width: 100%;
    padding: 9px 11px;
    background: rgba(255, 255, 255, 0.05);
    border: 1px solid var(--border);
    border-radius: 6px;
    color: var(--text-primary);
    font-size: 13px;
}
.auth-field input[type="password"]:focus {
    outline: none;
    border-color: var(--accent);
}
.auth-remember {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-bottom: 14px;
    font-size: 12px;
    color: var(--text-secondary);
    cursor: pointer;
}
.auth-error {
    margin: 0 0 12px;
    padding: 8px 10px;
    background: rgba(239, 68, 68, 0.12);
    border: 1px solid rgba(239, 68, 68, 0.4);
    border-radius: 5px;
    color: #fca5a5;
    font-size: 12px;
}
.auth-modal .btn-primary {
    width: 100%;
    padding: 10px;
    font-size: 13px;
}

/* ---- HELP-1: tutorial stepper + landing layout -----------------
   .help-panel hosts both the landing 4-card picker and the stepper
   view; the template (index.html) decides which subtree renders
   based on help.tutorial. Card grid mirrors the .settings-grid /
   .tonight visual rhythm so the page feels consistent. */
.help-panel {
    max-width: 1200px;
    margin: 0 auto;
    padding: 10px 2px 2px 2px;
}
.help-intro {
    color: var(--text-secondary);
    font-size: 13px;
    line-height: 1.55;
    margin: 0 0 18px;
    padding: 16px 4px 4px 4px;
}
.help-landing-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
    gap: 14px;
    margin-top: 8px;
}
.help-tutorial-card {
    display: flex;
    flex-direction: column;
    gap: 6px;
    padding: 18px 18px 16px;
    background: var(--bg-secondary);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    text-align: left;
    color: var(--text-primary);
    cursor: pointer;
    transition: border-color 0.15s, transform 0.15s, background 0.15s;
}
.help-tutorial-card:hover {
    border-color: var(--accent);
    transform: translateY(-2px);
    background: rgba(56, 132, 255, 0.06);
}
.help-card-icon {
    font-size: 28px;
    line-height: 1;
}
.help-tutorial-card h3 {
    margin: 0;
    font-size: 15px;
}
.help-tutorial-card p {
    margin: 0;
    color: var(--text-muted);
    font-size: 12px;
    line-height: 1.45;
}
.help-card-meta {
    margin-top: 4px;
    font-size: 11px;
    color: var(--text-muted);
    text-transform: uppercase;
    letter-spacing: 0.4px;
}

/* Stepper view */
.help-stepper-header {
    display: flex;
    align-items: center;
    gap: 12px;
    margin-bottom: 12px;
}
.help-back-btn {
    background: transparent;
    border: 1px solid var(--border);
    color: var(--text-secondary);
    padding: 4px 10px;
    border-radius: 5px;
    font-size: 12px;
    cursor: pointer;
}
.help-back-btn:hover {
    border-color: var(--accent);
    color: var(--text-primary);
}
.help-stepper-title {
    font-size: 16px;
    font-weight: 600;
    color: var(--text-primary);
    flex: 1;
}
.help-close-btn {
    background: transparent;
    border: 0;
    color: var(--text-muted);
    font-size: 20px;
    line-height: 1;
    cursor: pointer;
    padding: 4px 8px;
}
.help-close-btn:hover { color: var(--text-primary); }

.help-progress-dots {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
    margin-bottom: 14px;
}
.help-progress-dot {
    width: 10px;
    height: 10px;
    border-radius: 50%;
    background: rgba(255, 255, 255, 0.15);
    cursor: pointer;
    transition: background 0.15s, transform 0.15s;
}
.help-progress-dot:hover { transform: scale(1.2); }
.help-progress-dot.done { background: var(--accent); }
.help-progress-dot.active {
    background: var(--accent);
    box-shadow: 0 0 0 3px rgba(56, 132, 255, 0.25);
    transform: scale(1.2);
}
.help-progress-label {
    margin-left: auto;
    color: var(--text-muted);
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 0.4px;
}

.help-step-card {
    max-width: 920px;
    margin: 0 auto;
    padding: 24px 28px;
    background: var(--bg-secondary);
    border: 1px solid var(--border);
    border-radius: var(--radius);
}
.help-step-card h2 {
    margin: 0 0 16px;
    font-size: 20px;
    font-weight: 600;
    color: var(--text-primary);
}
.help-screenshot {
    position: relative;
    margin: 0 0 18px;
    border-radius: 8px;
    overflow: hidden;
    background: rgba(0, 0, 0, 0.25);
    border: 1px solid var(--border);
}
.help-screenshot img {
    display: block;
    width: 100%;
    height: auto;
}
.help-screenshot-placeholder {
    display: flex;
    align-items: center;
    gap: 14px;
    padding: 28px 24px;
    color: var(--text-muted);
    font-size: 13px;
}
.help-placeholder-icon {
    font-size: 32px;
    line-height: 1;
}
.help-screenshot-placeholder strong {
    display: block;
    color: var(--text-secondary);
    margin-bottom: 4px;
    font-size: 13px;
}
.help-screenshot-placeholder p {
    margin: 0;
    font-size: 12px;
    line-height: 1.45;
}
.help-screenshot-placeholder code {
    color: var(--accent);
    background: rgba(56, 132, 255, 0.08);
    padding: 1px 5px;
    border-radius: 3px;
    font-size: 11px;
}

.help-step-body p {
    margin: 0 0 12px;
    color: var(--text-secondary);
    font-size: 13px;
    line-height: 1.6;
}
.help-step-body p:last-child { margin-bottom: 0; }

.help-tip, .help-warn {
    display: flex;
    gap: 10px;
    align-items: flex-start;
    margin: 16px 0 0;
    padding: 12px 14px;
    border-radius: 6px;
    font-size: 12px;
    line-height: 1.5;
}
.help-tip {
    background: rgba(56, 132, 255, 0.08);
    border: 1px solid rgba(56, 132, 255, 0.30);
    color: var(--text-secondary);
}
.help-warn {
    background: rgba(245, 158, 11, 0.08);
    border: 1px solid rgba(245, 158, 11, 0.40);
    color: var(--text-secondary);
}
.help-tip-icon, .help-warn-icon {
    font-size: 16px;
    line-height: 1;
}

.help-step-actions {
    display: flex;
    gap: 8px;
    flex-wrap: wrap;
    margin-top: 18px;
}
.help-step-nav {
    display: flex;
    justify-content: space-between;
    gap: 8px;
    margin-top: 22px;
    padding-top: 18px;
    border-top: 1px solid var(--border);
}

/* Troubleshoot accordion list */
.help-faq-list {
    display: flex;
    flex-direction: column;
    gap: 6px;
    /* No width cap: the accordion spans the same width as the intro
       paragraph (the full .help-panel content width). */
    padding: 0 4px;
}
.help-faq {
    background: var(--bg-secondary);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    overflow: hidden;
}
.help-faq summary {
    padding: 12px 16px;
    cursor: pointer;
    font-size: 13px;
    font-weight: 600;
    color: var(--text-primary);
    user-select: none;
}
.help-faq summary:hover { background: rgba(255, 255, 255, 0.03); }
.help-faq[open] summary {
    border-bottom: 1px solid var(--border);
}
.help-faq-body {
    padding: 12px 16px 14px;
    font-size: 13px;
    color: var(--text-secondary);
    line-height: 1.55;
}
.help-faq-body p { margin: 0 0 10px; }
.help-faq-body p:last-child { margin-bottom: 0; }
.help-faq-body a { color: var(--accent); }

@media (max-width: 720px) {
    .help-step-card { padding: 18px 16px; }
    .help-stepper-title { font-size: 13px; }
    .help-screenshot-placeholder { flex-direction: column; align-items: flex-start; }
}

/* ===== FW-2: Flat Wizard sub-tab (AUTORUN → Flat Wizard) ===== */

/* Sections inside the Flat Wizard pane reuse the .settings-section
   visual chrome (card background + border) but override the SETTINGS-
   tab specific quirks that hurt here:
   - Drop the 400 px fixed height (those cards live in a CSS grid
     where uniform height matters; our sections stack vertically
     inside .autorun-list-col and should grow with content).
   - Loosen the inner padding so h4 + form sit comfortably without
     the sticky h3 chrome.
   - Bump the baseline font to 13 px so number inputs + labels are
     readable on phones / tablets. */
.flatwiz-pane .settings-section {
    height: auto;
    overflow: visible;
    padding: 12px 14px;
    margin-bottom: 12px;
    font-size: 13px;
}
.flatwiz-pane .settings-section > h4 {
    margin: 0 0 10px;
    font-size: 12px;
    font-weight: 600;
    color: var(--text-secondary);
    text-transform: uppercase;
    letter-spacing: 0.5px;
}

/* Dark + roomy form controls. Browser defaults render white bg + dark
   text which clashes with the rest of the dark theme; explicitly style
   number / text / select to use the input variables. Min-height + 9 px
   vertical padding gives a ~38 px touch target so the inputs are
   comfortable to tap on phones + tablets (Apple HIG recommends >= 44 px,
   Material >= 48 px; 38 px is the floor before things feel cramped). */
.flatwiz-pane input[type="number"],
.flatwiz-pane input[type="text"],
.flatwiz-pane select {
    background: var(--bg-input);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    color: var(--text-primary);
    font-size: 13px;
    padding: 9px 10px;
    min-height: 38px;
    outline: none;
    transition: border-color var(--transition);
    width: 100%;
    box-sizing: border-box;
}
.flatwiz-pane input[type="number"]:focus,
.flatwiz-pane input[type="text"]:focus,
.flatwiz-pane select:focus {
    border-color: var(--accent);
}
/* Settings form: each <label> stacks its caption above the input,
   which gives more horizontal room for the value (no cramped baseline
   row with caption + control fighting for width). */
.flatwiz-pane .settings-grid {
    grid-template-columns: 1fr 1fr;
    gap: 12px;
    padding: 0;
}
@media (min-width: 900px) {
    .flatwiz-pane .settings-grid { grid-template-columns: 1fr 1fr 1fr; }
}
@media (max-width: 600px) {
    .flatwiz-pane .settings-grid { grid-template-columns: 1fr; }
}
.flatwiz-pane .settings-grid > label {
    display: flex;
    flex-direction: column;
    gap: 4px;
    font-size: 12px;
    color: var(--text-secondary);
}

.flatwiz-preflight {
    display: flex;
    flex-direction: column;
    gap: 6px;
    font-size: 13px;
}
.flatwiz-preflight-row {
    display: flex;
    gap: 8px;
    align-items: baseline;
    padding: 4px 8px;
    border-radius: 4px;
    background: rgba(255, 255, 255, 0.03);
}
.flatwiz-preflight-row.ok   { border-left: 3px solid #22c55e; }
.flatwiz-preflight-row.warn { border-left: 3px solid #fbbf24; }
.flatwiz-preflight-row.err  { border-left: 3px solid #ef4444; }
.flatwiz-preflight-row.ok   > span:first-child { color: #22c55e; }
.flatwiz-preflight-row.warn > span:first-child { color: #fbbf24; }
.flatwiz-preflight-row.err  > span:first-child { color: #ef4444; }

.flatwiz-filter-pick {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
}
.flatwiz-filter-chip {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 4px 10px;
    border: 1px solid var(--border);
    border-radius: 999px;
    cursor: pointer;
    font-size: 13px;
    background: rgba(255, 255, 255, 0.04);
    user-select: none;
}
.flatwiz-filter-chip:hover { background: rgba(255, 255, 255, 0.08); }
.flatwiz-filter-chip input[type="checkbox"] {
    width: 14px;
    height: 14px;
    margin: 0;
    cursor: pointer;
}

.flatwiz-trained-table {
    width: 100%;
    border-collapse: collapse;
    font-size: 12px;
}
.flatwiz-trained-table th,
.flatwiz-trained-table td {
    padding: 4px 8px;
    border-bottom: 1px solid var(--border);
    text-align: left;
}
.flatwiz-trained-table th {
    color: var(--text-secondary);
    font-weight: 500;
    text-transform: uppercase;
    font-size: 10px;
    letter-spacing: 0.5px;
}
.flatwiz-trained-table tbody tr:hover { background: rgba(255, 255, 255, 0.03); }

/* Debug log panel scrollbar -- bumped to 20px wide. !important
   because the earlier 16px rule wasn't taking on the operator's
   Pi tablet (default UA rule kept winning at the same specificity).
   scrollbar-color/-width covers Firefox; ::-webkit-scrollbar covers
   Chromium + Safari. Thumb made more visible (0.32 alpha + min
   height 40px so it's grab-able even on short content). */
.log-panel-body {
    scrollbar-width: auto !important;
    scrollbar-color: rgba(255, 255, 255, 0.32) rgba(255, 255, 255, 0.06) !important;
}
.log-panel-body::-webkit-scrollbar {
    width: 20px !important;
    height: 20px !important;
}
.log-panel-body::-webkit-scrollbar-track {
    background: rgba(255, 255, 255, 0.06) !important;
}
.log-panel-body::-webkit-scrollbar-thumb {
    background: rgba(255, 255, 255, 0.32) !important;
    border-radius: 4px !important;
    min-height: 40px !important;
}
.log-panel-body::-webkit-scrollbar-thumb:hover {
    background: rgba(255, 255, 255, 0.5) !important;
}

/* Debug-log header controls (select + search input). Without an
   explicit class they fell back to UA white -- jarring on the dark
   panel and unreadable on the operator's tablet. Matches the
   .equip-input pattern used elsewhere. */
.log-ctl {
    background: rgba(255, 255, 255, 0.05);
    color: var(--text-primary);
    border: 1px solid rgba(255, 255, 255, 0.12);
    border-radius: 6px;
    padding: 5px 8px;
    font-size: 12px;
    font-family: inherit;
    outline: none;
}
.log-ctl:focus {
    border-color: var(--accent);
    background: rgba(255, 255, 255, 0.08);
}
/* <select> arrow stays system-default for clarity; just paint the
   option list so opened menus aren't bright white either. */
.log-ctl option {
    background: var(--bg-secondary);
    color: var(--text-primary);
}

/* Custom confirm() modal overlay -- z-index 10000 keeps it above the
   debug-log panel (z 9000) and any other overlay. First commit shipped
   the inline style at z 2000 which got buried behind the log, operator
   had to close the log to find the modal. Flex centering for the card;
   backdrop click cancels via @click.self. */
.confirm-modal-overlay {
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, 0.6);
    backdrop-filter: blur(4px);
    -webkit-backdrop-filter: blur(4px);
    z-index: 10000;
    display: flex;
    align-items: center;
    justify-content: center;
}
/* Custom confirm() modal card. Sized for readability on tablet
   without overflowing on phones. Sits inside the high-z-index
   .confirm-modal-overlay above. */
.confirm-modal-card {
    width: min(420px, 92vw);
    background: var(--bg-secondary);
    border: 1px solid var(--border);
    border-radius: 10px;
    padding: 20px 22px 16px;
    box-shadow: 0 16px 48px rgba(0, 0, 0, 0.6);
}
.confirm-modal-title {
    margin: 0 0 8px;
    font-size: 16px;
    font-weight: 600;
    color: var(--text-primary);
}
.confirm-modal-message {
    margin: 0 0 18px;
    font-size: 13px;
    line-height: 1.45;
    color: var(--text-secondary);
    /* Honour \n in messages (several confirms use multi-line bodies). */
    white-space: pre-line;
}
.confirm-modal-actions {
    display: flex;
    gap: 10px;
    justify-content: flex-end;
}

/* ---- Host-side Save/Open file dialog --------------------------------
   A file browser modal that navigates the HOST filesystem (via
   /api/files/*). Bigger than the confirm card because it holds a
   scrolling file list. Uses the same overlay + tokens. */
/* Leave a clear gap between the dialog and the window edges (overlay padding),
   so the card never sits flush against the top/bottom. */
.hostdlg-overlay { padding: 4vh 16px; box-sizing: border-box; }
.hostdlg-card {
    /* A real file browser: wide + tall so picking many frames isn't cramped.
       max-height:100% keeps it inside the overlay padding. */
    width: min(1040px, 100%);
    height: 84vh;
    max-height: 100%;
    display: flex;
    flex-direction: column;
    background: var(--bg-secondary);
    border: 1px solid var(--border);
    border-radius: 12px;
    box-shadow: 0 16px 48px rgba(0, 0, 0, 0.6);
    overflow: hidden;
}
.hostdlg-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    padding: 12px 14px;
    border-bottom: 1px solid var(--border);
}
.hostdlg-title {
    font-size: 15px;
    font-weight: 600;
    color: var(--text-primary);
}
.hostdlg-bar {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 10px 14px;
}
.hostdlg-path {
    flex: 1;
    min-width: 0;
    font-size: 12px;
    color: var(--text-secondary);
}
.hostdlg-list {
    flex: 1;
    min-height: 160px;
    overflow-y: auto;
    border-top: 1px solid var(--border);
    border-bottom: 1px solid var(--border);
    padding: 4px 0;
}
.hostdlg-err {
    padding: 12px;
    font-size: 13px;
    color: var(--danger, #e05260);
}
.hostdlg-row {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 6px 14px;
    font-size: 13px;
    color: var(--text-primary);
    cursor: pointer;
    user-select: none;
}
.hostdlg-row:hover {
    background: var(--bg-hover, rgba(255, 255, 255, 0.05));
}
.hostdlg-row.is-sel {
    background: var(--accent-soft, rgba(90, 160, 255, 0.18));
}
.hostdlg-row.is-dir .hostdlg-name {
    font-weight: 600;
}
.hostdlg-ico {
    flex: 0 0 auto;
}
.hostdlg-name {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.hostdlg-foot {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 12px 14px;
}
.hostdlg-fname {
    flex: 0 1 320px;
    font-size: 13px;
}

/* ---- Studio media viewer (animated GIF / video playback) ------------ */
.media-viewer-card {
    width: min(90vw, 1100px);
    max-height: 90vh;
    display: flex;
    flex-direction: column;
    background: var(--bg-secondary);
    border: 1px solid var(--border);
    border-radius: 12px;
    box-shadow: 0 16px 48px rgba(0, 0, 0, 0.6);
    overflow: hidden;
}
.media-viewer-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    padding: 10px 14px;
    border-bottom: 1px solid var(--border);
}
.media-viewer-title {
    font-size: 14px;
    font-weight: 600;
    color: var(--text-primary);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.media-viewer-body {
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 12px;
    background: #000;
    min-height: 200px;
    overflow: auto;
}
.media-viewer-media {
    max-width: 100%;
    max-height: 78vh;
    display: block;
    object-fit: contain;
}

/* ---- Power-box per-port grouping ------------------------------------ */
.pb-group-boxed {
    border-left: 2px solid var(--border);
    padding-left: 8px;
    margin: 6px 0;
}
.pb-group-head {
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--text-muted);
    font-weight: 600;
    margin: 4px 0 2px;
}

/* ---- Auth brand mark (loading / setup / login) ----------------------
   Polaris icon shown above the modal heading. On the boot/loading
   variant a thin accent-colored ring spins around it; on
   setup/login the ring is static (decorative gradient halo only).
   Same DOM in all three so the icon "stays put" between auth states
   instead of flashing. */
.auth-modal {
    text-align: center;
}
.auth-modal h2 {
    text-align: center;
}
.auth-modal .auth-hint,
.auth-modal .auth-field,
.auth-modal .auth-remember,
.auth-modal .auth-error,
.auth-modal .auth-recover {
    text-align: left;
}
.auth-brand {
    position: relative;
    width: 96px;
    height: 96px;
    margin: 0 auto 18px;
    display: flex;
    align-items: center;
    justify-content: center;
}
.auth-brand-icon {
    width: 72px;
    height: 72px;
    border-radius: 50%;
    object-fit: contain;
    filter: drop-shadow(0 4px 16px rgba(var(--accent-rgb), 0.35));
    position: relative;
    z-index: 1;
}
.auth-brand-static .auth-brand-icon {
    animation: auth-brand-pulse 4s ease-in-out infinite;
}
.auth-modal-boot .auth-brand-icon {
    animation: auth-brand-pulse 1.6s ease-in-out infinite;
}
/* Loading state spinner ring */
.auth-brand-spinner {
    position: absolute;
    inset: 0;
    border-radius: 50%;
    border: 3px solid rgba(var(--accent-rgb), 0.12);
    border-top-color: var(--accent, #60a5fa);
    animation: auth-brand-spin 1.1s linear infinite;
}
@keyframes auth-brand-spin {
    to { transform: rotate(360deg); }
}
@keyframes auth-brand-pulse {
    0%, 100% { transform: scale(1);    filter: drop-shadow(0 4px 16px rgba(var(--accent-rgb), 0.35)); }
    50%      { transform: scale(1.04); filter: drop-shadow(0 6px 22px rgba(var(--accent-rgb), 0.55)); }
}

/* ---- Modal polish pass: location-setup + editor-export ---------------
   Unify the visual rhythm across the app's modals. Existing rules win
   the base layout; these append visual upgrades on top:
   - Rounder corners (10px → 14px) to match the confirm-modal-card
   - Stronger shadow + subtle accent-color top border for the "sheet"
     feel, instead of looking like a flat 90s dialog
   - Fade-in animation so popping the modal isn't a hard cut
   No layout changes -- safe to ship without retesting the form flows. */
.location-setup-frame,
.editor-export-modal,
.confirm-modal-card {
    border-radius: 14px !important;
    box-shadow: 0 20px 60px rgba(0, 0, 0, 0.65),
                0 0 0 1px rgba(255, 255, 255, 0.04) inset !important;
    border-top: 2px solid var(--accent, #60a5fa);
    animation: modal-fade-in 180ms ease-out;
}
.location-setup-modal,
.confirm-modal-overlay {
    animation: modal-backdrop-fade 180ms ease-out;
}
@keyframes modal-fade-in {
    from { opacity: 0; transform: translateY(8px) scale(0.98); }
    to   { opacity: 1; transform: translateY(0)    scale(1); }
}
@keyframes modal-backdrop-fade {
    from { opacity: 0; }
    to   { opacity: 1; }
}
/* Location-setup heading bumped + accent-tinted so the operator's
   eye lands on the title before scanning the search box. */
.location-setup-frame h2 {
    font-size: 20px !important;
    font-weight: 600 !important;
    color: var(--text-primary);
}
