/* Capsid Control console.
 *
 * One stylesheet, no build step, no external fonts. Every selector the
 * templates and static JS already rely on is preserved (.panel, .muted, .ok,
 * .error, .state-*, .field, .grid, .link-block, .collapse-*, .screen-*), so
 * markup and behaviour stay where they are and only the presentation moves.
 */

/* A bare `display:` further down beats the UA sheet's [hidden] rule, so every
 * `<progress hidden>` and `<div hidden>` in the templates rendered anyway --
 * an idle grey progress bar sat on the hook and device-config pages. Restore
 * the attribute's meaning once, globally; nothing in the console reveals an
 * element by writing style.display, so !important costs nothing. */
[hidden] { display: none !important; }

/* ---------------------------------------------------------------- tokens */

:root {
    color-scheme: light dark;

    --bg: #f4f6f9;
    --surface: #ffffff;
    --surface-2: #eff2f6;
    --surface-3: #e6eaf0;
    --border: #e0e5ec;
    --border-strong: #c9d1dc;

    --text: #101419;
    --text-2: #576070;
    --text-3: #667080;

    --accent: #2f56d3;
    --accent-hover: #2748b8;
    --accent-text: #ffffff;
    --accent-soft: #e8edfd;

    --ok: #0e7a3d;
    --ok-soft: #e3f5e9;
    --danger: #c0261c;
    --danger-hover: #a51f16;
    --danger-soft: #fdeceb;
    --warn: #96580a;
    --warn-soft: #fdf1de;

    --ring: #6c8ce8;
    --shadow-1: 0 1px 2px rgba(16, 20, 25, .05), 0 1px 3px rgba(16, 20, 25, .04);
    --shadow-2: 0 2px 4px rgba(16, 20, 25, .06), 0 8px 20px rgba(16, 20, 25, .07);

    /* 机身与外部面板/卡片之间的分隔线。浅色主题面板是白的,用深色才看得见。 */
    --phone-edge: #39414e;

    --radius: 10px;
    --radius-sm: 6px;
    --radius-pill: 999px;

    --code-bg: #11161d;
    --code-text: #dbe3ee;

    --font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
            "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif;
    --font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas,
                 "Liberation Mono", monospace;
}

/* Dark palette. Applied from the system preference, and forced either way by
 * the theme toggle in the header (which stamps data-theme on <html>). */
@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) {
        --bg: #0d1117;
        --surface: #151b23;
        --surface-2: #1c232d;
        --surface-3: #242c38;
        --border: #262e3a;
        --border-strong: #3a4553;

        --text: #e7ebf0;
        --text-2: #a4aebc;
        --text-3: #7d8798;

        --accent: #6f8ff5;
        --accent-hover: #86a2f7;
        --accent-text: #0d1117;
        --accent-soft: #1c2740;

        --ok: #56d38a;
        --ok-soft: #14301f;
        --danger: #f4796d;
        --danger-hover: #f7938a;
        --danger-soft: #33191a;
        --warn: #e3b25c;
        --warn-soft: #322612;

        --ring: #6f8ff5;
        --shadow-1: 0 1px 2px rgba(0, 0, 0, .4);
        --shadow-2: 0 2px 4px rgba(0, 0, 0, .4), 0 8px 24px rgba(0, 0, 0, .45);

        --code-bg: #0a0e13;
        --code-text: #cdd6e2;
    }
}

:root[data-theme="dark"] {
    --bg: #0d1117;
    --surface: #151b23;
    --surface-2: #1c232d;
    --surface-3: #242c38;
    --border: #262e3a;
    --border-strong: #3a4553;

    --text: #e7ebf0;
    --text-2: #a4aebc;
    --text-3: #7d8798;

    --accent: #6f8ff5;
    --accent-hover: #86a2f7;
    --accent-text: #0d1117;
    --accent-soft: #1c2740;

    --ok: #56d38a;
    --ok-soft: #14301f;
    --danger: #f4796d;
    --danger-hover: #f7938a;
    --danger-soft: #33191a;
    --warn: #e3b25c;
    --warn-soft: #322612;

    --ring: #6f8ff5;
    --shadow-1: 0 1px 2px rgba(0, 0, 0, .4);
    --shadow-2: 0 2px 4px rgba(0, 0, 0, .4), 0 8px 24px rgba(0, 0, 0, .45);

    /* 深色主题面板本身就很暗,分隔线反过来用亮的,否则和卡片糊在一起。 */
    --phone-edge: #5a6778;

    --code-bg: #0a0e13;
    --code-text: #cdd6e2;
}

/* ----------------------------------------------------------------- base */

* { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body {
    margin: 0;
    font-family: var(--font);
    font-size: 14px;
    line-height: 1.55;
    background: var(--bg);
    color: var(--text);
    -webkit-font-smoothing: antialiased;
}

a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }

/* One focus treatment everywhere, keyboard-only. */
:focus-visible {
    outline: 2px solid var(--ring);
    outline-offset: 2px;
    border-radius: var(--radius-sm);
}

@media (prefers-reduced-motion: reduce) {
    * { transition: none !important; animation: none !important; }
}

/* --------------------------------------------------------------- header */

header {
    position: sticky;
    top: 0;
    z-index: 20;
    background: color-mix(in srgb, var(--surface) 88%, transparent);
    backdrop-filter: saturate(1.6) blur(8px);
    border-bottom: 1px solid var(--border);
    padding: 0 1.25rem;
    height: 52px;
    display: flex;
    gap: .35rem;
    align-items: center;
}

.brand {
    font-weight: 650;
    font-size: .95rem;
    letter-spacing: -.01em;
    color: var(--text);
    margin-right: .75rem;
    white-space: nowrap;
}
.brand:hover { text-decoration: none; }
.brand::before {
    content: "";
    display: inline-block;
    width: 8px;
    height: 8px;
    margin-right: .5rem;
    vertical-align: 1px;
    border-radius: 2px;
    background: var(--accent);
}

header nav { display: flex; gap: .15rem; align-items: center; }

header nav a {
    white-space: nowrap;
    flex-shrink: 0;
    color: var(--text-2);
    font-weight: 500;
    padding: .32rem .6rem;
    border-radius: var(--radius-sm);
    transition: background-color .12s ease, color .12s ease;
}
header nav a:hover { background: var(--surface-2); color: var(--text); text-decoration: none; }
header nav a[aria-current="page"] { background: var(--accent-soft); color: var(--accent); }

header .spacer { flex: 1; }
header form { margin: 0; }

.lang-switch {
    display: flex;
    gap: .1rem;
    align-items: center;
    font-size: .8rem;
}
.lang-switch a {
    color: var(--text-3);
    padding: .2rem .45rem;
    border-radius: var(--radius-sm);
}
.lang-switch a:hover { background: var(--surface-2); color: var(--text); text-decoration: none; }

/* Theme toggle: an icon-only button that swaps glyphs by theme. */
.theme-toggle {
    background: transparent;
    border: 1px solid var(--border);
    color: var(--text-2);
    width: 30px;
    height: 30px;
    padding: 0;
    display: grid;
    place-items: center;
    font-size: .95rem;
    line-height: 1;
}
.theme-toggle:hover { background: var(--surface-2); color: var(--text); }
.theme-toggle .icon-dark { display: none; }
:root[data-theme="dark"] .theme-toggle .icon-dark { display: block; }
:root[data-theme="dark"] .theme-toggle .icon-light { display: none; }
@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) .theme-toggle .icon-dark { display: block; }
    :root:not([data-theme="light"]) .theme-toggle .icon-light { display: none; }
}

/* ----------------------------------------------------------------- page */

main {
    max-width: 1400px;
    margin: 0 auto;
    padding: 1.5rem 1.25rem 3rem;
}

h1 {
    font-size: 1.45rem;
    font-weight: 640;
    letter-spacing: -.02em;
    margin: 0 0 1rem;
}
h2 {
    font-size: 1rem;
    font-weight: 620;
    letter-spacing: -.01em;
    margin: 1.75rem 0 .6rem;
}
h1 + .panel, h2 + .panel { margin-top: 0; }

.panel {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    box-shadow: var(--shadow-1);
    padding: 1rem 1.15rem;
    margin-bottom: 1rem;
}
/* A table filling a panel supplies its own padding via cells. */
.panel > table:only-child,
.panel > .table-scroll:only-child { margin: -1rem -1.15rem; width: auto; }
.panel > table:only-child { width: calc(100% + 2.3rem); }

/* ---------------------------------------------------------------- table */

table {
    width: 100%;
    border-collapse: collapse;
    font-variant-numeric: tabular-nums;
}

th, td {
    padding: .6rem .85rem;
    text-align: left;
    font-size: .875rem;
    border: 0;
    border-bottom: 1px solid var(--border);
    vertical-align: middle;
}

/* Every table on the site sits inside .table-scroll, which is a scroll
 * container (overflow-x: auto) -- so this sticks to that box, never to the
 * viewport. Offsetting by the 52px site header therefore pushed the header
 * row 52px DOWN, on top of the first body row, instead of clearing anything. */
thead th {
    position: sticky;
    top: 0;
    z-index: 1;
    background: var(--surface);
    color: var(--text-3);
    font-size: .72rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: .06em;
    white-space: nowrap;
    border-bottom: 1px solid var(--border);
}
thead th:first-child { border-top-left-radius: var(--radius); }
thead th:last-child { border-top-right-radius: var(--radius); }

tbody tr { transition: background-color .1s ease; }
tbody tr:hover { background: var(--surface-2); }
/* The key/value panels are built as <tr><th>label</th><td>value</td></tr>, so
 * clearing only `td` left the last row with a rule under the label column and
 * nothing under the value column -- a short line hanging off the left edge. */
tbody tr:last-child > * { border-bottom: 0; }

.table-scroll { overflow-x: auto; border-radius: var(--radius); }
.table-scroll table { min-width: max-content; }

/* --------------------------------------------------------------- status */

/* The dashboard poller rewrites this element's className to exactly
 * "state-<name>", so the pill has to hang off that class alone: any extra
 * class would be wiped on the first refresh.
 *
 * The pill is an inline-block, so it must never be put on a <td> directly:
 * that drops the cell out of the row box and the browser re-wraps the rest of
 * the row in an anonymous table, visibly tearing the row apart. Always wrap
 * the text in a <span> inside the cell. */
[class^="state-"] {
    display: inline-block;
    padding: .1rem .5rem;
    border-radius: var(--radius-pill);
    font-size: .75rem;
    font-weight: 600;
    letter-spacing: .01em;
    white-space: nowrap;
    background: var(--surface-3);
    color: var(--text-2);
}
.state-running, .state-connected {
    background: var(--ok-soft);
    color: var(--ok);
}
.state-creating, .state-stopping, .state-deleting, .state-wiping, .state-unknown {
    background: var(--warn-soft);
    color: var(--warn);
}
/* 排队 / 执行中的实例操作(列表页):强调色 + 一个转圈。 */
.state-pending { background: var(--accent-soft); color: var(--accent); }
.state-pending::before {
    content: "";
    display: inline-block;
    width: .7em;
    height: .7em;
    margin-right: .35em;
    border: 2px solid currentColor;
    border-right-color: transparent;
    border-radius: 50%;
    vertical-align: -.1em;
    animation: capsid-spin .8s linear infinite;
}
/* 还在旧镜像上的实例(镜像列)。 */
.image-old { color: var(--warn); font-weight: 600; cursor: help; }
/* capsid 镜像版本(打包时间号),跟在镜像名后面。 */
.image-version {
    display: inline-block;
    margin-left: .45em;
    padding: 0 .4em;
    border-radius: var(--radius-sm);
    background: var(--surface-2);
    color: var(--text-2);
    font-family: var(--font-mono);
    font-size: .78em;
    line-height: 1.6;
    cursor: help;
}
.state-exited, .state-absent, .state-create_failed,
.state-offline, .state-agent_offline {
    background: var(--danger-soft);
    color: var(--danger);
}

/* -------------------------------------------------------------- buttons */

button, .btn {
    font: inherit;
    font-size: .85rem;
    font-weight: 500;
    line-height: 1;
    min-height: 32px;
    padding: .45rem .8rem;
    border: 1px solid transparent;
    border-radius: var(--radius-sm);
    background: var(--accent);
    color: var(--accent-text);
    cursor: pointer;
    transition: background-color .12s ease, border-color .12s ease,
                color .12s ease, transform .04s ease;
}
button:hover { background: var(--accent-hover); }
button:active { transform: translateY(1px); }
button:disabled { cursor: not-allowed; transform: none; }

button.ghost {
    background: var(--surface);
    color: var(--text);
    border-color: var(--border-strong);
}
button.ghost:hover { background: var(--surface-2); }

button.danger { background: var(--danger); color: #fff; }
button.danger:hover { background: var(--danger-hover); }

/* ---------------------------------------------------------------- forms */

input, select, textarea {
    font: inherit;
    font-size: .875rem;
    padding: .42rem .6rem;
    min-height: 32px;
    color: var(--text);
    background: var(--surface);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-sm);
    transition: border-color .12s ease, box-shadow .12s ease;
}
input:hover, select:hover, textarea:hover { border-color: var(--text-3); }
input:focus, select:focus, textarea:focus {
    outline: none;
    border-color: var(--accent);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 22%, transparent);
}
input::placeholder, textarea::placeholder { color: var(--text-3); }

textarea {
    width: 100%;
    font-family: var(--font-mono);
    font-size: .8rem;
    line-height: 1.5;
    padding: .6rem;
}

label { color: var(--text); }

form.inline { display: inline; }
form.row { display: flex; gap: .5rem; align-items: center; flex-wrap: wrap; margin: 0; }

/* Several action forms sit side by side in a table cell. */
td form.inline + form.inline { margin-left: .3rem; }

.field { display: flex; flex-direction: column; gap: .3rem; margin: .55rem 0; }
.field > span:first-child {
    color: var(--text-2);
    font-size: .78rem;
    font-weight: 500;
    letter-spacing: .01em;
}
.field input, .field select, .field textarea { width: 100%; }
.field textarea { min-height: 3.2rem; resize: vertical; }

.grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(170px, 1fr));
    gap: 0 .9rem;
}

.save-row {
    display: flex;
    align-items: center;
    gap: .65rem;
    margin-top: .9rem;
    padding-top: .9rem;
    border-top: 1px solid var(--border);
}

/* ------------------------------------------------------------- messages */

.muted { color: var(--text-2); font-size: .82rem; }
span.muted { font-size: inherit; }

.ok, span.ok { color: var(--ok); font-weight: 600; }
span.error { color: var(--danger); font-weight: 600; background: none; border: 0; padding: 0; }

/* Page-level alert. */
.error {
    display: flex;
    gap: .5rem;
    background: var(--danger-soft);
    border: 1px solid color-mix(in srgb, var(--danger) 30%, transparent);
    color: var(--danger);
    padding: .65rem .85rem;
    border-radius: var(--radius-sm);
    margin-bottom: 1rem;
    font-size: .875rem;
}
.error::before { content: "!"; font-weight: 700; }
span.error::before { content: none; }

pre {
    background: var(--code-bg);
    color: var(--code-text);
    padding: .9rem 1rem;
    overflow: auto;
    font-family: var(--font-mono);
    font-size: .78rem;
    line-height: 1.55;
    border-radius: var(--radius-sm);
    margin: 0;
}

progress {
    display: block;
    width: 100%;
    max-width: 480px;
    height: .45rem;
    margin: .45rem 0;
    border: 0;
    border-radius: var(--radius-pill);
    overflow: hidden;
    background: var(--surface-3);
    accent-color: var(--accent);
}
progress::-webkit-progress-bar { background: var(--surface-3); border-radius: var(--radius-pill); }
progress::-webkit-progress-value { background: var(--accent); border-radius: var(--radius-pill); }
progress::-moz-progress-bar { background: var(--accent); border-radius: var(--radius-pill); }

details > summary {
    cursor: pointer;
    user-select: none;
    color: var(--text-2);
    font-size: .82rem;
    margin: .4rem 0;
    padding: .2rem 0;
}
details > summary:hover { color: var(--text); }

.nowrap { white-space: nowrap; }

/* ------------------------------------------------------------ workspace */

.workspace {
    display: grid;
    grid-template-columns: minmax(320px, 1.5fr) minmax(300px, 1fr);
    gap: 1rem;
    align-items: start;
}
.workspace-screen, .workspace-controls { min-width: 0; }
.workspace .panel h2 { margin-top: 0; font-size: .95rem; }

.collapse-toggle {
    cursor: pointer;
    user-select: none;
    display: flex;
    align-items: center;
    gap: .4rem;
}
.collapse-toggle .caret { color: var(--text-3); font-size: .7rem; transition: transform .15s ease; }

.screen-head { display: flex; align-items: center; gap: .6rem; margin-bottom: .6rem; }
.screen-head .spacer { flex: 1; }
.screen-stage { width: 100%; }
.screen-stage video { max-width: 100%; }

.screen-panel { position: relative; padding: .85rem; text-align: center; }
.screen-panel video {
    display: block;
    margin: 0 auto;
    background: #000;
    border-radius: var(--radius-sm);
    box-shadow: var(--shadow-2);
    touch-action: none;
    cursor: crosshair;
    object-fit: cover;
}
.screen-panel video:focus-visible { outline: 2px solid var(--ring); outline-offset: 3px; }
/* A replaced or dropped session leaves its last decoded frame on screen, which
 * looks exactly like a live device while every tap is dropped. Mark it so the
 * picture stops claiming to be interactive. */
.screen-panel video.screen-stale {
    opacity: .45;
    filter: grayscale(1);
    cursor: not-allowed;
}
.screen-reclaim { margin-top: .5rem; }
.screen-panel #screen-status { margin-top: .6rem; text-align: left; min-height: 1.2em; }

/* "正在等"的转圈:实例启动、投屏重连这些要等几秒到一分钟的状态。挂在文案前面,
   文案本身仍然是判据 —— `prefers-reduced-motion` 那条把 animation 关掉之后它只是
   一个静止的小圈,"在等什么"照样读得出来,不靠动效传达信息。 */
@keyframes capsid-spin {
    to { transform: rotate(360deg); }
}
.spinner,
#screen-status[data-busy="1"]::before,
#instance-state-badge[data-busy="1"]::after {
    content: "";
    display: inline-block;
    width: .85em;
    height: .85em;
    border: 2px solid currentColor;
    border-right-color: transparent;
    border-radius: 50%;
    vertical-align: -.12em;
    animation: capsid-spin .8s linear infinite;
}
.spinner,
#screen-status[data-busy="1"]::before { margin-right: .45em; }
#instance-state-badge[data-busy="1"]::after {
    width: .6em;
    height: .6em;
    margin-left: .4em;
}
/* 容器在跑、但容器里的 Android 系统服务已经死了(docker 看不出来这件事):
   徽章边上一个"!"的告警色,别让人只看 (running) 就以为设备是好的。 */
#instance-state-badge[data-warn="1"] {
    color: var(--danger);
}
#instance-state-badge[data-warn="1"]::after {
    content: "!";
    display: inline-block;
    margin-left: .35em;
    padding: 0 .3em;
    border: 1px solid currentColor;
    border-radius: 4px;
    font-weight: 700;
    vertical-align: .05em;
}
.screen-panel.drop-active { outline: 2px dashed var(--accent); outline-offset: -4px; }

/* The markup says `class="row screen-toolbar"`, but the only `.row` rule in
 * this sheet is `form.row`, so a <div> never matched it: these controls were
 * laid out as inline boxes sharing a text baseline. Text-only buttons happened
 * to line up that way; an icon-only button has no text baseline of its own, so
 * the key groups sat off the line. The input rule further down has always
 * assumed the flex row that was never actually applied. */
.screen-toolbar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    /* .screen-panel centers its content with text-align, which does nothing
     * for flex items — keep the toolbar centered under the video. */
    justify-content: center;
    gap: .5rem;
    margin-top: .6rem;
}
.screen-toolbar input { flex: 1; min-width: 180px; line-height: 1; }
/* 实例页右侧栏把输入模块拆成独立面板后,输入框占满面板宽度。 */
.panel #text-input { width: 100%; }

/* 分类分组:比一排裸露控件更像"模块",标签是拖拽手柄(顺序由 screen-controls.js
 * 持久化)。视觉上给轻底色 + 圆角,和内容控件本身(白底分段键)拉开层次。 */
.toolbar-group {
    display: inline-flex;
    align-items: center;
    gap: .45rem;
    padding: .3rem .45rem;
    border: 1px solid var(--border);
    border-radius: .7rem;
    background: var(--surface-2);
}
.toolbar-group .tg-label {
    font-size: .72rem;
    font-weight: 600;
    letter-spacing: .02em;
    color: var(--text-2);
    cursor: grab;
    user-select: none;
    padding: .15rem .25rem;
    border-radius: .4rem;
    white-space: nowrap;
}
.toolbar-group .tg-label:hover { background: var(--surface-3); color: var(--text); }
.toolbar-group .tg-label:active { cursor: grabbing; }
.toolbar-group.dragging { opacity: .45; border-style: dashed; }
.toolbar-group .tg-status {
    font-size: .72rem;
    max-width: 15rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.toolbar-group input[type="text"] { min-width: 160px; }
.screen-keys .keycap.wide { width: auto; padding: 0 .6rem; font-size: .75rem; }
/* 文本输入单独成块:整行铺开,排在控制分组下方,不再挤在按钮堆里。 */
.toolbar-group[data-group="input"] { flex: 1 0 100%; }
.toolbar-group[data-group="input"] #text-input { flex: 1; min-width: 200px; }
.toolbar-group .tg-grip { opacity: .5; margin-right: .2rem; font-size: .8em; letter-spacing: -.1em; }
.toolbar-group .tg-move { display: inline-flex; gap: .1rem; margin-left: .35rem; opacity: 0; transition: opacity .12s ease; }
.toolbar-group:hover .tg-move, .toolbar-group:focus-within .tg-move { opacity: 1; }
.toolbar-group .tg-move-btn {
    padding: 0 .3rem;
    min-height: 0;
    font-size: .6rem;
    line-height: 1.6;
    border-radius: .3rem;
    background: var(--surface);
    border: 1px solid var(--border);
    color: var(--text-2);
}
.toolbar-group .tg-move-btn:hover { color: var(--text); background: var(--surface-3); }

/* App 管理里的 APK 投放区(拖拽目标从投屏画面挪到这里)。 */
.apk-dropzone {
    display: flex;
    align-items: center;
    gap: .6rem;
    padding: .6rem .8rem;
    margin-bottom: .6rem;
    border: 1px dashed var(--border-strong);
    border-radius: var(--radius-sm);
    background: var(--surface-2);
}
.apk-dropzone.drop-active {
    border-color: var(--accent);
    background: var(--accent-soft);
}
.screen-toolbar .screen-kick {
    display: inline-flex;
    align-items: center;
    gap: .4rem;
    padding: .45rem .55rem;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    white-space: nowrap;
    cursor: pointer;
    user-select: none;
}
.screen-toolbar .screen-kick:hover { background: var(--surface-2); }
.screen-toolbar .screen-kick input {
    flex: none;
    min-width: 0;
    width: auto;
    margin: 0;
}

/* Navigation keys: the phone's back / home / recents triangle-circle-square,
 * grouped into one segmented control so they read as the device's nav bar
 * instead of three unrelated commands sitting next to a text field. */
.screen-keys {
    display: inline-flex;
    align-items: stretch;
    height: 32px;
    background: var(--surface);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-sm);
    overflow: hidden;
}
.screen-keys .keycap {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    /* The group contributes the 1px border, so the caps fill what is left of
     * the 32px control height rather than adding their own minimum on top. */
    min-height: 0;
    align-self: stretch;
    padding: 0;
    border: 0;
    border-radius: 0;
    background: var(--surface);
    color: var(--text-2);
}
.screen-keys .keycap + .keycap { border-left: 1px solid var(--border); }
.screen-keys .keycap:hover { background: var(--surface-2); color: var(--text); }
.screen-keys .keycap:focus-visible { outline: 2px solid var(--ring); outline-offset: -2px; }
.screen-keys .keycap svg { width: 15px; height: 15px; fill: currentColor; }

/* Power is a menu rather than a key: restarting and shutting a device down
 * are not keystrokes, and a single power button could never express them. */
/* Chrome slots <details> content into ::details-content, which keeps a box in
 * flow even when the menu is closed and the panel is absolutely positioned:
 * the natural content height is 44.8px with the <summary> centred in it. The
 * 32px height alone only clipped that box — the summary stayed 6.4px down —
 * so centre the items too, which lands the summary exactly on the row. */
.menu {
    position: relative;
    display: inline-flex;
    align-items: center;
    height: 32px;
}
.menu > summary {
    display: inline-flex;
    align-items: center;
    gap: .4rem;
    min-height: 32px;
    padding: .45rem .8rem;
    font-size: .85rem;
    font-weight: 500;
    line-height: 1;
    white-space: nowrap;
    color: var(--text);
    background: var(--surface);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-sm);
    cursor: pointer;
    user-select: none;
    list-style: none;
}
.menu > summary::-webkit-details-marker { display: none; }
.menu > summary::after {
    content: "";
    width: 0;
    height: 0;
    border-left: 4px solid transparent;
    border-right: 4px solid transparent;
    border-top: 4px solid currentColor;
    opacity: .6;
}
.menu > summary:hover { background: var(--surface-2); }
.menu > summary:focus-visible { outline: 2px solid var(--ring); outline-offset: 2px; }
.menu[open] > summary { background: var(--surface-2); }
.menu-panel {
    position: absolute;
    z-index: 30;
    top: calc(100% + 4px);
    left: 0;
    display: flex;
    flex-direction: column;
    min-width: 190px;
    padding: .3rem;
    text-align: left;
    background: var(--surface);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-sm);
    box-shadow: var(--shadow-2);
}
.menu-item {
    width: 100%;
    padding: .45rem .6rem;
    font-weight: 400;
    text-align: left;
    color: var(--text);
    background: transparent;
    border: 0;
}
.menu-item:hover { background: var(--surface-2); }
.menu-item-danger { color: var(--danger); }
.menu-item-danger:hover { background: var(--danger-soft); }
.menu-sep { height: 1px; margin: .3rem .2rem; background: var(--border); }

/* The typing hint is the first thing worth dropping when the row runs out of
 * width; the controls it explains stay reachable. */
@media (max-width: 720px) {
    .screen-toolbar .screen-hint { display: none; }
}

.output-box {
    max-height: 280px;
    overflow: auto;
    margin: .6rem 0 0;
    white-space: pre-wrap;
    word-break: break-all;
}

.link-block {
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    background: var(--surface-2);
    padding: .7rem .85rem;
    margin: .65rem 0;
}
.link-block .remove-link, .link-block .remove-bss {
    margin-top: .55rem;
    min-height: 28px;
    background: transparent;
    color: var(--danger);
    border: 1px solid currentColor;
    padding: .25rem .7rem;
    font-size: .8rem;
}
.link-block .remove-link:hover, .link-block .remove-bss:hover {
    background: var(--danger-soft);
}

/* ----------------------------------------------------------- login page */

body.login-page {
    display: grid;
    place-items: center;
    min-height: 100vh;
    background:
        radial-gradient(900px 420px at 50% -10%,
            color-mix(in srgb, var(--accent) 12%, transparent), transparent 70%),
        var(--bg);
}
.login-card { width: 100%; max-width: 360px; padding: 0 1rem; }
.login-card .panel { padding: 1.5rem; box-shadow: var(--shadow-2); }
.login-card h1 {
    font-size: 1.2rem;
    text-align: center;
    margin-bottom: 1.25rem;
}
.login-card .lang-switch { justify-content: flex-end; margin-bottom: .5rem; }
.login-card button[type="submit"] { width: 100%; margin-top: .35rem; min-height: 36px; }

/* --------------------------------------------------------- small screens */

@media (max-width: 900px) {
    .workspace { grid-template-columns: 1fr; }
}

@media (max-width: 640px) {
    /* The header is one non-wrapping row on desktop; at phone widths brand +
     * four nav links + language + theme + logout cannot fit, and the whole
     * page ended up scrolling sideways. Wrap the nav onto its own row and let
     * it scroll there instead. */
    header {
        height: auto;
        min-height: 52px;
        flex-wrap: wrap;
        align-content: center;
        padding: .45rem .75rem;
        row-gap: .3rem;
        gap: .3rem;
    }
    header nav {
        order: 3;
        flex: 1 0 100%;
        overflow-x: auto;
        scrollbar-width: none;
    }
    header nav::-webkit-scrollbar { display: none; }
    /* A flex:1 spacer inside a wrapping flex row eats the whole line and
     * pushes every following item onto one of its own — six stacked rows
     * instead of two. It has no job once the header wraps. */
    header .spacer { display: none; }
    .brand { margin-right: auto; }
    .logout-user { display: none; }

    main { padding: 1rem .75rem 2.5rem; }
    .panel { padding: .85rem; border-radius: var(--radius-sm); }
    .panel > table:only-child,
    .panel > .table-scroll:only-child { margin: -.85rem; }
    .panel > table:only-child { width: calc(100% + 1.7rem); }
    /* The header height is no longer fixed, so page-sticky headers would sit
     * at the wrong offset. */
    thead th { position: static; }
}

/* Hook & modules console: bare <div class="row"> has no flex rule of its own
 * (only form.row does), so give these rows the same wrap-and-space layout the
 * screen toolbar uses. `hidden` on a scaffold row must still win, hence the
 * explicit reset. */
.hook-console-row, #hook-panel .row, #hook-console .row,
.panel .scaffold .row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: .5rem;
    margin: .5rem 0;
}
.panel .scaffold .row[hidden] { display: none; }
.scaffold {
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    padding: .4rem .8rem .8rem;
    margin: .6rem 0;
}
.scaffold legend { padding: 0 .4rem; font-size: .8rem; color: var(--text-2); }
#module-source { width: 100%; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: .8rem; }
.page-footer {
    /* 和 main 同宽居中,别贴着窗口左边。 */
    max-width: 1400px;
    margin: 1.2rem auto .6rem;
    padding: .4rem 1.25rem;
    border-top: 1px solid var(--border);
    font-size: .78rem;
    color: var(--text-2);
}
.screens-head h1 { margin-bottom: .4rem; }
/* 标记是 <div class="row">,但 .row 只对 form 生效:不显式 flex 的话 align-items
   不起作用,勾选框会比旁边的按钮高出半行。 */
.screens-controls { display: flex; flex-wrap: wrap; align-items: center; gap: .4rem; }
.wall-controls { margin-top: .35rem; }
.wall-controls #wall-target { font-weight: 600; }
.wall-controls #wall-text { min-width: 200px; flex: 1 1 200px; }
.screens-grid {
    display: grid;
    gap: .8rem;
    margin-top: .8rem;
    justify-content: center;
}
.screen-tile {
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--panel);
    overflow: hidden;
}
.screen-tile.focused { box-shadow: 0 0 0 2px var(--accent); }
.screen-tile-head {
    display: flex;
    align-items: center;
    gap: .5rem;
    padding: .35rem .5rem;
    border-bottom: 1px solid var(--border);
    font-size: .85rem;
}
.screen-tile-head .tile-name { font-weight: 600; }
.screen-tile-head .tile-status { font-size: .75rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.screen-tile-head button { padding: .1rem .4rem; }
/* 面板高度跟着宽度走(竖屏手机比例),不再固定 72vh:4 列这种窄面板下面
   不会空出一大片,2 列的宽面板由 max-height 封顶。 */
.screen-tile-body {
    position: relative;
    width: 100%;
    aspect-ratio: 10 / 17;
    max-height: min(80vh, 860px);
    background: var(--surface-2);
}
.screen-tile-frame {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    border: 0;
}
.screen-tile-start {
    position: absolute;
    inset: 0;
    margin: auto;
    width: max-content;
    height: max-content;
    padding: .8rem 1.6rem;
    font-size: 1rem;
}
.screen-tile-full {
    position: fixed;
    inset: 0;
    z-index: 2000;
    margin: 0;
    border-radius: 0;
}
.screen-tile-full .screen-tile-body { height: 100%; aspect-ratio: auto; max-height: none; }
/* 投屏墙 iframe 内页:外层面板已经有边框和标题栏,这里不再套一层卡片、不带整站
   页边距;手机机身在面板里水平垂直居中,任何列数下都不出滚动条。 */
body.screen-embed {
    height: 100vh;
    margin: 0;
    padding: .5rem;
    box-sizing: border-box;
    overflow: hidden;
    background: var(--surface-2);
}
body.screen-embed main { max-width: none; height: 100%; margin: 0; padding: 0; }
body.screen-embed .screen-panel {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    height: 100%;
    margin: 0;
    padding: 0;
    border: 0;
    border-radius: 0;
    box-shadow: none;
    background: transparent;
}
/* 连接状态已经同步显示在面板标题栏上(screens.js 轮询),这里不再重复一遍。 */
body.screen-embed #screen-status { display: none; }
.screens-controls label { display: inline-flex; align-items: center; gap: .3rem; }
.screens-controls label input { margin: 0; }

/* ---------------------------------------------------------------- polish */
/* 2026-09-23 视觉整理("清水房"装修):面板标题与内容分层、表格行有悬停反馈、
 * 输入聚焦有环、控件过渡顺滑。只做加法,不改动既有选择器的语义。 */
.panel > h2:first-child {
    margin: -.15rem 0 .8rem;
    padding-bottom: .55rem;
    border-bottom: 1px solid var(--border);
    font-size: .95rem;
    letter-spacing: .01em;
}
table tbody tr { transition: background .1s ease; }
table tbody tr:hover { background: rgba(47, 86, 211, .04); }
input[type="text"], input[type="number"], input:not([type]), select, textarea {
    transition: border-color .12s ease, box-shadow .12s ease;
}
input:focus-visible, select:focus-visible, textarea:focus-visible {
    outline: none;
    border-color: var(--ring);
    box-shadow: 0 0 0 3px rgba(108, 140, 232, .22);
}
button { transition: background .12s ease, color .12s ease, box-shadow .12s ease; }
button.ghost:hover { box-shadow: var(--shadow-1); }

/* 状态胶囊:实例信息表里的"系统服务/SystemUI/启动完成"用短标签 + 小圆点,
 * 长解释进 title。整格染红既丑又截断(2026-09-23 反馈),这里只染胶囊。 */
.status-pill {
    display: inline-flex;
    align-items: center;
    gap: .4rem;
    padding: .1rem .55rem;
    border-radius: var(--radius-pill);
    font-size: .8rem;
    line-height: 1.6;
    white-space: nowrap;
}
.status-pill::before {
    content: "";
    width: .5rem;
    height: .5rem;
    border-radius: 50%;
    background: currentColor;
    flex: none;
}
.status-pill.ok { background: var(--ok-soft); color: var(--ok); }
.status-pill.bad { background: var(--danger-soft); color: var(--danger); }
.status-pill.warn { background: var(--warn-soft); color: var(--warn); }
.status-pill.unknown { background: var(--surface-2); color: var(--text-3); }

/* ------------------------------------------------- 手机机身(模拟真机布局)
 * 屏幕包在一个深色机身里:右侧音量摇杆 + 电源键、左侧 SIM 卡槽、底部导航条。
 * 侧键从机身边缘探出 3px,悬停/按下有反馈;弹出面板复用 .menu-panel。 */
.phone-body {
    position: relative;
    display: inline-block;
    /* --phone-scale 由 screen.js 的 fitVideo() 按视频宽度写入(参考 360px),
       机身上所有硬件尺寸都乘这个比例,跟着手机一起缩放。 */
    --phone-scale: 1;
    padding: calc(14px * var(--phone-scale)) calc(18px * var(--phone-scale)) calc(10px * var(--phone-scale));
    background: linear-gradient(160deg, #262b33, #15181d 70%);
    /* 机身圆角收小(2026-09-24 反馈"外面的圆角直径太大"):30px 看着像胶囊,
       12px 才是手机该有的样子。 */
    border-radius: calc(12px * var(--phone-scale));
    /* 2px 描边用 box-shadow spread 画:不改变布局尺寸,侧键还能从描边上探出来。
       之前只有一圈 5% 白色的内衬,深色主题里机身和面板几乎糊在一起。 */
    box-shadow: var(--shadow-2), 0 0 0 2px var(--phone-edge), inset 0 0 0 1px rgba(255, 255, 255, .05);
}
.phone-body video {
    display: block;
    background: #000;
    /* 屏幕与机身的边界:一道浅色亮边 + 一圈暗内衬,屏幕再黑也能一眼看出
       "玻璃到哪为止"——否则深色画面和深色机身糊成一片(2026-09-23 反馈)。 */
    border: 1px solid #4a515c;
    box-shadow: 0 0 0 1px rgba(0, 0, 0, .6), inset 0 0 0 1px rgba(255, 255, 255, .05);
}
.phone-side { position: absolute; z-index: 5; }
.phone-key {
    display: block;
    width: calc(6px * var(--phone-scale));
    min-height: 0;
    padding: 0;
    border: 1px solid #23272e;
    border-radius: calc(3px * var(--phone-scale));
    background: #3d434c;
    cursor: pointer;
}
.phone-key:hover { background: #4d545f; }
.phone-key:active { transform: none; background: #59616d; }
.phone-power > summary { list-style: none; }
.phone-power > summary::-webkit-details-marker { display: none; }
/* 侧键/卡槽的纵向位置用百分比:跟着机身高度走,不写死像素 —— 写死时在矮面板里
   全挤到顶上去了(2026-09-24 反馈"太靠上")。 */
.phone-vol { right: calc(-5px * var(--phone-scale)); top: 18%; display: grid; gap: calc(5px * var(--phone-scale)); }
.phone-vol .phone-key { height: calc(38px * var(--phone-scale)); }
.phone-power { right: calc(-5px * var(--phone-scale)); top: 32%; }
.phone-power .phone-key { height: calc(44px * var(--phone-scale)); }
.phone-power .menu-panel { top: 0; left: auto; right: calc(100% + 8px); }
.sim-tray { left: calc(-5px * var(--phone-scale)); top: 25%; }
.phone-slot {
    display: block;
    width: calc(6px * var(--phone-scale));
    height: calc(26px * var(--phone-scale));
    border: 1px solid #23272e;
    border-radius: calc(3px * var(--phone-scale));
    background: #2c313a;
    cursor: pointer;
    list-style: none;
}
.phone-slot::-webkit-details-marker { display: none; }
.sim-tray[open] .phone-slot { background: #59616d; }
.sim-tray .menu-panel { top: 0; left: calc(100% + 8px); }
.sim-pop { min-width: 264px; padding: .5rem .6rem; }
.sim-pop .row { gap: .35rem; }
.phone-navbar {
    position: relative;
    display: flex;
    height: calc(28px * var(--phone-scale));
    margin-top: calc(8px * var(--phone-scale));
    border-radius: calc(14px * var(--phone-scale));
    background: rgba(255, 255, 255, .05);
    overflow: hidden;
}
.gesture-zone {
    flex: 1;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: 0;
    padding: 0;
    border: 0;
    border-radius: 0;
    background: transparent;
    color: #98a1af;
}
.gesture-zone:hover { background: rgba(255, 255, 255, .07); color: #fff; }
.gesture-zone svg { width: calc(13px * var(--phone-scale)); height: calc(13px * var(--phone-scale)); fill: currentColor; opacity: .8; }

/* ------------------------------------------------- 首页实例列表(2026-10-09) */
/* 标题行:左标题、右页面级操作。 */
.page-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    flex-wrap: wrap;
    margin: 0 0 1rem;
}
.page-head h1 { margin: 0; }
.page-head-actions { display: flex; align-items: center; gap: .5rem; }
.page-head-actions form, .host-group-head form { margin: 0; }

/* 按钮里的台数徽标。 */
.badge {
    display: inline-block;
    min-width: 1.5em;
    margin-left: .45em;
    padding: 0 .45em;
    border-radius: var(--radius-pill);
    background: var(--accent-soft);
    color: var(--accent);
    font-size: .78em;
    font-weight: 600;
    line-height: 1.6;
    text-align: center;
}
button:disabled .badge { background: var(--surface-2); color: var(--text-3); }

/* 用户块标题行:沿用 .panel > h2:first-child 的分隔线,右侧放批量分配。 */
.block-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: .75rem;
    flex-wrap: wrap;
    margin: -.15rem 0 .8rem;
    padding-bottom: .55rem;
    border-bottom: 1px solid var(--border);
}
.block-head h2 { margin: 0; font-size: .95rem; letter-spacing: .01em; }
.bulk-bar { display: flex; align-items: center; gap: .75rem; flex-wrap: wrap; margin: 0; }
.bulk-bar b { color: var(--text); font-weight: 600; }
/* 批量栏里一组相关按钮;组与组之间一条细竖线。 */
.bulk-group { display: inline-flex; align-items: center; gap: .35rem; }
.bulk-group + .bulk-group { padding-left: .75rem; border-left: 1px solid var(--border); }

/* 宿主分组小标题:左宿主名/地址/台数,右侧该宿主的「启动全部」。 */
.host-group-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: .75rem;
    margin: 1.1rem 0 .5rem;
}
.block-head + .host-group-head { margin-top: .3rem; }
.host-group-title { margin: 0; font-size: .95rem; font-weight: 600; }
.host-group-title .muted { font-weight: 400; margin-left: .35rem; }
/* 宿主分组折叠:箭头按钮紧贴标题左侧;收起时隐藏紧随其后的那张表。 */
.host-group-title { display: flex; align-items: center; gap: .25rem; }
button.collapse-toggle, button.collapse-toggle:hover { padding: 0 .3rem; min-width: 1.6rem; line-height: 1.4; border: none; background: transparent; box-shadow: none; }
.host-group-head.collapsed + .table-scroll { display: none; }
/* 实例列表的用户分组标题(只做归类):收起时隐藏整节。 */
.group-section-head { margin: 1.4rem 0 .4rem; }
.group-section-title { display: flex; align-items: center; gap: .25rem; margin: 0; font-size: 1.1rem; }
.group-section-title .muted { font-weight: 400; font-size: .9rem; margin-left: .35rem; }
.group-section-head.collapsed + .group-section-body { display: none; }
button.btn-sm { padding: .18rem .6rem; font-size: .8rem; }

/* 勾选列:窄、不撑开表格。 */
th.col-select, td.col-select { width: 2rem; padding-right: 0; }


/* ================================================= 2026-10-16 首页 / 详情页改版 */

/* 禁用一律中性灰:以前主按钮降到 50% 透明度变成淡蓝,看着像"选中"而不是"不可点"。 */
button:disabled, button:disabled:hover,
button.ghost:disabled, button.ghost:disabled:hover,
button.danger:disabled, button.danger:disabled:hover {
    background: var(--surface-2);
    color: var(--text-3);
    border-color: var(--border);
    box-shadow: none;
    cursor: not-allowed;
    transform: none;
}

.spacer { flex: 1; }
.visually-hidden {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
}
.mono { font-family: var(--font-mono); font-size: .8rem; }
.filtered-out { display: none !important; }

button.btn-sm { min-height: 28px; }
.icon-btn { min-width: 32px; padding-left: .5rem; padding-right: .5rem; }
/* 文字链样式的小按钮:宿主分隔行里的「启动停止的 / 重启运行的」。 */
button.link-btn {
    min-height: 0;
    padding: .15rem .4rem;
    font-size: .78rem;
    background: transparent;
    color: var(--accent);
    border: 0;
}
button.link-btn:hover { background: var(--accent-soft); }
button.link-btn:disabled, button.link-btn:disabled:hover { background: transparent; color: var(--text-3); }
/* 危险但不常用:描边红,不再整块实心红。 */
button.danger-outline {
    background: transparent;
    color: var(--danger);
    border-color: color-mix(in srgb, var(--danger) 45%, transparent);
}
button.danger-outline:hover { background: var(--danger-soft); }

/* 旧镜像改成黄色小标签(以前是一段加粗黄字)。 */
.image-old {
    display: inline-block;
    padding: 0 .45rem;
    border-radius: var(--radius-pill);
    background: var(--warn-soft);
    font-size: .72rem;
    line-height: 1.7;
}
.image-version + .image-old { margin-left: .4rem; }

/* ---- 下拉菜单(「批量 ▾」、每行的「⋯」):复用 .menu / .menu-panel / .menu-item。 */
.menu-panel-right { left: auto; right: 0; }
.menu-panel form { margin: 0; }
.menu-item {
    display: flex;
    align-items: center;
    gap: .45rem;
    font-size: .85rem;
    line-height: 1.25;
    border-radius: var(--radius-sm);
    white-space: nowrap;
}
a.menu-item { color: var(--text); text-decoration: none; }
.menu-item .badge { margin-left: auto; }
.menu-item:disabled, .menu-item:disabled:hover { background: transparent; color: var(--text-3); border: 0; }
.menu-note {
    padding: .3rem .6rem .45rem;
    margin-bottom: .25rem;
    border-bottom: 1px solid var(--border);
    font-size: .75rem;
    color: var(--text-2);
    white-space: nowrap;
}
.menu-note b { color: var(--text); font-weight: 600; }
.row-menu > summary.icon-summary {
    justify-content: center;
    width: 32px;
    padding: 0;
    font-size: 1.05rem;
    color: var(--text-2);
}
.row-menu > summary.icon-summary::after { display: none; }

/* ---- 首页工具条:统计胶囊(兼作筛选)+ 搜索 + 宿主范围。 */
.list-toolbar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: .5rem;
    margin: 0 0 1.25rem;
}
.stat-chips { display: flex; flex-wrap: wrap; gap: .4rem; }
.stat {
    display: inline-flex;
    align-items: center;
    gap: .4rem;
    min-height: 30px;
    padding: .3rem .75rem;
    border: 1px solid var(--border);
    border-radius: var(--radius-pill);
    background: var(--surface);
    color: var(--text-2);
    font-size: .82rem;
    font-weight: 500;
    line-height: 1;
}
button.stat:hover { background: var(--surface-2); color: var(--text); }
.stat b { color: var(--text); font-weight: 650; font-variant-numeric: tabular-nums; }
.stat[aria-pressed="true"], .stat[aria-pressed="true"]:hover {
    border-color: color-mix(in srgb, var(--accent) 55%, transparent);
    background: var(--accent-soft);
    color: var(--accent);
}
.stat[aria-pressed="true"] b { color: var(--accent); }
.stat-static { cursor: default; }
.dot { width: 7px; height: 7px; border-radius: 50%; background: var(--text-3); flex: none; }
.dot-ok { background: var(--ok); }
.dot-bad { background: var(--danger); }
.dot-warn { background: var(--warn); }
#list-search {
    width: 270px;
    padding-left: 2rem;
    background: var(--surface) no-repeat .65rem 50% / 14px
        url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23808a99' stroke-width='2.2'%3E%3Ccircle cx='11' cy='11' r='7'/%3E%3Cpath d='m20 20-3.5-3.5'/%3E%3C/svg%3E");
}

/* ---- 分组节标题:细标签 + 一条延伸到右边的分隔线。 */
.section-label {
    display: flex;
    align-items: center;
    gap: .35rem;
    margin: 1.5rem 0 .6rem;
    font-size: .8rem;
    color: var(--text-2);
}
.section-label::after { content: ""; flex: 1; height: 1px; margin-left: .4rem; background: var(--border); }
.section-label .section-name { color: var(--text); font-size: .92rem; font-weight: 620; }
.section-label.collapsed + .group-section-body { display: none; }

/* ---- 客户卡片:一张卡一个表头,宿主是表格里的分隔行。 */
.inst-card {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    box-shadow: var(--shadow-1);
    margin-bottom: .9rem;
}
.inst-card-head {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: .5rem;
    padding: .65rem 1rem;
}
.inst-card-head h2 { margin: 0; font-size: .95rem; }
.inst-card-head h2 .muted { font-weight: 500; }
.inst-card-head .card-endpoint { font-weight: 400; font-size: .8rem; }
.inst-card.collapsed .inst-table { display: none; }
.avatar {
    display: inline-grid;
    place-items: center;
    width: 26px;
    height: 26px;
    flex: none;
    border-radius: 50%;
    background: var(--accent-soft);
    color: var(--accent);
    font-size: .75rem;
    font-weight: 650;
}
.avatar-none { background: var(--surface-3); color: var(--text-2); }
.avatar-host { border-radius: 7px; background: var(--surface-3); color: var(--text-2); font-size: .8rem; }
/* 分段切换(实例列表的视角)。 */
.seg {
    display: inline-flex;
    overflow: hidden;
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-sm);
    background: var(--surface);
}
.seg a {
    display: inline-flex;
    align-items: center;
    min-height: 30px;
    padding: 0 .75rem;
    font-size: .82rem;
    font-weight: 500;
    color: var(--text-2);
    white-space: nowrap;
}
.seg a + a { border-left: 1px solid var(--border); }
.seg a:hover { background: var(--surface-2); color: var(--text); text-decoration: none; }
.seg a[aria-current="true"] { background: var(--accent-soft); color: var(--accent); }
.host-actions { display: inline-flex; align-items: center; gap: .15rem; }
.host-actions form { display: inline; margin: 0; }

.inst-table th, .inst-table td { padding: .4rem .75rem; }
/* 全局 input 规则给了 32px 最小高度和内边距,复选框也吃到了,把表头/每行都撑高一截。 */
.inst-table input[type="checkbox"] { min-height: 0; padding: 0; margin: 0; vertical-align: middle; }
.section-label button.collapse-toggle, .inst-card-head button.collapse-toggle,
tr.host-row button.collapse-toggle { min-height: 24px; }
.inst-table tr.inst-row > td { height: 46px; }
/* 表格不再包在横向滚动容器里(那会把「⋯」菜单裁掉),表头可以直接贴着站点顶栏吸顶。 */
.inst-table thead th {
    top: 52px;
    border-top: 1px solid var(--border);
    font-size: .68rem;
}
.inst-table thead th:first-child, .inst-table thead th:last-child { border-radius: 0; }
button.th-sort, button.th-sort:hover {
    min-height: 0;
    padding: 0;
    background: none;
    border: 0;
    box-shadow: none;
    color: inherit;
    font: inherit;
    letter-spacing: inherit;
    text-transform: inherit;
}
button.th-sort:hover { color: var(--text); }
.th-sort .sort-arrow { color: var(--accent); }
tbody.host-group.collapsed tr.inst-row { display: none; }
tr.host-row > td {
    padding: .3rem .75rem;
    background: var(--surface-2);
    border-bottom: 1px solid var(--border);
    font-size: .8rem;
    color: var(--text-2);
}
tbody.host-group + tbody.host-group tr.host-row > td { border-top: 1px solid var(--border); }
table tbody tr.host-row:hover { background: none; }
.host-row-inner { display: flex; flex-wrap: wrap; align-items: center; gap: .4rem; }
.host-row-inner .host-name { color: var(--text); font-weight: 620; }
.inst-table tbody.host-group:last-child tr.inst-row:last-child > td { border-bottom: 0; }
.inst-name { font-weight: 600; }
.inst-id { margin-left: .4rem; color: var(--text-3); font-size: .75rem; }
.c-adb { white-space: nowrap; }
.adb-text { font-family: var(--font-mono); font-size: .8rem; }
button.copy-btn {
    min-height: 0;
    margin-left: .15rem;
    padding: .1rem .3rem;
    background: none;
    border: 0;
    color: var(--text-3);
    font-size: .85rem;
    opacity: 0;
}
tr:hover button.copy-btn, button.copy-btn:focus-visible { opacity: 1; }
button.copy-btn:hover { background: none; color: var(--accent); }
button.copy-btn.copied { opacity: 1; color: var(--ok); }
.c-img { white-space: nowrap; }
.c-img .image-version { margin-left: 0; }
.image-name { font-size: .8rem; color: var(--text-2); }
/* 归属下拉:平时只是文字,悬停/聚焦才露出边框。选完自动保存。 */
select.owner-select {
    min-height: 28px;
    padding: .15rem 1.6rem .15rem .45rem;
    font-size: .82rem;
    border-color: transparent;
    background-color: transparent;
}
tr:hover select.owner-select, select.owner-select:focus { border-color: var(--border-strong); background-color: var(--surface); }
.c-actions { width: 1%; text-align: right; white-space: nowrap; }
.c-actions .row-menu { height: 28px; margin-left: .3rem; vertical-align: middle; }
.c-actions .row-menu > summary.icon-summary { min-height: 28px; height: 28px; width: 28px; }
.c-actions .row-menu .menu-panel { top: calc(100% + 4px); }

/* ---- 勾选后浮出的全局批量栏。 */
.selection-bar {
    position: fixed;
    left: 50%;
    bottom: 20px;
    z-index: 40;
    transform: translateX(-50%);
    display: flex;
    align-items: center;
    gap: .45rem;
    margin: 0;
    padding: .45rem .5rem .45rem 1rem;
    border-radius: 12px;
    background: var(--text);
    color: var(--bg);
    box-shadow: var(--shadow-2);
    font-size: .85rem;
}
.selection-bar b { color: inherit; }
.selection-count { margin-right: .3rem; white-space: nowrap; }
.selection-bar button.ghost {
    background: transparent;
    color: var(--bg);
    border-color: color-mix(in srgb, var(--bg) 35%, transparent);
}
.selection-bar button.ghost:hover { background: color-mix(in srgb, var(--bg) 14%, transparent); box-shadow: none; }
body:has(.selection-bar:not([hidden])) main { padding-bottom: 6rem; }

/* ---- 对话框(新建实例)。 */
dialog.dialog {
    width: min(560px, calc(100vw - 32px));
    padding: 0;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--surface);
    color: var(--text);
    box-shadow: var(--shadow-2);
}
dialog.dialog::backdrop { background: rgba(10, 14, 20, .45); }
.dialog-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: .7rem .75rem .7rem 1.1rem;
    border-bottom: 1px solid var(--border);
}
.dialog-head h2 { margin: 0; font-size: 1rem; }
.dialog-body { padding: .5rem 1.1rem 1rem; }
.dialog-body .muted { margin: .6rem 0 0; }
.dialog-foot {
    display: flex;
    justify-content: flex-end;
    gap: .5rem;
    padding: .7rem 1.1rem;
    border-top: 1px solid var(--border);
    background: var(--surface-2);
    border-radius: 0 0 var(--radius) var(--radius);
}

/* ---- 实例详情页。 */
.detail-head { margin: 0 0 1rem; }
.back-link { font-size: .82rem; color: var(--text-2); }
.detail-title-row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: .75rem 1rem;
    margin-top: .35rem;
}
.detail-title { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem .65rem; min-width: 0; }
.detail-title h1 { margin: 0; }
#instance-state-badge { font-size: .8rem; padding: .15rem .6rem; }
.detail-meta { font-size: .85rem; }
.detail-meta .image-version { margin-left: 0; }
.detail-actions { display: flex; flex-wrap: wrap; align-items: center; gap: .4rem; }
.detail-actions form, .detail-actions a { margin: 0; }
.detail-actions a:hover { text-decoration: none; }
#instance-action-pending { margin-top: .55rem; }
/* 投屏一栏吃掉剩余宽度、手机在里面居中;操作栏定宽。投屏吸顶,往下翻操作时画面一直在。 */
.workspace { grid-template-columns: minmax(0, 1fr) minmax(360px, 480px); }
.workspace-screen { position: sticky; top: 68px; }
/* 投屏上方的只读信息带:每组 2x2,宽屏一行排开,窄了折行。 */
.info-strip {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
    gap: .25rem 0;
    padding: .7rem 0;
}
.info-group {
    display: grid;
    grid-template-columns: 1fr 1fr;
    align-content: start;
    column-gap: .9rem;
    padding: 0 1.15rem;
    min-width: 0;
}
.info-group + .info-group { border-left: 1px solid var(--border); }
.info-group-title {
    grid-column: 1 / -1;
    margin-bottom: .1rem;
    font-size: .68rem;
    font-weight: 650;
    letter-spacing: .06em;
    text-transform: uppercase;
    color: var(--text-3);
}
.info-strip .kv { border-bottom: 0; padding: .3rem 0; }
.kv {
    display: flex;
    flex-direction: column;
    gap: .1rem;
    min-width: 0;
    padding: .42rem 0;
    border-bottom: 1px solid var(--border);
}
.kv-label { font-size: .72rem; font-weight: 500; color: var(--text-3); }
.kv-value { font-size: .875rem; overflow-wrap: anywhere; }
.kv-value .status-pill { vertical-align: middle; }
.kv-value .image-version { margin-left: 0; }
.save-row .muted { flex: 1 1 200px; }
/* 输入框和下拉框在 .field 里一样高(以前数字框比下拉框高 5px)。 */
.workspace .field input, .workspace .field select,
dialog .field input, dialog .field select { height: 36px; }
.save-row .spacer { flex: 0; }
.form-note { margin-top: .45rem; }
.ctl-row { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem; padding: .55rem 0; }
.ctl-row + .ctl-row { border-top: 1px solid var(--border); }
.ctl-label { flex: none; width: 4.75rem; font-size: .8rem; font-weight: 500; color: var(--text-2); }
.ctl-row form { display: flex; align-items: center; gap: .4rem; margin: 0; }
.ctl-row form.ctl-grow { flex: 1 1 220px; }
.ctl-row form.ctl-grow input { flex: 1; min-width: 0; }
.panel-pair { display: grid; grid-template-columns: 1fr 1fr; column-gap: 1rem; }

@media (max-width: 900px) {
    .workspace { grid-template-columns: 1fr; }
    .workspace-screen { position: static; }
}

@media (max-width: 640px) {
    .list-toolbar .spacer { display: none; }
    #list-search { flex: 1 1 100%; width: auto; }
    .inst-card-head { padding: .6rem .75rem; }
    /* 手机宽度:表格变两行卡片(名称+状态 / ADB),操作在右侧,不再横向滚动。 */
    .inst-table thead { display: none; }
    .inst-table, .inst-table tbody, .inst-table tr, .inst-table td { display: block; }
    .inst-table tr.inst-row {
        display: grid;
        grid-template-columns: auto minmax(0, 1fr) auto;
        grid-template-areas: "cb name actions" "cb meta actions";
        column-gap: .6rem;
        row-gap: .1rem;
        padding: .55rem .75rem;
        border-bottom: 1px solid var(--border);
    }
    .inst-table tr.inst-row > td { height: auto; padding: 0; border: 0; }
    .inst-table td.c-cb { grid-area: cb; align-self: center; width: auto; }
    .inst-table td.c-name { grid-area: name; }
    .inst-table td.c-state { grid-area: name; justify-self: end; }
    .inst-table td.c-adb { grid-area: meta; }
    .inst-table td.c-img, .inst-table td.c-owner { display: none; }
    .inst-table td.c-actions { grid-area: actions; align-self: center; width: auto; }
    .selection-bar {
        left: 8px;
        right: 8px;
        transform: none;
        justify-content: space-between;
        gap: .3rem;
        padding: .4rem .5rem;
    }
    .selection-bar button { padding-left: .5rem; padding-right: .5rem; white-space: nowrap; }
    .panel-pair { grid-template-columns: 1fr; }
    .info-group { padding: 0 .85rem; }
    .info-group + .info-group { border-left: 0; border-top: 1px solid var(--border); padding-top: .5rem; margin-top: .3rem; }
}
