/* Stream + toolbar styles shared by the home-page bundle and the ws-scrcpy library bundle.
 *
 * This file is also loaded standalone by embed.html (which does NOT link app.css),
 * so it must self-contain every theme variable the stream/toolbar styling depends on.
 * Home-page callers (app.css) re-define the same variables — identical values, so
 * the cascade harmless. Light-mode auto-detection uses prefers-color-scheme, but
 * an explicit data-theme="light"/"dark" attribute on <html> wins.
 */

:root,
[data-theme="dark"] {
    --control-buttons-bg-color: #2d3741;
    --svg-button-fill: #ffffff;
    --svg-checkbox-bg-color: #10b981;
}

@media (prefers-color-scheme: light) {
    :root:not([data-theme="dark"]) {
        --control-buttons-bg-color: #f8f9fa;
        --svg-button-fill: #495057;
        --svg-checkbox-bg-color: #198754;
    }
}

[data-theme="light"] {
    --control-buttons-bg-color: #f8f9fa;
    --svg-button-fill: #495057;
    --svg-checkbox-bg-color: #198754;
}

/* Stream layout: device-view is a flex row (video | toolbar). Video cell uses
 * CSS grid so the video-layer and touch-layer stack in the same cell — this
 * replaces legacy position:absolute layers which broke parent auto-sizing.
 * DOM order is toolbar-first; the toolbar uses `order: 1` to appear right.
 *
 * Defaults deliberately DO NOT use flex:1 — that would force the container to
 * stretch to max height in fit-content parents (like the ConnectModal frame),
 * leaving dead space below a smaller canvas. Full-body embed contexts that
 * need the stream to fill the viewport opt in via body[data-embed-entry]
 * below. */
.device-view {
    display: flex;
    flex-direction: row;
    min-width: 0;
    min-height: 0;
}

.video {
    display: grid;
    min-width: 0;
    min-height: 0;
    place-items: center;
    background-color: #000;
    /* Grid layout + the canvas max-width/max-height caps control the displayed
     * cell size with correct aspect; `.video` itself stays auto. The device
     * resolution is exposed as the --video-width / --video-height custom props
     * (set by the player's setScreenInfo) instead of an inline width/height, so
     * no `!important` is needed to keep the cell auto-sized. (#106) */
    width: auto;
    height: auto;
}

/* Viewport-based max caps on the canvas layers. Canvas elements preserve
 * intrinsic aspect ratio when both max-width and max-height are set, so these
 * make the canvas shrink-to-fit the viewport while preserving the device
 * aspect ratio. (The player's `calculateScreenInfoForBounds` never runs
 * because `resizeVideoToBounds` is false on every subclass — so CSS alone
 * has to do the sizing.) Modal context overrides these with narrower caps. */
.video-layer {
    grid-area: 1 / 1;
    max-width: calc(100vw - 3.715rem);
    max-height: 100vh;
}

/* Shown over the canvas while the device reports its keyguard is up. Android
 * will not let anything capture the lock screen, so the stream is legitimately
 * black and there is nothing to fix on our side — the point is to say so
 * instead of leaving a black rectangle with no explanation. */
.stream-locked-notice {
    grid-area: 1 / 1;
    align-self: center;
    justify-self: center;
    z-index: 2;
    padding: 0.6rem 1rem;
    border: 1px solid var(--warning-color);
    border-radius: 4px;
    background-color: var(--controls-bg-color);
    color: var(--warning-color);
    font-size: 0.85rem;
    text-align: center;
    /* Never swallow touches meant for the device. */
    pointer-events: none;
}

.stream-locked-notice[hidden] {
    display: none;
}

.control-wrapper > input[type="checkbox"] {
    display: none;
}

.control-wrapper > label.control-button {
    display: block;
    margin: 0.571rem auto;
}

.control-wrapper > input[type="checkbox"].two-images:checked + label > svg.image-on {
    display: block;
}

.control-wrapper > input[type="checkbox"].two-images:not(:checked) + label > svg.image-on {
    display: none;
}

.control-wrapper > input[type="checkbox"].two-images:checked + label > svg.image-off {
    display: none;
}

.control-wrapper > input[type="checkbox"].two-images:not(:checked) + label > svg.image-off {
    display: block;
}

.control-wrapper > input[type="checkbox"]:checked + label > svg {
    fill: var(--svg-checkbox-bg-color);
}

.touch-layer {
    grid-area: 1 / 1;
    max-width: calc(100vw - 3.715rem);
    max-height: 100vh;
}

.control-buttons-list {
    flex-shrink: 0;
    width: 3.715rem;
    background-color: var(--control-buttons-bg-color);
    order: 1; /* DOM order is toolbar-first, but we want video-left toolbar-right */
}

/* Embed page (embed.html) marks its body with data-embed-entry. The stream
 * container fills the viewport, but the video cell itself stays content-sized
 * so canvas + toolbar hug together as a pair — matches the legacy float:right
 * look where the toolbar is flush against the mirror's right edge. Without
 * justify-end the pair sits left and portrait devices leave the toolbar
 * marooned at the far right. Modal contexts (fit-content frame) skip this. */
body[data-embed-entry] .device-view {
    flex: 1;
    justify-content: flex-end;
}

.control-button {
    margin: 0.571rem auto;
    padding: 0;
    display: block;
    width: 2.143rem;
    height: 2.143rem;
    border: none;
    opacity: 0.75;
    background-color: var(--control-buttons-bg-color);
    overflow: hidden;
}

.control-button:hover {
    opacity: 1;
}

/* biome-ignore lint/style/noDescendingSpecificity: intentional source order; reordering would change the cascade */
.control-button > svg {
    fill: var(--svg-button-fill);
    width: 100%;
    height: 100%;
    display: block;
}

:root {
    --font-size: 14px;
    --font-mono: Consolas, "Segoe UI Mono", "SF Mono", Menlo, "Liberation Mono", "DejaVu Sans Mono", monospace;
}

/* Dark theme (default) */
:root,
[data-theme="dark"] {
    --main-bg-color: #2a2a2a;
    --stream-bg-color: #333333;
    --shell-bg-color: #000000;
    --text-shadow-color: hsl(218, 17%, 18%);
    --header-bg-color: #333333;
    --controls-bg-color: rgba(45, 55, 65, 0.8);
    --text-color: #e0e0e0;
    --text-color-light: #a8a8a8;
    --success-color: #4ade80;
    --success-hover-bg: rgba(74, 222, 128, 0.1);
    --danger-color: #f06c75;
    --danger-hover-bg: rgba(240, 108, 117, 0.1);
    --warning-color: #fb923c;
    --info-color: #60a5fa;
    --link-color: hsl(218, 63%, 70%);
    --link-color-light: hsl(218, 63%, 50%);
    --link-color-visited: hsl(267, 31%, 47%);
    --link-color-visited-light: hsl(267, 31%, 27%);
    --kill-button-hover-color: #f06c75;
    --url-color: #888888;
    --button-text-color: hsl(214, 82%, 76%);
    --button-border-color: #444444;
    --progress-background-color: rgba(50, 100, 255, 0.2);
    --progress-background-error-color: rgba(255, 50, 50, 0.2);
    --accent-color: #5b9aff;
    --accent-rgb: 91, 154, 255;
    --danger-rgb: 240, 108, 117;
    --success-rgb: 74, 222, 128;
    --modal-divider: rgba(255, 255, 255, 0.08);
    --border-muted: rgba(255, 255, 255, 0.2);
}

/* Light theme */
[data-theme="light"] {
    --main-bg-color: #ffffff;
    --stream-bg-color: #f0f2f5;
    --shell-bg-color: #000000;
    --text-shadow-color: hsl(218, 67%, 95%);
    --header-bg-color: #f8f9fa;
    --controls-bg-color: rgba(248, 249, 250, 0.8);
    --text-color: #212529;
    --text-color-light: #495057;
    --success-color: #15803d;
    --success-hover-bg: rgba(21, 128, 61, 0.1);
    --danger-color: #b91c1c;
    --danger-hover-bg: rgba(185, 28, 28, 0.1);
    --warning-color: #b45309;
    --info-color: #0969da;
    --link-color: hsl(218, 85%, 43%);
    --link-color-light: hsl(218, 85%, 73%);
    --link-color-visited: hsl(271, 68%, 32%);
    --link-color-visited-light: hsl(271, 68%, 72%);
    --kill-button-hover-color: #dc3545;
    --url-color: #6c757d;
    --button-text-color: #0d6efd;
    --button-border-color: #c0c6cc;
    --progress-background-color: rgba(50, 100, 255, 0.2);
    --progress-background-error-color: rgba(255, 50, 50, 0.2);
    /* Darker than the dark-theme accent so it clears WCAG AA contrast (at least
     * 4.5:1) as a focus ring / accent text color on the light background —
     * #0969da is ~5.2:1 on white; reuses the light --info-color blue. */
    --accent-color: #0969da;
    --accent-rgb: 9, 105, 218;
    --danger-rgb: 185, 28, 28;
    --success-rgb: 21, 128, 61;
    --modal-divider: rgba(0, 0, 0, 0.08);
    --border-muted: rgba(0, 0, 0, 0.2);
}

html {
    font-size: var(--font-size);
}

/* Respect the OS "reduce motion" preference (WCAG 2.3.3): collapse animations
 * and transitions to near-instant rather than removing them, so state changes
 * stay legible. `!important` is the idiomatic override for this global reset. (#103) */
@media (prefers-reduced-motion: reduce) {
    *,
    *::before,
    *::after {
        /* biome-ignore-start lint/complexity/noImportantStyles: WCAG prefers-reduced-motion global reset — !important is the idiomatic override (#103) */
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
        scroll-behavior: auto !important;
        /* biome-ignore-end lint/complexity/noImportantStyles: end reduced-motion reset */
    }
}

a {
    color: var(--link-color);
}

a:visited {
    color: var(--link-color-visited);
}

body {
    color: var(--text-color);
    background-color: var(--main-bg-color);
    position: absolute;
    margin: 0;
    height: 100%;
    width: 100%;
    overflow: hidden;
}

body.shell {
    background-color: var(--shell-bg-color);
}

body.stream {
    background-color: var(--stream-bg-color);
}

.terminal-container {
    width: 100%;
    height: 100%;
    padding: 5px;
}

/* Keyboard focus indicator (WCAG 2.4.7). Inputs that swap to a border highlight
   opt out via their own more-specific :focus rule; everything else shows this
   outline on keyboard navigation only (not on mouse click). */
:focus-visible {
    outline: 2px solid var(--accent-color);
    outline-offset: 2px;
}

.wait {
    cursor: wait;
}

/* The "bookmark this URL" reminder (BookmarkReminder). In-flow at the top of
   the page: it never covers the app or captures its clicks — the reason it
   replaced the PortChangeModal / ServiceFirstRunModal dialogs (item 113).
   Colours come from the theme tokens only (design-tokens.test.ts). */
.bookmark-reminder {
    margin: 1rem auto;
    max-width: 960px;
    padding: 0.75rem 1rem;
    border-radius: 6px;
    background-color: rgba(var(--accent-rgb), 0.08);
    border: 1px solid var(--border-muted);
    border-left: 3px solid var(--accent-color);
    color: var(--text-color);
    font-size: 14px;
    line-height: 1.5;
}

.bookmark-reminder-inner {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    flex-wrap: wrap;
}

.bookmark-reminder-text {
    flex: 1 1 320px;
}

.bookmark-reminder-text a {
    color: var(--accent-color);
}

.bookmark-reminder-actions {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    flex-shrink: 0;
}

.bookmark-reminder-got-it,
.bookmark-reminder-never {
    padding: 0.35rem 0.85rem;
    border-radius: 4px;
    border: 1px solid var(--text-color);
    background: transparent;
    color: var(--text-color);
    font-size: 14px;
    cursor: pointer;
}

.bookmark-reminder-never {
    border-color: var(--border-muted);
    color: var(--text-color-light);
}

.bookmark-reminder-got-it:hover,
.bookmark-reminder-never:hover {
    background-color: rgba(var(--accent-rgb), 0.12);
}

.bookmark-reminder-close {
    border: 0;
    background: transparent;
    color: var(--text-color-light);
    font-size: 18px;
    line-height: 1;
    cursor: pointer;
    padding: 0 0.25rem;
}

.bookmark-reminder-close:hover {
    color: var(--text-color);
}

.dep-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 0.75rem;
}

.dep-header h2 {
    margin: 0;
}

.dep-table {
    width: 100%;
    border-collapse: collapse;
    font-size: 1rem;
}

.dep-table th {
    text-align: left;
    padding: 0.4rem 0.6rem;
    border-bottom: 1px solid var(--button-border-color);
    color: var(--text-color-light);
    font-weight: 600;
    white-space: nowrap;
}

.dep-table td {
    padding: 0.45rem 0.6rem;
    border-bottom: 1px solid var(--button-border-color);
    vertical-align: middle;
}

.dep-table tr:last-child td {
    border-bottom: none;
}

.dep-version {
    font-family: var(--font-mono);
    font-size: 0.93rem;
    white-space: nowrap;
}

.dep-description {
    font-size: 0.93rem;
    color: var(--text-color-light);
    margin-top: 0.2rem;
}

.dep-paired {
    font-size: 0.86rem;
    color: var(--text-color-light);
    margin-left: 0.35rem;
}

/* Buttons */
.dep-btn {
    padding: 0.3rem 0.75rem;
    border: 0.5px solid var(--accent-color);
    border-radius: 6px;
    background: transparent;
    color: var(--accent-color);
    font-family: var(--font-mono);
    font-size: var(--font-size);
    cursor: pointer;
    white-space: nowrap;
    transition: background-color 0.15s;
}

.dep-btn:hover:not(:disabled) {
    background-color: var(--device-list-hover-color);
}

.dep-btn:disabled {
    opacity: 0.45;
    cursor: not-allowed;
}

/* "Check for updates" — semantic match with the "turn on" / success
   sleep-wake button. Border + text in success-green. */
.dep-btn.dep-check-all {
    border-color: var(--success-color);
    color: var(--success-color);
}

/* Network-discovery scan buttons — semantic match with caution / start-
   an-action. Border + text in warning-orange (--warning-color is solidly
   orange now: dark #fb923c, light #b45309). */
.dep-btn.discovery-quick-scan-btn,
.dep-btn.discovery-scan-btn,
.dep-btn.discovery-manual-btn {
    border-color: var(--warning-color);
    color: var(--warning-color);
}

.dep-update {
    border-color: #2a6a2a;
    color: var(--success-color);
    background-color: #1a3a1a;
}

.dep-update:hover:not(:disabled) {
    background-color: #1f451f;
    opacity: 1;
}

/* Status badges */
.dep-badge {
    display: inline-block;
    padding: 0.2rem 0.55rem;
    border-radius: 999px;
    border: 0.5px solid currentColor;
    background: transparent;
    font-size: 0.86rem;
    font-weight: 500;
    white-space: nowrap;
}

.dep-ok {
    color: var(--success-color);
}

.dep-warn {
    color: var(--warning-color);
}

.dep-info {
    color: var(--info-color);
}

.dep-error {
    color: var(--danger-color);
    cursor: help;
}

.dep-unknown {
    color: var(--text-color-light);
}

/* Restart prompt bar */
.dep-restart-prompt {
    display: flex;
    align-items: center;
    gap: 1rem;
    padding: 0.6rem 0.75rem;
    margin-bottom: 0.75rem;
    background: #3a3a1a;
    border: 1px solid #5a5a1a;
    border-radius: 4px;
    color: #fbbf24;
    font-size: 1rem;
}

.dep-restart-prompt p {
    margin: 0;
    flex: 1;
}

.dep-restart-btn {
    border-color: #7a5a00;
    color: #fbbf24;
    background-color: #4a3a00;
}

.dep-restart-btn:hover:not(:disabled) {
    background-color: #5a4800;
    opacity: 1;
}

/* Restarting overlay */
.dep-restarting {
    padding: 2rem;
    text-align: center;
    color: var(--text-color-light);
}

.dep-restarting h2 {
    margin: 0 0 0.5rem;
    color: var(--text-color);
}

.dep-restarting p {
    margin: 0;
    font-size: 1rem;
}

/* Error message row */
.dep-error-msg {
    text-align: center;
    color: var(--danger-color);
    padding: 1rem;
    font-size: 1rem;
}

.first-run-banner {
    /* Fixed amber + near-black for theme-independent high contrast (~11:1, exceeds WCAG AA). */
    margin: 1rem auto;
    max-width: 960px;
    padding: 0.75rem 1rem;
    border-radius: 6px;
    background-color: #ffb74d;
    color: #1a1a1a;
    border: 1px solid rgba(0, 0, 0, 0.2);
    font-size: 14px;
}

.first-run-banner-inner {
    display: flex;
    align-items: center;
    gap: 0.75rem;
}

.first-run-banner-icon {
    font-size: 18px;
    line-height: 1;
    flex-shrink: 0;
}

.first-run-banner-text {
    flex: 1;
}

.first-run-banner-retry {
    flex-shrink: 0;
    padding: 0.35rem 0.85rem;
    border-radius: 4px;
    border: 1px solid rgba(0, 0, 0, 0.35);
    background: transparent;
    color: inherit;
    font-size: 14px;
    cursor: pointer;
}

.first-run-banner-retry:hover:not(:disabled) {
    background-color: rgba(0, 0, 0, 0.08);
}

.first-run-banner-retry:disabled {
    opacity: 0.6;
    cursor: not-allowed;
}

/* src/style/home.css */

/* Theme toggle */
.theme-toggle {
    position: fixed;
    top: 12px;
    right: 12px;
    z-index: 100;
    background: var(--device-list-default-color);
    border: 1px solid var(--device-border-color);
    border-radius: 50%;
    width: 36px;
    height: 36px;
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    color: var(--text-color);
    transition: background-color 0.15s;
}

.theme-toggle:hover {
    background: var(--device-list-hover-color);
}

/* Settings (gear) header button — sits to the LEFT of the theme toggle.
   Mirrors .theme-toggle dimensions/glassmorphism for visual consistency. */
.settings-header {
    position: fixed;
    top: 12px;
    right: 56px; /* 12px (theme-toggle right) + 36px (theme-toggle width) + 8px gap */
    z-index: 100;
    background: var(--device-list-default-color);
    border: 1px solid var(--device-border-color);
    border-radius: 50%;
    width: 36px;
    height: 36px;
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    color: var(--text-color);
    transition: background-color 0.15s;
    padding: 0;
}

.settings-header:hover {
    background: var(--device-list-hover-color);
}

/* Page container — centers content, caps at 5 device cards.
   padding-top reserves space at the top for the fixed-position controls
   cluster (settings + theme + update button, all at top:12px with 36px
   height = 48px bottom edge), so the connected-devices section's outer
   top border sits ~8px below the controls instead of being crowded by
   them. */
.page-container {
    max-width: 1800px;
    margin: 0 auto;
    padding: 56px 0 0 0;
}

/* Page sections */
.home-section {
    padding: 20px;
    margin-bottom: 35px;
    font-family: var(--font-mono);
    border: 1.5px solid var(--section-border-color, #5a5a5a);
    border-radius: 8px;
}

.home-section h2 {
    margin: 0 0 16px 0;
    font-size: 18px;
    color: var(--text-color);
    font-weight: 600;
}

.section-divider {
    border: none;
    border-top: 1px solid var(--device-border-color);
    margin: 0;
}

/* Subtle outline card for section content */
.section-card {
    border: 1px solid var(--device-border-color);
    border-radius: 8px;
    padding: 16px;
    background: var(--device-list-default-color);
}

/* Empty state card — used when no results to show */
.empty-state-card {
    border: 1px solid var(--device-border-color);
    border-radius: 8px;
    padding: 16px;
    background: var(--device-list-default-color);
    color: var(--text-color-light);
    font-size: 14px;
    text-align: center;
}

/* Network Discovery */
.discovery-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 12px;
}

.discovery-header h2 {
    margin: 0;
}

.discovery-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
    gap: 10px;
}

.discovery-results:not(:empty) {
    margin-bottom: 15px;
}

.discovery-card {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    padding: 12px 16px;
    border: 1px solid var(--device-border-color);
    border-radius: 8px;
    background: var(--device-list-default-color);
}

.discovery-card-info {
    min-width: 0;
    flex: 1 1 auto;
}

.discovery-card-name {
    font-size: 14px;
    font-weight: 500;
    color: var(--text-color);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.discovery-card-name:empty {
    display: none;
}

.discovery-card-address {
    font-size: 12px;
    font-family: var(--font-mono);
    color: var(--text-color-light);
    margin-top: 2px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.discovery-card-actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px;
    width: 100%;
    margin-top: 8px;
}

.discovery-name-input {
    flex-basis: 100%;
    background: var(--stream-bg-color);
    border: 1px solid var(--device-border-color);
    border-radius: 6px;
    color: var(--text-color);
    font-size: 13px;
    padding: 6px 10px;
}

.discovery-name-input:focus,
.discovery-manual-form input[type="text"]:focus {
    outline: none;
    border-color: var(--accent-color);
}

.discovery-name-input::placeholder {
    color: var(--text-color-light);
}

.discovery-header-actions {
    display: flex;
    gap: 8px;
}

.discovery-manual-form {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px;
    margin-bottom: 12px;
    padding: 12px;
    border: 1px solid var(--device-border-color);
    border-radius: 8px;
    background: var(--device-list-default-color);
}

.discovery-manual-form[hidden] {
    display: none;
}

.discovery-manual-form input[type="text"] {
    background: var(--stream-bg-color);
    border: 1px solid var(--device-border-color);
    border-radius: 6px;
    color: var(--text-color);
    font-size: 13px;
    padding: 6px 10px;
}

.discovery-manual-form input[type="text"]::placeholder {
    color: var(--text-color-light);
}

.discovery-manual-form input.discovery-manual-address {
    flex: 1;
    min-width: 140px;
    font-family: var(--font-mono);
}

.discovery-manual-form input.discovery-manual-port {
    width: 70px;
    font-family: var(--font-mono);
}

.discovery-manual-form input.discovery-manual-label {
    flex: 1;
    min-width: 140px;
}

.discovery-manual-close {
    background: transparent;
    border: none;
    color: var(--text-color-light);
    font-size: 20px;
    line-height: 1;
    cursor: pointer;
    padding: 4px 8px;
}

.discovery-manual-close:hover {
    color: var(--text-color);
}

.discovery-manual-result,
.discovery-card-result {
    flex-basis: 100%;
    font-size: 13px;
    margin-top: 4px;
    font-family: var(--font-mono);
    word-break: break-word;
}

.discovery-manual-result.error,
.discovery-card-result.error {
    color: var(--danger-color);
}

.discovery-manual-result.success,
.discovery-card-result.success {
    color: var(--success-color);
}

/* Discovery action buttons recolor the .dep-btn base (blue) to success-green /
   danger-red. Qualified with the base class so they win by specificity rather
   than !important. (The unreferenced .dep-ok-btn rule that lived here was dead
   CSS and was removed.) */
.dep-btn.discovery-connect-btn {
    border-color: var(--success-color);
    color: var(--success-color);
    background: transparent;
}

.dep-btn.discovery-connect-btn:hover:not(:disabled) {
    background: var(--success-hover-bg);
}

.dep-btn.discovery-dismiss-btn {
    border-color: var(--danger-color);
    color: var(--danger-color);
    background: transparent;
}

.dep-btn.discovery-dismiss-btn:hover:not(:disabled) {
    background: var(--danger-hover-bg);
}

/* ── Update button (top-right, third from edge) ──
   Layout: [update-button] ← [settings-header] ← [theme-toggle] ← right edge.
   Theme toggle sits at right:12px (36px wide), settings-header at right:56px
   (36px wide + 8px gap), so update-button starts at right:100px and grows
   leftward (it's a pill, not a circle). Mirrors their glassmorphism. */
.update-button-container {
    position: fixed;
    top: 12px;
    right: 100px;
    z-index: 100;
    display: flex;
    align-items: center;
    gap: 6px;
    height: 36px;
    padding: 0 12px;
    background: var(--device-list-default-color);
    border: 1px solid var(--device-border-color);
    border-radius: 18px;
    color: var(--text-color);
    font-size: 13px;
    font-family: var(--font-mono);
    transition:
        background-color 0.15s,
        border-color 0.15s,
        color 0.15s;
}

.update-button-container .update-button-label {
    white-space: nowrap;
}

.update-button-container .update-button-action,
.update-button-container .update-button-retry {
    background: transparent;
    border: none;
    color: inherit;
    font-family: var(--font-mono);
    font-size: inherit;
    cursor: pointer;
    padding: 0;
    white-space: nowrap;
}

.update-button-container .update-button-action:disabled,
.update-button-container .update-button-retry:disabled {
    opacity: 0.5;
    cursor: not-allowed;
}

.update-button-container .update-button-retry {
    margin-left: 4px;
    text-decoration: underline;
}

/* Spinner — pure CSS (no SVG). */
.update-button-container .update-button-spinner {
    display: inline-block;
    width: 12px;
    height: 12px;
    border: 2px solid currentColor;
    border-top-color: transparent;
    border-radius: 50%;
    animation: update-button-spin 0.8s linear infinite;
}

@keyframes update-button-spin {
    to {
        transform: rotate(360deg);
    }
}

/* State colors — map to existing CSS vars where they exist; fall back to
   sensible literals matching the rest of the app's palette. */
.update-button-container.state-checking {
    color: var(--text-color-light);
    border-color: var(--device-border-color);
}

.update-button-container.state-downloading {
    color: var(--accent-color); /* deep blue — matches .settings-btn-primary */
    border-color: var(--accent-color);
}

.update-button-container.state-downloading:hover {
    background: var(--device-list-hover-color);
}

.update-button-container.state-ready {
    color: var(--success-color);
    border-color: var(--success-color);
    cursor: pointer;
}

.update-button-container.state-ready:hover {
    background: var(--success-hover-bg);
}

.update-button-container.state-error {
    color: var(--danger-color);
    border-color: var(--danger-color);
}

/* ── Native <dialog> modal system ── */

/* ── A note on the noDescendingSpecificity suppressions below (item 122, audited
   2026-09-09) ──
   Biome flagged 11 selectors in this file. Every one was checked by hand against
   the rule it was flagged against, and all 11 are false positives, for one of
   three reasons:

   1. DISJOINT PROPERTIES (.modal-frame, 6 sites). The high-specificity rules
      they collide with are the nested-modal and light-theme frame overrides
      right below, which set `background` and nothing else. Every flagged rule
      sets opacity/transform/transition or width/max-height — never `background`
      — so nothing is shadowed either way.
   2. MUTUALLY EXCLUSIVE DIALOG CLASSES (.modal-body, 3 sites).
      `dialog.modal.shell-modal` versus `dialog.connect-modal`,
      `dialog.list-files-modal`, `dialog.service-operation-modal`: a dialog
      carries exactly one of these, so no element ever matches two of them.
   3. SIBLING CONTAINERS (.modal-advanced, 2 sites). `.modal-controls` and
      `.modal-advanced` are SIBLINGS — `ConfigureScrcpy.buildBody()` appends both
      to the modal body — so an input under one is never under the other.

   The rule assumes a stylesheet ordered by specificity; this one is ordered by
   component section, which is the right organisation for it and cannot be
   reconciled with the rule by reordering (moving a block merely re-points the
   complaint at the next-highest rule above it). So the rule stays ON — it still
   guards every other selector in this repo, and Biome's own `suppressions/unused`
   check turns a suppression red once it stops applying — and all 11 sites carry
   a suppression naming which of the three reasons applies: nine inline, plus one
   `biome-ignore-start`/`-end` range over the `.modal-advanced` field rule (a
   selector list needs one suppression per flagged selector, and interleaving
   comments inside the list makes the formatter rewrap it).

   NOT DONE, deliberately: raising the flagged selectors' specificity to silence
   the rule "properly". Rewriting the `.modal-advanced` list as
   `:is(select, input:not([type="checkbox"]))` does exactly that — same match set,
   specificity (0,3,2) instead of (0,2,2) — and it CHANGES BEHAVIOUR. At (0,2,2)
   the `select` selector loses to the `:disabled` rule above (also (0,3,2)) and a
   disabled advanced select keeps its faded background; level them up and this
   rule, being later, wins instead. Nothing here is worth a visual regression, so
   the properties stay exactly as they were and only comments were added. */

/* The <dialog> itself is an invisible full-screen positioning layer.
   padding:0 is critical: clicks on ::backdrop bubble to the <dialog> element,
   and we detect backdrop clicks by checking event.target === dialog.
   All visible content lives inside .modal-frame. */
dialog.modal {
    padding: 0;
    border: none;
    background: transparent;
    max-width: 100vw;
    max-height: 100vh;
    overflow: visible;
}

/* ── Backdrop ── */
dialog.modal::backdrop {
    background: rgba(0, 0, 0, 0.45);
}

/* ── Glassmorphism frame ── */
dialog.modal .modal-frame {
    font-family: var(--font-mono);
    color: var(--text-color, #ddd);
    width: clamp(400px, 50vw, 700px);
    max-height: 80vh;
    display: flex;
    flex-direction: column;
    background: rgba(42, 42, 42, 0.85);
    border: 1px solid rgba(255, 255, 255, 0.1);
    border-radius: 12px;
    box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
    overflow: hidden;
}

[data-theme="light"] dialog.modal .modal-frame {
    background: rgba(245, 248, 252, 0.87);
    border: 1px solid rgba(0, 0, 0, 0.1);
    box-shadow: 0 8px 32px rgba(0, 0, 0, 0.15);
}

/* Nested-modal readability: when a second modal opens on top, the top one uses
   a fully opaque frame so its content is crisp over the lower modal's glass. */
body:has(dialog.modal[open] ~ dialog.modal[open]) dialog.modal[open]:last-of-type .modal-frame {
    background: rgb(42, 42, 42);
}

[data-theme="light"] body:has(dialog.modal[open] ~ dialog.modal[open]) dialog.modal[open]:last-of-type .modal-frame {
    background: rgb(245, 248, 252);
}

dialog.modal .modal-frame button,
dialog.modal .modal-frame select,
dialog.modal .modal-frame input {
    font-family: var(--font-mono);
    font-size: var(--font-size);
}

dialog.modal .modal-frame select {
    text-overflow: ellipsis;
}

/* ── Open/close transitions via @starting-style ──
   How this works:
   - Base state (no [open]) = exit-end values (invisible)
   - [open] state = visible values
   - @starting-style = entry-start values (invisible, same as base)
   - allow-discrete on display/overlay keeps dialog rendered during exit transition
*/

/* Dialog element: transparent container, stays rendered during exit */
dialog.modal {
    opacity: 0;
    transition:
        opacity 0.2s ease-out,
        display 0.2s ease-out allow-discrete,
        overlay 0.2s ease-out allow-discrete;
}

dialog.modal[open] {
    opacity: 1;
}

/* Frame: exit-end state (invisible, scaled down) */
/* biome-ignore lint/style/noDescendingSpecificity: no background here; the earlier higher-specificity rule sets background only — disjoint properties, see the audit note at the top */
dialog.modal .modal-frame {
    opacity: 0;
    transform: scale(0.96) translateY(8px);
    transition:
        opacity 0.2s ease-out,
        transform 0.2s ease-out;
}

/* Frame: visible state when open */
/* biome-ignore lint/style/noDescendingSpecificity: no background here; the earlier higher-specificity rule sets background only — disjoint properties, see the audit note at the top */
dialog.modal[open] .modal-frame {
    opacity: 1;
    transform: scale(1) translateY(0);
}

/* Backdrop: exit-end state */
dialog.modal::backdrop {
    background: rgba(0, 0, 0, 0.45);
    opacity: 0;
    transition:
        opacity 0.2s ease-out,
        display 0.2s ease-out allow-discrete;
}

/* Backdrop: visible state when open */
dialog.modal[open]::backdrop {
    opacity: 1;
}

/* Entry animation: these are the "from" values when [open] is first applied */
@starting-style {
    dialog.modal[open] {
        opacity: 0;
    }
    dialog.modal[open] .modal-frame {
        opacity: 0;
        transform: scale(0.96) translateY(8px);
    }
    dialog.modal[open]::backdrop {
        opacity: 0;
    }
}

/* ── Header ── */
dialog.modal .modal-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 0.75rem 1rem;
    border-bottom: 1px solid var(--modal-divider);
    flex-shrink: 0;
}

dialog.modal .modal-title {
    font-size: 15px;
    font-weight: 600;
}

dialog.modal .modal-header-controls {
    display: flex;
    align-items: center;
    gap: 40px;
}

/* Reset home-page theme-toggle fixed positioning when it's reused inside a modal */
dialog.modal .modal-header-controls .theme-toggle {
    position: static;
    top: auto;
    right: auto;
    z-index: auto;
    width: auto;
    height: auto;
    background: transparent;
    border: none;
    border-radius: 0;
}

dialog.modal .modal-close {
    background: transparent;
    border: none;
    color: var(--text-color-light, #888);
    cursor: pointer;
    padding: 4px;
    line-height: 1;
    font-size: 18px;
}

dialog.modal .modal-close:hover {
    color: var(--text-color, #ddd);
}

/* ── Body (scrollable) ── */
dialog.modal .modal-body {
    padding: 1rem;
    overflow-y: auto;
    flex: 1;
    min-height: 0;
}

/* ── Footer ── */
dialog.modal .modal-footer {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 0.75rem 1rem;
    border-top: 1px solid var(--modal-divider);
    flex-shrink: 0;
}

/* ── Controls grid (used by ConfigureScrcpy) ── */
dialog.modal .modal-controls {
    display: grid;
    grid-template-columns: [labels] 35% [controls] 1fr;
    gap: 0.5rem 0.75rem;
    align-items: center;
    padding-right: 0.5rem;
    min-width: 0;
}

dialog.modal .modal-controls select,
dialog.modal .modal-controls input {
    max-width: 100%;
    box-sizing: border-box;
    min-width: 0;
}

dialog.modal .modal-controls .label {
    grid-column: labels;
    color: var(--text-color-light, #888);
    font-size: 13px;
}

dialog.modal .modal-controls .input,
dialog.modal .modal-controls select,
dialog.modal .modal-controls input:not([type="checkbox"]) {
    grid-column: controls;
    box-sizing: border-box;
    background: var(--stream-bg-color, #1a1a2e);
    border: 1px solid rgba(255, 255, 255, 0.1);
    border-radius: 6px;
    color: var(--text-color, #ddd);
    padding: 4px 8px;
}

[data-theme="light"] dialog.modal .modal-controls select,
[data-theme="light"] dialog.modal .modal-controls input:not([type="checkbox"]) {
    background: #fff;
    border-color: rgba(0, 0, 0, 0.15);
    color: #333;
}

dialog.modal .modal-controls select:focus,
dialog.modal .modal-controls input:focus {
    outline: none;
    border-color: var(--accent-color);
}

dialog.modal .modal-controls input:disabled,
dialog.modal .modal-controls select:disabled,
dialog.modal .modal-advanced input:disabled,
dialog.modal .modal-advanced select:disabled {
    opacity: 0.4;
    cursor: not-allowed;
    background: rgba(255, 255, 255, 0.03);
}

[data-theme="light"] dialog.modal .modal-controls input:disabled,
[data-theme="light"] dialog.modal .modal-controls select:disabled,
[data-theme="light"] dialog.modal .modal-advanced input:disabled,
[data-theme="light"] dialog.modal .modal-advanced select:disabled {
    background: #eee;
}

/* ── Slider ── */
dialog.modal .modal-controls input[type="range"] {
    grid-column: controls;
    width: 100%;
    cursor: pointer;
    accent-color: var(--accent-color);
}

/* ── Advanced toggle ── */
dialog.modal .modal-advanced-separator {
    grid-column: 1 / -1;
    border-top: 1px solid var(--modal-divider);
    margin-top: 0.5rem;
    padding-top: 0.5rem;
}

dialog.modal .modal-advanced-toggle {
    grid-column: 1 / -1;
    display: flex;
    align-items: center;
    justify-content: space-between;
    cursor: pointer;
    padding: 0.25rem 0;
    color: var(--text-color-light, #888);
    font-size: 13px;
    user-select: none;
    background: transparent;
    border: none;
    width: 100%;
    font-family: var(--font-mono);
}

dialog.modal .modal-advanced-toggle:hover {
    color: var(--text-color, #ddd);
}

dialog.modal .modal-advanced-toggle .chevron {
    transition: transform 0.3s ease;
    font-size: 12px;
}

dialog.modal .modal-advanced-toggle .chevron.expanded {
    transform: rotate(180deg);
}

/* ── Advanced section (animated reveal) ── */
dialog.modal .modal-advanced {
    grid-column: 1 / -1;
    display: grid;
    grid-template-columns: [labels] 35% [controls] 1fr;
    gap: 0.5rem 0.75rem;
    align-items: center;
    padding-right: 0.5rem;
    overflow: hidden;
    max-height: 0;
    opacity: 0;
    transition:
        max-height 0.3s ease,
        opacity 0.3s ease,
        margin 0.3s ease;
    margin-top: 0;
}

dialog.modal .modal-advanced.expanded {
    max-height: 300px;
    opacity: 1;
    margin-top: 0.5rem;
}

dialog.modal .modal-advanced .label {
    grid-column: labels;
    color: var(--text-color-light, #888);
    font-size: 13px;
}

/* biome-ignore-start lint/style/noDescendingSpecificity: reason 3 from the note at the top — `.modal-advanced` is a SIBLING of `.modal-controls` (ConfigureScrcpy.buildBody appends both to the modal body), never nested inside it, so no element ever matches both */
dialog.modal .modal-advanced .input,
dialog.modal .modal-advanced select,
dialog.modal .modal-advanced input:not([type="checkbox"]) {
    grid-column: controls;
    box-sizing: border-box;
    width: 100%;
    background: var(--stream-bg-color, #1a1a2e);
    border: 1px solid rgba(255, 255, 255, 0.1);
    border-radius: 6px;
    color: var(--text-color, #ddd);
    padding: 4px 8px;
}
/* biome-ignore-end lint/style/noDescendingSpecificity: end of the sibling-container block above */

dialog.modal .modal-controls input[type="checkbox"],
dialog.modal .modal-advanced input[type="checkbox"] {
    grid-column: controls;
    justify-self: start;
    width: 16px;
    height: 16px;
    cursor: pointer;
    accent-color: var(--accent-color);
}

[data-theme="light"] dialog.modal .modal-advanced select,
[data-theme="light"] dialog.modal .modal-advanced input:not([type="checkbox"]) {
    background: #fff;
    border-color: rgba(0, 0, 0, 0.15);
    color: #333;
}

/* ── Settings buttons ── */
dialog.modal .modal-settings {
    display: flex;
    gap: 8px;
    justify-content: center;
    margin-top: 1rem;
    padding-top: 0.75rem;
    border-top: 1px solid var(--modal-divider);
}

dialog.modal .modal-settings button {
    border: 0.5px solid var(--text-color, #ddd);
    border-radius: 6px;
    background: transparent;
    color: var(--accent-color);
    padding: 6px 16px;
    cursor: pointer;
    white-space: nowrap;
}

dialog.modal .modal-settings button:hover {
    background: var(--device-list-hover-color, hsl(218, 17%, 18%));
}

/* Shared confirm-dialog button (ConfirmModal, AdminConfirmModal,
   ShellCloseConfirmModal, ResetConfirmModal; smoke row 4.5). Matches the welcome
   modal's outline buttons — white outline + white text, transparent, mono font —
   replacing the browser-default look (the class previously had no CSS). The
   bookmark / service-first-run reminders that also used it are a non-modal card
   since item 113 (bookmark-reminder.css). */
.modal-button {
    border: 0.5px solid var(--text-color, #ddd);
    border-radius: 6px;
    background: transparent;
    color: var(--text-color, #ddd);
    padding: 8px 16px;
    cursor: pointer;
    font: inherit;
}

.modal-button:hover {
    background: var(--device-list-hover-color, hsl(218, 17%, 18%));
}

/* ── Status text (footer) ── */
dialog.modal .status-text {
    font-size: 13px;
}

dialog.modal .status-text.status-probing {
    color: var(--danger-color);
}

dialog.modal .status-text.status-ready {
    color: var(--success-color);
}

dialog.modal .status-text.status-error {
    color: var(--danger-color);
}

/* ── Connect button (footer) ── */
dialog.modal .connect-btn {
    border: 0.5px solid var(--text-color, #ddd);
    border-radius: 6px;
    background: transparent;
    color: var(--accent-color);
    padding: 6px 20px;
    cursor: pointer;
    white-space: nowrap;
}

dialog.modal .connect-btn:hover:not(:disabled) {
    background: var(--device-list-hover-color, hsl(218, 17%, 18%));
}

dialog.modal .connect-btn:disabled {
    opacity: 0.4;
    cursor: not-allowed;
}

/* ── Shell modal overrides ── */
/* biome-ignore lint/style/noDescendingSpecificity: no background here; the earlier higher-specificity rule sets background only — disjoint properties, see the audit note at the top */
dialog.modal.shell-modal .modal-frame {
    width: clamp(500px, 90vw, 1600px);
    max-height: 90vh;
}

dialog.modal .shell-warning {
    padding: 4px 1rem;
    font-size: 11px;
    color: var(--danger-color);
    border-bottom: 1px solid var(--modal-divider);
    flex-shrink: 0;
}

dialog.modal.shell-modal .modal-body {
    padding: 0;
    background: #000;
    min-height: 600px;
    position: relative;
    overflow: hidden;
}

dialog.modal .terminal-container {
    position: absolute;
    inset: 0;
    overflow: hidden;
}

/* ── Connect modal overrides ── */
/* biome-ignore lint/style/noDescendingSpecificity: no background here; the earlier higher-specificity rule sets background only — disjoint properties, see the audit note at the top */
dialog.connect-modal .modal-frame {
    max-height: 90vh;
    width: fit-content;
    max-width: 95vw;
}

/* biome-ignore lint/style/noDescendingSpecificity: dialog.connect-modal and dialog.modal.shell-modal are mutually exclusive — no element matches both */
dialog.connect-modal .modal-body {
    padding: 0;
    display: flex;
    flex-direction: row;
    overflow: hidden;
    background: #000;
}

/* Stream layout (.device-view, .video, .control-buttons-list) lives in
 * ws-scrcpy.css and is shared with embed.html. The modal-body above is the
 * flex parent that gives .device-view its box.
 *
 * The modal still needs to cap the canvas layers: StreamClientScrcpy.getMaxSize()
 * bounds the canvas to window.innerWidth/Height, but the modal-frame is
 * constrained to max 95vw × 90vh. Without these caps, the canvas overflows the
 * frame and gets clipped by the modal-body's `overflow: hidden`. */
dialog.connect-modal .video-layer,
dialog.connect-modal .touch-layer {
    max-width: calc(95vw - 3.715rem);
    max-height: calc(90vh - 2.5rem);
}

/* ── List files modal overrides ── */
/* biome-ignore lint/style/noDescendingSpecificity: no background here; the earlier higher-specificity rule sets background only — disjoint properties, see the audit note at the top */
dialog.list-files-modal .modal-frame {
    width: clamp(500px, 70vw, 900px);
    max-height: 85vh;
}

/* biome-ignore lint/style/noDescendingSpecificity: dialog.list-files-modal and dialog.modal.shell-modal are mutually exclusive — no element matches both */
dialog.list-files-modal .modal-body {
    padding: 0;
    display: flex;
    flex-direction: column;
    overflow: hidden;
}

/* ── Settings modal ── */
/* Wider than the base modal: the Settings modal has accreted enough sections
   (service scope, updates, App-section) that long info/error text
   wrapped into many lines and pushed the content past max-height -> a vertical
   scrollbar. The extra horizontal room lets that text breathe (fewer wrapped
   lines -> shorter content). Scoped to .settings-modal so the small confirm
   modals (AdminConfirmModal etc.) stay narrow. */
/* biome-ignore lint/style/noDescendingSpecificity: no background here; the earlier higher-specificity rule sets background only — disjoint properties, see the audit note at the top */
dialog.settings-modal .modal-frame {
    width: clamp(460px, 66vw, 820px);
    /* 95vh (not the base 80vh / prior 85vh): the App + Updates + Service
       sections together are tall enough that 85vh forced an inner scrollbar on
       smaller viewports. 95vh leaves a ~2.5% margin top/bottom so the frame
       doesn't kiss the screen edges; long sections still scroll inside. */
    max-height: 95vh;
}

dialog.settings-modal .settings-section {
    padding: 0.75rem 0;
    border-bottom: 1px solid var(--modal-divider);
}

dialog.settings-modal .settings-section:last-child {
    border-bottom: none;
}

dialog.settings-modal .settings-section-heading {
    font-size: 14px;
    font-weight: 600;
    margin: 0 0 0.5rem;
}

/*
 * v0.1.23-stable Settings layout: two-column CSS grid mirroring
 * .modal-controls in ConfigureScrcpy. Labels live on the left, every
 * control sits on the right at the same x-coordinate across all rows
 * and all sections. Section action buttons (save / check now / install)
 * live in .settings-section-footer which spans both columns.
 */
dialog.settings-modal .settings-section-body {
    display: grid;
    /* Labels fixed at 20rem so the controls boundary doesn't shift when
       dynamic label text changes (status messages, version strings) or
       when button text changes (install/uninstall states). Controls
       take 1fr (was 16rem + [end] 1fr buffer pre-2026-05-28). The fixed
       16rem controls column was sized against Windows font rendering
       ("not installed — install?" ≈ 231px at 13px monospace, fit) but
       wrapped on Linux 1920x1080 unscaled where the same monospace
       string renders ~10% wider. Flexing controls to 1fr gives Linux
       the breathing room without affecting Windows (controls hug left
       via .settings-control justify-content: flex-start, so the extra
       width on Windows is just empty space to the right of each control). */
    grid-template-columns: [labels] 20rem [controls] 1fr;
    align-items: center;
    gap: 0.5rem 1rem;
}

dialog.settings-modal .settings-row {
    display: contents;
}

/*
 * Error rows stretch out. The error message is rendered on the row's label,
 * which normally lives in the fixed 20rem [labels] column — too narrow for the
 * longer service/update errors. When a row's label carries .settings-status-error,
 * lift the whole row out of `display: contents` into a full-width block spanning
 * both columns so the error wraps across the full width; the retry/save button
 * drops beneath it. (Status notes already span via grid-column:1/-1 on a <p>.)
 */
dialog.settings-modal .settings-row:has(.settings-status-error) {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 0.25rem;
    grid-column: 1 / -1;
}

dialog.settings-modal .settings-label {
    grid-column: labels;
    color: var(--text-color-light, #888);
    font-size: 13px;
    /* Equal row heights across the whole modal — sets the visual rhythm.
       Long labels (status messages, descriptive notes) wrap within the
       40% column rather than pushing the modal wide. */
    min-height: 32px;
    display: flex;
    align-items: center;
    line-height: 1.4;
    word-break: break-word;
    padding-right: 0.75rem;
}

/*
 * Right-column wrapper. Multi-control rows (radios, checkbox+text)
 * use this to lay them out in a flex row inside the grid cell.
 * min-height matches .settings-label so rows stay flush at 32px.
 * justify-content stays at flex-start (default) so controls hug the
 * left edge of the right column — buttons align with inputs above
 * them across all rows.
 */
dialog.settings-modal .settings-control {
    grid-column: controls;
    display: flex;
    align-items: center;
    gap: 0.75rem;
    min-width: 0;
    min-height: 32px;
}

dialog.settings-modal .settings-control .settings-radio-label {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    color: var(--text-color, #ddd);
    font-size: 13px;
    cursor: pointer;
    margin: 0;
}

/* Bright accent so the radio's selected dot stays visible even when the label
 * is muted (locked scope). Matches the app's checkboxes/range controls. The
 * radio is never `disabled` (Chromium desaturates accent-color on :disabled),
 * so this dot renders at full saturation in both the active and locked state. */
dialog.settings-modal .settings-control .settings-radio-label input[type="radio"] {
    accent-color: var(--accent-color);
}

/*
 * Locked radio (service scope while a service is installed): mute the whole
 * label so the read-only state reads at a glance, and block interaction with
 * pointer-events. We do NOT use the `disabled` attribute — Chromium
 * desaturates accent-color on :disabled controls, which hid the selected dot
 * (item 42). The radio stays enabled (accent-color renders) and is locked via
 * this class (set by lockScopeRadioControl) plus tabindex=-1 on the input.
 */
dialog.settings-modal .settings-control .settings-radio-label.settings-radio-locked {
    opacity: 0.65;
    cursor: not-allowed;
    pointer-events: none;
}

/*
 * Apply-update button styling — green outline + green text — mirroring
 * the home-page UpdateButton chip's "ready" state. Higher specificity
 * than .settings-btn-primary so it overrides when both classes are
 * present (used in the Updates section's dual-purpose button).
 */
dialog.modal .settings-btn.settings-btn-ready {
    color: var(--success-color);
    border-color: var(--success-color);
}

dialog.modal .settings-btn.settings-btn-ready:hover {
    background: rgba(var(--success-rgb), 0.08);
}

dialog.settings-modal .settings-input {
    box-sizing: border-box;
    background: var(--stream-bg-color, #1a1a2e);
    border: 1px solid rgba(255, 255, 255, 0.1);
    border-radius: 6px;
    color: var(--text-color, #ddd);
    padding: 4px 8px;
    width: 100%;
    max-width: 240px;
    font: inherit;
}

dialog.settings-modal .settings-input:focus {
    outline: none;
    border-color: var(--accent-color);
}

[data-theme="light"] dialog.settings-modal .settings-input {
    background: #fff;
    border-color: rgba(0, 0, 0, 0.15);
}

dialog.modal .settings-btn {
    border: 0.5px solid var(--text-color, #ddd);
    border-radius: 6px;
    background: transparent;
    color: var(--text-color, #ddd);
    padding: 6px 14px;
    cursor: pointer;
    font: inherit;
}

dialog.modal .settings-btn:disabled {
    opacity: 0.5;
    cursor: not-allowed;
}

dialog.modal .settings-btn-primary {
    color: var(--accent-color);
    border-color: var(--accent-color);
}

dialog.modal .settings-btn-danger {
    color: var(--danger-color);
    border-color: var(--danger-color);
}

dialog.modal .settings-btn-danger-outline {
    color: var(--danger-color);
    border-color: var(--danger-color);
    background: transparent;
}

dialog.settings-modal .settings-status {
    margin: 0.25rem 0 0;
    /* Notes + status read as sub-text of the setting they annotate: indented +
       bold-italic to set them apart. Errors add red via .settings-status-error
       (layered on top, so they inherit this indent/weight/style). */
    padding-left: 1.25rem;
    color: var(--text-color-light, #888);
    font-size: 13px;
    min-height: 1em;
    font-style: italic;
    font-weight: 600;
}

dialog.settings-modal .settings-status-error {
    color: var(--danger-color);
}

/*
 * Status text turns green when an update is ready to apply, mirroring
 * the .settings-btn-ready button color (var(--success-color)) so the description
 * and action visually pair — "update: vX.Y.Z" + green "apply update"
 * button reinforce one another.
 */
dialog.settings-modal .settings-status-ready {
    color: var(--success-color);
}

dialog.settings-modal .settings-stub-note {
    margin: 0;
    padding-left: 1.25rem;
    color: var(--text-color-light, #888);
    font-size: 13px;
    font-style: italic;
    font-weight: 600;
}

/*
 * v0.1.23-stable: per-section legacy layout overrides removed.
 * All sections now use the unified .settings-section-body grid above.
 * Section-specific input widths handled inline (max-width on the
 * .settings-input itself).
 */

/* --- Service operation modal (Phase 3) --- */

dialog.service-operation-modal .modal-header-controls {
    display: none;
}

/* biome-ignore lint/style/noDescendingSpecificity: dialog.service-operation-modal and dialog.modal.shell-modal are mutually exclusive — no element matches both */
dialog.service-operation-modal .modal-body {
    text-align: center;
    padding: 1rem 0;
}

.service-operation-spinner {
    width: 32px;
    height: 32px;
    border: 3px solid var(--border-muted);
    border-top-color: var(--accent-color);
    border-radius: 50%;
    margin: 0 auto 1rem;
    animation: service-op-spin 0.8s linear infinite;
}

@keyframes service-op-spin {
    to {
        transform: rotate(360deg);
    }
}

/* Dark theme (default) */
:root,
[data-theme="dark"] {
    --device-border-color: #444444;
    --device-list-default-color: #252525;
    --device-list-hover-color: hsl(218, 17%, 18%);
    --section-border-color: #5a5a5a;
}

/* Light theme */
[data-theme="light"] {
    --device-border-color: #c0c6cc;
    --device-list-default-color: #ffffff;
    --device-list-hover-color: hsl(218, 67%, 95%);
    --section-border-color: #8f959d;
}

body.list {
    position: static;
    height: auto;
    width: auto;
    overflow: auto;
}

#devices {
    padding: 20px;
    width: 100%;
    box-sizing: border-box;
}

body.list #devices {
    border: 1.5px solid var(--section-border-color, #5a5a5a);
    border-radius: 8px;
    margin-top: 35px;
    margin-bottom: 35px;
}

body.stream #devices {
    background-color: var(--device-list-default-color);
    opacity: 0.8;
    position: absolute;
    top: 0;
    left: 0;
    z-index: 3;
    height: 100%;
    width: 100%;
}

body.list #device_list_menu {
    display: none;
}

/* biome-ignore lint/style/noDescendingSpecificity: intentional source order; reordering would change the cascade */
#device_list_menu {
    display: block;
    position: absolute;
    bottom: 0;
    left: 0;
    z-index: 4;
}

/* Device list container */
#devices .device-list {
    font-family: var(--font-mono);
    font-size: var(--font-size);
}

#devices .device-list button {
    font-family: var(--font-mono);
    font-size: var(--font-size);
    color: var(--button-text-color);
}

/* Device card */
#devices .device-list div.device {
    border: 1px solid var(--device-border-color);
    border-radius: 8px;
    /* Symmetric horizontal padding so the dark-grey content area sits
       equidistant from the left and right card borders. (The active
       device's 3px green / not-active 3px red left border is outside
       this padding and replaces the 1px default left border — that
       asymmetry is intentional, not part of the content padding.) */
    padding: 8px 14px;
    background: var(--device-list-default-color);
    transition:
        border-color 0.15s,
        box-shadow 0.15s;
}

#devices .device-list div.device:hover {
    border-color: var(--device-list-hover-color);
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
}

#devices .device-list div.device.active {
    border-left: 3px solid var(--success-color);
}

#devices .device-list div.device.not-active {
    border-left: 3px solid var(--danger-color);
    opacity: 0.7;
}

/* Device info table */
#devices table.device-info {
    margin-bottom: 0;
    border-collapse: collapse;
    font-size: 13px;
    width: 100%;
}

#devices .device-info td {
    padding: 2px 0;
    vertical-align: top;
}

#devices .device-info td.device-label {
    color: var(--text-color-light, #888);
    padding-right: 24px;
    white-space: nowrap;
    width: 1%;
}

#devices .device-info tr:first-child td:last-child {
    font-size: 15px;
    font-weight: 600;
}

#devices .device-serial {
    font-family: var(--font-mono);
    font-size: 12px;
}

/* Device action buttons (disconnect + sleep/wake) — sibling row to the
   device-info table. 2-column grid with outer borders on all four sides;
   border-top is the row divider shared with the services grid above.
   Inner vertical divider is drawn as a border-right on the first
   .action-cell (an element edge → integer pixel → crisp rendering, no
   sub-pixel AA artifacts). */
#devices .device-list div.device .device-actions {
    display: grid;
    grid-template-columns: 1fr 1fr;
    grid-template-rows: auto;
    gap: 0;
    border-top: 1px solid var(--device-border-color);
    border-left: 1px solid var(--device-border-color);
    border-right: 1px solid var(--device-border-color);
    border-bottom: 1px solid var(--device-border-color);
    padding: 0;
}

/* Each action button lives inside an .action-cell that stretches to
   fill its grid cell. The cell's right border (first cell only) is
   the inner vertical divider. */
#devices .device-list div.device .device-actions > .action-cell {
    justify-self: stretch;
    align-self: stretch;
    display: flex;
    align-items: center;
    justify-content: flex-start;
    padding: 0 8px;
    height: 36px;
    box-sizing: border-box;
}

#devices .device-list div.device .device-actions > .action-cell:nth-child(1) {
    border-right: 1px solid var(--device-border-color);
}

#devices .device-list div.device .device-actions:empty {
    display: none;
}

#devices .device-list .disconnect-btn {
    border: 0.5px solid var(--danger-color);
    border-radius: 6px;
    background: transparent;
    color: var(--danger-color);
    padding: 2px 8px;
    font-size: var(--font-size);
    cursor: pointer;
    white-space: nowrap;
    min-width: 95px;
    text-align: center;
}

#devices .device-list .disconnect-btn:hover:not(:disabled) {
    background: var(--danger-hover-bg);
}

#devices .device-list .disconnect-btn:disabled {
    opacity: 0.5;
    cursor: not-allowed;
}

/* Sleep/wake button */
#devices .device-list .sleep-wake-btn {
    border-radius: 6px;
    background: transparent;
    padding: 2px 8px;
    font-size: var(--font-size);
    cursor: pointer;
    white-space: nowrap;
    min-width: 70px;
    text-align: center;
}

#devices .device-list .sleep-wake-btn.state-off {
    border: 0.5px solid var(--success-color);
    color: var(--success-color);
}

#devices .device-list .sleep-wake-btn.state-on {
    border: 0.5px solid var(--danger-color);
    color: var(--danger-color);
}

#devices .device-list .sleep-wake-btn.state-unknown {
    border: 0.5px solid var(--text-color-light, #888);
    color: var(--text-color-light, #888);
}

#devices .device-list .sleep-wake-btn.state-off:hover:not(:disabled) {
    background: var(--success-hover-bg);
}

#devices .device-list .sleep-wake-btn.state-on:hover:not(:disabled) {
    background: var(--danger-hover-bg);
}

#devices .device-list .sleep-wake-btn:disabled {
    opacity: 0.5;
    cursor: not-allowed;
}

/* Modal-launch buttons — 2x2 grid filled column-first:
   left col = [shell][list files], right col = [connect][configure stream].
   Outer borders on left/right; border-top is the row divider shared with
   the device-info table above. Inner cross dividers are drawn as
   border-right / border-bottom on the cells (the .desc-block items
   themselves stretch to fill their cell). Borders sit at element edges
   (integer pixels) so they render crisply, unlike 1px grid-track
   dividers which can land on sub-pixel positions and anti-alias. */
#devices .device-list div.device .services {
    display: grid;
    grid-template-columns: 1fr 1fr;
    grid-template-rows: auto auto;
    grid-auto-flow: column;
    gap: 0;
    border-top: 1px solid var(--device-border-color);
    border-left: 1px solid var(--device-border-color);
    border-right: 1px solid var(--device-border-color);
    padding: 0;
}

/* Each .desc-block (modal-launch cell) stretches to fill its grid cell.
   Padding gives breathing room around the inner button (which carries
   the blue border — moved off .desc-block so .desc-block can act as
   the gray-bordered cell). */
#devices .device-list div.device .services > .desc-block {
    justify-self: stretch;
    align-self: stretch;
    display: flex;
    align-items: center;
    justify-content: flex-start;
    padding: 0 8px;
    margin: 0;
    border: none;
    border-radius: 0;
    overflow: visible;
    height: 36px;
    box-sizing: border-box;
}

/* Cross dividers via cell borders. With grid-auto-flow: column over 2
   rows: nth-child(1)=top-left, (2)=bottom-left, (3)=top-right,
   (4)=bottom-right. Top-left gets right + bottom; bottom-left gets
   right; top-right gets bottom; bottom-right gets none. For inactive
   devices (3 buttons, no `connect`), nth-child(3) is `config stream`
   sitting at top-right with border-bottom — that's fine; the line
   ends at the cell boundary regardless of whether anything below
   occupies the empty bottom-right cell. */
#devices .device-list div.device .services > .desc-block:nth-child(1) {
    border-right: 1px solid var(--device-border-color);
    border-bottom: 1px solid var(--device-border-color);
}
#devices .device-list div.device .services > .desc-block:nth-child(2) {
    border-right: 1px solid var(--device-border-color);
}
#devices .device-list div.device .services > .desc-block:nth-child(3) {
    border-bottom: 1px solid var(--device-border-color);
}

/* Insecure-origin notice — spans both columns beneath the modal-launch grid.
   WebCodecs is secure-context-only, so at http://<lan-ip> no player registers
   and the `connect` cell is absent entirely. Without this the card just looked
   like it had one fewer button, with nothing to explain the missing stream. */
#devices .device-list div.device .services > .insecure-origin-notice {
    grid-column: 1 / -1;
    padding: 8px;
    border-top: 1px solid var(--device-border-color);
    color: var(--warning-color);
    font-size: 12px;
    line-height: 1.4;
    text-align: left;
}

#devices .device-list div.device .services:last-child {
    padding-bottom: 0;
}

#devices .device-list div.device .services:first-of-type {
    margin-top: 8px;
}

/* .desc-block default styles for places OUTSIDE the services grid
   (kept for any other consumer). Inside the services grid, .desc-block
   acts as a cell; the blue border is moved to the inner button/link. */
#devices .device-list div.desc-block {
    margin: 0;
    display: inline-flex;
    align-items: center;
    border-radius: 6px;
    overflow: hidden;
    white-space: nowrap;
}

#devices .device-list div.desc-block.hidden {
    display: none;
}

/* Inactive device styling */
#devices .device-list .device.not-active div.desc-block button > svg > path {
    fill: var(--text-color-light);
}

#devices .device-list .device.not-active {
    color: var(--text-color-light);
}

#devices .device-list .device.not-active a {
    color: var(--link-color-light);
}

#devices .device-list .device.not-active a:visited {
    color: var(--link-color-visited-light);
}

/* Action buttons and links styled as buttons. Blue border moved here
   from .desc-block so the parent .desc-block can act as a grid cell
   (with gray cell borders) without an outer blue border around it. */
/* biome-ignore-start lint/style/noDescendingSpecificity: intentional source order; reordering would change the cascade */
#devices .device-list div.desc-block .action-button,
#devices .device-list div.desc-block a {
    border: 0.5px solid var(--accent-color);
    border-radius: 6px;
    background-color: transparent;
    color: var(--accent-color);
    text-decoration: none;
    padding: 0.15rem 0.5rem;
    font-family: var(--font-mono);
    font-size: var(--font-size);
    cursor: pointer;
    display: inline-block;
}
/* biome-ignore-end lint/style/noDescendingSpecificity: end desc-block action selectors */

#devices .device-list div.desc-block .action-button:hover,
#devices .device-list div.desc-block a:hover {
    background-color: var(--device-list-hover-color);
}

/* Tracker block — inherits grid layout so device cards flow correctly */
#devices .device-list > div {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(340px, 1fr));
    gap: 12px;
}

/* Tracker name header */
#devices .tracker-name {
    padding: 0 0 8px 0;
    font-size: 18px;
    font-weight: 600;
    color: var(--text-color, #eee);
    grid-column: 1 / -1;
}

/* Empty-state card inside the tracker block spans the full row instead of
 * sitting in a single grid cell — matches the look of the scan-network
 * info box below Available Network Devices. */
#devices .empty-state-card {
    grid-column: 1 / -1;
}

/* Device name row — inline label editor */
#devices .device-name-cell {
    position: relative;
    /* biome-ignore lint/complexity/noImportantStyles: targeted override of the row's base padding for the inline name-editor affordance */
    padding-right: 28px !important;
}

#devices .device-name-text {
    font-size: 15px;
    font-weight: 600;
}

#devices .device-name-text.unnamed {
    font-style: italic;
    opacity: 0.5;
    font-weight: 400;
}

#devices .device-name-edit-btn {
    position: absolute;
    right: 0;
    top: 50%;
    transform: translateY(-50%);
    background: transparent;
    border: none;
    cursor: pointer;
    color: var(--text-color-light, #888);
    padding: 2px;
    line-height: 1;
    opacity: 0.6;
}

#devices .device-name-edit-btn:hover {
    opacity: 1;
    color: var(--text-color, #ddd);
}

#devices .device-name-input {
    width: calc(100% - 24px);
    background: var(--stream-bg-color, #333);
    border: 1px solid var(--text-color-light, #888);
    border-radius: 4px;
    color: var(--text-color, #ddd);
    font-size: 14px;
    padding: 2px 6px;
}

#devices .device-name-input:focus {
    outline: none;
    border-color: var(--accent-color);
}

a.link-stream .kind-icon {
    margin-left: 6px;
    width: 14px;
    height: 14px;
    vertical-align: middle;
}

