/* 后台样式。刻意不用前端框架 —— 这套界面只有你一个人用，
   引一个 Tailwind/Bootstrap 只会多一堆构建步骤和体积。 */

:root {
    --bg: #f6f7f9;
    --panel: #ffffff;
    --border: #e3e6ea;
    --text: #1f2328;
    --muted: #6b7280;
    --accent: #2563eb;
    --accent-hover: #1d4ed8;
    --danger: #dc2626;
    --warn: #b45309;
    --ok: #15803d;
    --radius: 8px;
}

* { box-sizing: border-box; }

body {
    margin: 0;
    background: var(--bg);
    color: var(--text);
    font: 14px/1.6 -apple-system, "Segoe UI", "Microsoft YaHei", Roboto, sans-serif;
}

a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }

/* ---------- 顶栏 ---------- */

header.topbar {
    display: flex;
    align-items: center;
    gap: 14px;
    height: 56px;
    padding: 0 20px;
    background: var(--panel);
    border-bottom: 1px solid var(--border);
    position: sticky;
    top: 0;
    z-index: 20;
}

/* 顶栏左侧的两个图标按钮（侧栏开关、新增产品）。
   它们是一组，间距要小于顶栏整体间距，不然两个图标看着像各管各的。 */
.topbar-left {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    gap: 6px;
}

/* 中间那块占满剩余空间、内容居中 ——
   左边只有两个图标（窄），右边是账号相关（宽），
   所以真正意义上的"居中"做不到，视觉上居中在这块空间里就够了。 */
.topbar-title {
    flex: 1;
    min-width: 0;
    display: flex;
    align-items: baseline;
    justify-content: center;
    gap: 10px;
}

header.topbar .brand {
    font-weight: 600;
    font-size: 15px;
    color: var(--text);
}

/* 顶栏里的产品切换下拉。
   做成"选择器"而不是"表单控件"的样子 —— 蓝色描边、浅底，
   和左边的标题并排看起来像同一组东西，而不是一个突兀的下拉框。 */
header.topbar .product-picker {
    max-width: 260px;
    min-height: 30px;
    padding: 3px 26px 3px 10px;
    font-size: 13px;
    font-weight: 500;
    color: var(--accent);
    background: #f5f9ff;
    border: 1px solid #dbeafe;
    border-radius: 7px;
    cursor: pointer;
}

header.topbar .product-picker:hover { border-color: var(--accent); }

.topbar-actions {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    gap: 10px;
}

/* 顶栏里的按钮统一收小、收静。
 * 顶栏是"外框"，不是内容区 —— 里面塞一个 42px 的实心主按钮，
 * 会在视觉上压过整页内容，而且和旁边安静的账号名、退出按钮不是一个重量级。
 * 所以这里整体降一级：32px 高、细边框、无填充。 */
header.topbar .topbar-actions button {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    min-height: 32px;
    padding: 5px 13px;
    font-size: 13px;
    border-radius: 7px;
    border: 1px solid var(--border);
    background: transparent;
    color: var(--text);
    cursor: pointer;
}

header.topbar .topbar-actions button:hover { background: var(--bg); }
header.topbar .topbar-actions button:disabled { opacity: .45; cursor: not-allowed; }

header.topbar .user { color: var(--muted); font-size: 13px; }

/* ---------- 左右分栏 ---------- */

.workspace {
    display: flex;
    align-items: flex-start;
    /* 刻意不用 gap：gap 在元素宽度为 0 时**依然占位**，
       侧栏收起后会凭空多出 20px 空白。
       改用 sidebar 自己的 margin-right，收起时一起归零。 */
    max-width: 1440px;
    margin: 20px auto;
    padding: 0 20px;
}

.sidebar {
    width: 270px;
    flex: 0 0 270px;
    margin-right: 20px;
    background: var(--panel);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 8px;
    position: sticky;
    top: 74px;
    overflow: hidden;
    transition: flex-basis .18s ease, width .18s ease, padding .18s ease,
                margin-right .18s ease, opacity .15s ease, border-width .18s ease;
}

/* 收起 = 整栏滑走，**不留下任何东西**。
 *
 * 早先试过收成一条 44px 窄轨、只留那个按钮 —— 孤零零一个方块杵在那儿很难看。
 * 现在展开入口挪到内容区左上角（.expand-btn），那儿才是用户会去找它的地方。 */
.workspace.sidebar-collapsed .sidebar {
    flex-basis: 0;
    width: 0;
    margin-right: 0;
    padding: 0;
    border-width: 0;
    opacity: 0;
    pointer-events: none;
}

/* 侧栏标题。切换按钮在顶栏，这里不放第二个入口 —— 两个开关反而让人犹豫点哪个 */
.side-head {
    display: flex;
    align-items: center;
    min-height: 30px;
    font-size: 12px;
    font-weight: 600;
    color: var(--muted);
    padding: 2px 10px;
    letter-spacing: .04em;
}

/* 一行 = [名字 …… 数量] [编辑][删除] */
.side-row {
    display: flex;
    align-items: center;
    gap: 3px;
}

.side-item {
    display: flex;
    align-items: center;
    gap: 7px;
    flex: 1;
    min-width: 0;
    padding: 7px 9px;
    border-radius: 6px;
    color: var(--text);
    font-size: 13px;
}

.side-item:hover { background: var(--bg); text-decoration: none; }

.side-item .side-name {
    flex: 1;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.side-row.active .side-item {
    background: #eef2ff;
    color: var(--accent);
    font-weight: 500;
}

.side-item .count {
    font-size: 11.5px;
    color: var(--muted);
    background: var(--bg);
    padding: 1px 7px;
    border-radius: 999px;
}

.side-row.active .count { background: #dbeafe; color: var(--accent); }

/* 编辑/删除：始终可见（之前是鼠标移上去才出现，不好找）。
   用图标而不是文字 —— "编辑"、"删除"两个词挤在名字和数量旁边太占地方。 */
.side-actions {
    display: flex;
    gap: 1px;
    flex: 0 0 auto;
}

/* ---------- 图标按钮 ---------- */

/* 产品行的编辑/删除、顶栏的收起按钮都用它。
   类选择器比 .side-actions button 那样的写法优先级低，所以这里
   不靠继承、把尺寸写全，避免被别处的 button 规则盖掉。 */
.icon-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 26px;
    height: 26px;
    padding: 0;
    min-height: 0;
    border: 1px solid transparent;
    border-radius: 6px;
    background: transparent;
    color: var(--muted);
    cursor: pointer;
    flex: 0 0 auto;
}

.icon-btn:hover {
    background: var(--bg);
    border-color: var(--border);
    color: var(--text);
}

.icon-btn.danger:hover {
    background: #fef2f2;
    border-color: #fecaca;
    color: var(--danger);
}

/* 试用转正。绿色 —— 和「停用」的红拉开。这是个"好事"的操作，
   而且它旁边就是停用按钮，颜色不分清楚太容易点错。 */
.icon-btn.ok:hover {
    background: #f0fdf4;
    border-color: #bbf7d0;
    color: var(--ok);
}

/* 顶栏里那个可以大一点 */
header.topbar .icon-btn { width: 32px; height: 32px; }

.side-note {
    font-size: 12px;
    color: var(--muted);
    padding: 10px;
    line-height: 1.7;
}

.content { flex: 1; min-width: 0; }

/* ---------- 面板 ---------- */

.panel {
    background: var(--panel);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 16px;
    margin-bottom: 16px;
}

.panel.highlight { border-color: #bfdbfe; background: #f8faff; }

.panel-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    margin-bottom: 12px;
}

.panel h2 { margin: 0; font-size: 15px; font-weight: 600; }

/* ---------- 筛选行 ---------- */

/* 左边是筛选表单，右边是"新增"按钮。
   按钮放在表单外面，靠 margin-left:auto 顶到最右 ——
   塞进表单里虽然也能用（type=button 不触发提交），
   但一个"新增"按钮挂在"搜索"表单里读起来很别扭。 */
.list-head {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
    margin-bottom: 12px;
}

.filters {
    display: flex;
    gap: 8px;
    align-items: center;
    flex-wrap: wrap;
    margin-bottom: 0;   /* 间距由外面的 .list-head 管 */
}

.new-btn {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    margin-left: auto;   /* 顶到最右 */
    min-height: 38px;    /* 和筛选控件一样高，同一行看起来才齐 */
    padding: 7px 16px;
    font-size: 13px;
}

/* ---------- 表格 ---------- */

table { width: 100%; border-collapse: collapse; background: var(--panel);
        border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; }

th, td {
    text-align: left;
    padding: 12px 14px;
    border-bottom: 1px solid var(--border);
    vertical-align: middle;
    font-size: 13px;
}

th {
    font-weight: 600;
    font-size: 12px;
    color: var(--muted);
    background: #fafbfc;
}

tr:last-child td { border-bottom: none; }
tbody tr:hover { background: #fafbfc; }
table.tight th, table.tight td { padding: 6px 10px; font-size: 12px; }

tr.row-deleted { opacity: .55; }
tr.row-deleted .mono { text-decoration: line-through; }

/* ---------- 徽章与标签 ---------- */

.badge {
    display: inline-block;
    padding: 2px 9px;
    border-radius: 999px;
    font-size: 12px;
    font-weight: 500;
    white-space: nowrap;
}

.badge.active  { background: #dcfce7; color: var(--ok); }
.badge.unused  { background: #e5e7eb; color: var(--muted); }
.badge.expired { background: #fef3c7; color: var(--warn); }
.badge.revoked { background: #fee2e2; color: var(--danger); }
.badge.deleted { background: #f3f4f6; color: #9ca3af; }
/* 曾激活、已解绑、等着新机器重新绑定 */
.badge.unbound { background: #dbeafe; color: var(--accent); }
/* 试用码被正式码顶替了 —— 和「已停用」（红）分开，免得分不清是你封的还是客户升级的 */
.badge.upgraded { background: #ede9fe; color: #6d28d9; }
/* 试用中 —— 和正式授权的绿分开。这两个必须一眼能区分：
   一个是"还没付钱"，一个是"已经付了钱"，混成一个颜色就白标了。 */
.badge.trial    { background: #ffedd5; color: #c2410c; }
/* 试用原地转正过来的。它确实是正式授权，但颜色和"原生正式码"稍作区分 ——
   免得你以为它是你卖出去的那张码。 */
.badge.converted { background: #ccfbf1; color: #0f766e; }
/* 设备历史弹框里**真正生效**的那一条。只有这一条有标记 ——
   作废的那些靠状态徽章 + 说明列（"已被 X 替换"）说清楚就够了。 */
tr.row-current td { background: #f9fafb; }

/* 看起来像链接的按钮（设备指纹）。
   用 <button> 而不是 <a>：它不是导航，是打开弹窗，用 <a> 会被中键/新窗口行为搞出毛刺。
   平时**不带下划线**，鼠标停上去才出现 —— 一屏二十行，每行都挂着蓝虚线的表格很吵。 */
.link-btn {
    padding: 0;
    min-height: 0;                  /* 全局 button 有 min-height:42px，会把行撑高 */
    border: 0;
    background: none;
    color: #374151;                 /* 比正文深一点，但不到"链接蓝" */
    font: inherit;
    font-size: 12.5px;
    cursor: pointer;
}

.link-btn:hover {
    color: var(--accent);
    text-decoration: underline;
    text-underline-offset: 3px;
}

/* 角标要落在文字**右边**、不能压在文字上。
   靠 padding-right 给它留出位置：wrapper 只包住文字，角标定位在 padding 区里。
   直接 right:-9px 的话角标会盖住设备码最后一位（真被盖掉过一个字符）。 */
.dot-wrap {
    position: relative;
    display: inline-block;
    padding-right: 20px;
}

/* 角标本体。可以带数字（历史条数），也可以是空的小圆点（标记"当前"）。 */
.dot-badge {
    position: absolute;
    top: -6px;
    right: 0;
    min-width: 15px;
    height: 15px;
    min-height: 0;                  /* 同上：全局 button 有 min-height，不覆写会变成一根竖条 */
    padding: 0 4px;
    box-sizing: border-box;
    border: 0;
    border-radius: 999px;
    color: #fff;
    font-family: inherit;
    font-size: 10px;
    line-height: 15px;
    text-align: center;
    cursor: pointer;
}

/* 历史条数：红点带数字。红在这套界面里就是"有东西需要你看一眼"。 */
.dot-count { background: #dc2626; }

.dot-count:hover { background: #b91c1c; }

/* 「当前」那个标记：小圆点。绿色 —— 红 = 停用/删除，绿 = 有效，别用混。 */
.dot-current {
    top: -3px;
    right: 7px;
    min-width: 0;
    width: 8px;
    height: 8px;
    padding: 0;
    background: var(--ok, #16a34a);
    cursor: default;
    box-shadow: 0 0 0 2px #fff;
}

/* 默认视图藏起来的那些（作废/停用/已删除）不在列表下面报数了 ——
   报个「另有：已被替换 2」你也看不出是谁，没有意义。
   要看得去状态筛选里选「全部状态（含作废/停用）」或者对应的那一类。 */

.tag {
    display: inline-block;
    margin-left: 6px;
    padding: 1px 6px;
    border-radius: 4px;
    font-size: 11px;
    background: #ede9fe;
    color: #6d28d9;
}

/* ---------- 表单元素 ---------- */

input[type=text], input[type=password], input[type=number],
input[type=date], select, textarea {
    padding: 10px 13px;
    min-height: 42px;
    border: 1px solid var(--border);
    border-radius: 8px;
    font: inherit;
    font-size: 14px;
    background: #fff;
    color: var(--text);
}

input:focus, select:focus, textarea:focus {
    outline: none;
    border-color: var(--accent);
    box-shadow: 0 0 0 3px rgba(37, 99, 235, .12);
}

/* 筛选条和分页条里的控件统一尺寸。
   基础样式的 input 是 42px、button 也是 42px，但这行要做紧凑些，
   所以两类都要覆盖 —— 只改 input 不改 button 的话，
   一排东西会高高低低（按钮比输入框高 4px，align-items:center 也救不回来）。 */
.filters input, .filters select {
    min-height: 38px;
    padding: 7px 11px;
    font-size: 13px;
}

.filters button, .filters .btn {
    min-height: 38px;
    padding: 7px 16px;
    font-size: 13px;
}

label { display: block; margin-bottom: 6px; font-size: 13px; color: var(--muted); }
label.check {
    display: flex;
    align-items: center;
    gap: 9px;
    font-size: 14px;
    color: var(--text);
    margin: 16px 0;
}
label.check input { margin: 0; min-height: 0; padding: 0; }

.field { margin-bottom: 18px; }
.hint { font-size: 12.5px; color: var(--muted); margin: 6px 0 0; line-height: 1.65; }

/* 一行单选：三选一的「到期设置」用 */
.radio-row {
    display: flex;
    gap: 18px;
    flex-wrap: wrap;
    padding: 2px 0;
}

.radio-row label {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    margin: 0;
    font-size: 14px;
    color: var(--text);
    cursor: pointer;
}

.radio-row input[type=radio] {
    margin: 0;
    min-height: 0;
    padding: 0;
    cursor: pointer;
}

button, .btn {
    padding: 10px 20px;
    min-height: 42px;
    border: 1px solid var(--border);
    border-radius: 8px;
    background: #fff;
    color: var(--text);
    font: inherit;
    font-size: 14px;
    cursor: pointer;
    white-space: nowrap;
}

button:hover, .btn:hover { background: var(--bg); text-decoration: none; }
button:disabled { opacity: .45; cursor: not-allowed; }

button.primary { background: var(--accent); border-color: var(--accent); color: #fff; }
button.primary:hover:not(:disabled) { background: var(--accent-hover); }

button.danger { color: var(--danger); border-color: #fecaca; }
button.danger:hover { background: #fef2f2; }

/* 弹框里的主按钮更大一些 —— 这是用户要点的那个 */
.dialog-actions button { padding: 10px 22px; }

/* ---------- 行内与侧栏的小按钮 ---------- */

/* 表格每行有 5-6 个按钮，按主按钮的尺寸会把行撑爆，所以单独收小。
   但仍然要让它们好点 —— 之前 3px/12px 太小了。 */
/* 每行有 5-6 个操作，全用图标。这里只管布局，尺寸交给 .icon-btn。
   ⚠️ 不要在这里写 `.row-actions button { padding: ... }` —— 那个选择器（0,1,1）
   优先级高于 .icon-btn（0,1,0），会把图标按钮撑成文字按钮的尺寸。踩过一次。 */
/* 操作按钮**靠右对齐**，不留空位。
 *
 * 为什么不是留空槽位：那会在一排图标中间插出个洞，比错位还难看。
 * 靠右对齐的效果是：最右边那几个（记录、删除）位置永远固定，
 * 不管这行有几个按钮 —— 而它们恰好是最需要"闭着眼也能点中"的那几个。
 * 左边的变动（解绑有没有、停用还是取消停用）不影响右手边。
 *
 * .slot 用 display:contents 让包裹它的 span 不生成盒子，
 * 于是空槽位自然消失、里面的按钮直接参与父级 flex 布局。 */
.row-actions {
    display: flex;
    justify-content: flex-end;
    gap: 2px;
}

.slot {
    display: contents;
}

/* 表头也要靠右，否则"操作"两个字和按钮不在同一边 */
th.col-actions {
    text-align: right;
}

/* ---------- 提示条 ---------- */

/* 生成激活码之后那条提示。刻意做成细细一条、而且能关掉 ——
   之前是个大 panel 杵在列表上面，生成完就赖着不走，只能刷新页面才消失。
   码本身现在就在下面的列表里，每行后面有小复制按钮。 */
.genbar {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 7px 12px;
    margin-bottom: 12px;
    border: 1px solid #bbf7d0;
    background: #f0fdf4;
    color: var(--ok);
    border-radius: var(--radius);
    font-size: 13px;
}

.genbar > span { flex: 1; }

.genbar button {
    min-height: 0;
    padding: 3px 10px;
    font-size: 12px;
    background: #fff;
    border: 1px solid #bbf7d0;
    color: var(--ok);
    border-radius: 6px;
    cursor: pointer;
}

.genbar button:hover { background: #dcfce7; }

/* 激活码后面那个小复制按钮。平时很淡，鼠标移到那一行才明显 ——
   它太显眼的话，一屏二十行全是按钮，反而看不清码本身。 */
.copy-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 20px;
    height: 20px;
    margin-left: 5px;
    padding: 0;
    min-height: 0;
    vertical-align: -3px;
    border: 1px solid transparent;
    border-radius: 4px;
    background: transparent;
    color: var(--muted);
    opacity: .55;
    cursor: pointer;
}

tr:hover .copy-btn { opacity: 1; }

.copy-btn:hover {
    background: var(--bg);
    border-color: var(--border);
    color: var(--text);
}

/* 复制成功那一下的反馈。就地变色，不弹窗。 */
.copy-btn.done {
    opacity: 1;
    color: var(--ok);
    background: #f0fdf4;
    border-color: #bbf7d0;
}

.alert {
    padding: 10px 14px;
    border-radius: var(--radius);
    margin-bottom: 14px;
    border: 1px solid;
    font-size: 13px;
}

.alert.ok  { background: #f0fdf4; border-color: #bbf7d0; color: var(--ok); }
.alert.err { background: #fef2f2; border-color: #fecaca; color: var(--danger); }

/* ---------- 浮动提示 ---------- */

/* 后台里的提示做成浮在顶部中间的 toast，3 秒后自动淡出。
   放在文档流里的话它会一直杵在那儿 —— 用户以为"这提示怎么关不掉"，
   实际只是没有东西把它收走。 */
.alert.toast {
    position: fixed;
    top: 70px;
    left: 50%;
    transform: translateX(-50%);
    margin: 0;
    z-index: 200;
    min-width: 240px;
    max-width: 60vw;
    text-align: center;
    box-shadow: 0 10px 30px rgba(15, 23, 42, .16);
    animation: toast-in .16s ease-out;
}

.alert.toast.hiding {
    opacity: 0;
    transform: translate(-50%, -8px);
    transition: opacity .32s ease, transform .32s ease;
}

@keyframes toast-in {
    from { opacity: 0; transform: translate(-50%, -12px); }
    to   { opacity: 1; transform: translate(-50%, 0); }
}

/* ---------- 生成的码 ---------- */

.codes {
    background: #0f172a;
    color: #a5f3fc;
    padding: 14px;
    border-radius: var(--radius);
    font-family: "Cascadia Mono", Consolas, monospace;
    font-size: 13px;
    line-height: 1.9;
    white-space: pre;
    overflow-x: auto;
    margin-bottom: 10px;
}

/* ---------- 分页 ---------- */

.pager {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 14px;
    margin-top: 16px;
    font-size: 13px;
    color: var(--muted);
}

.pager-total b { color: var(--text); font-weight: 600; }

/* 分页里的控件不用主表单那种 42px 的大尺寸，会把这行撑得很高 */
.pager-size {
    /* ⚠️ 必须清掉 margin-bottom。全局的 `label { margin-bottom: 6px }`
       本来是给表单里"标签在上、输入框在下"那种排版用的。
       这里 label 是横排的一整块，那 6px 会被 align-items:center 算进居中，
       导致整个"每页 [20] 条"比左边的"共 N 条"高 3px。 */
    display: inline-flex;
    align-items: center;
    gap: 6px;
    margin-bottom: 0;
    color: var(--muted);
}

.pager-size select {
    width: auto;
    min-height: 30px;
    padding: 3px 8px;
    font-size: 13px;
}

.pager-pages {
    display: flex;
    align-items: center;
    gap: 4px;
}

.page-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 30px;
    height: 30px;
    padding: 0 7px;
    border: 1px solid var(--border);
    border-radius: 6px;
    background: #fff;
    color: var(--text);
    font-size: 13px;
    text-decoration: none;
    cursor: pointer;
    user-select: none;
}

a.page-btn:hover {
    border-color: var(--accent);
    color: var(--accent);
    text-decoration: none;
}

/* 当前页：实心高亮 */
.page-btn.current {
    background: var(--accent);
    border-color: var(--accent);
    color: #fff;
    font-weight: 500;
}

/* 到头了：灰掉。用 span 而不是 a，点了不会有反应也不该有 */
.page-btn.disabled {
    color: #cbd5e1;
    background: #fafbfc;
    cursor: not-allowed;
}

.page-ellipsis { padding: 0 2px; color: var(--muted); }

.pager-jump {
    display: flex;
    align-items: center;
    gap: 6px;
    margin-left: auto;
}

.pager-jump input[type=number] {
    width: 62px;
    min-height: 30px;
    padding: 3px 8px;
    font-size: 13px;
    text-align: center;
}

.pager-jump button {
    padding: 4px 12px;
    min-height: 30px;
    font-size: 13px;
    border-radius: 6px;
}

/* ---------- 弹框 ---------- */

dialog {
    border: none;
    border-radius: 12px;
    padding: 22px;
    width: 440px;
    max-width: calc(100vw - 40px);
    box-shadow: 0 20px 50px rgba(0, 0, 0, .18);
    color: var(--text);
}

dialog.wide { width: 820px; }
dialog::backdrop { background: rgba(15, 23, 42, .42); }

.dialog-title { margin: 0 0 16px; font-size: 15px; font-weight: 600; }

.dialog-actions {
    display: flex;
    justify-content: flex-end;
    gap: 8px;
    margin-top: 20px;
}

.quick { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; }
.quick button { padding: 6px 13px; min-height: 0; font-size: 13px; }

/* ---------- 登录页 ---------- */

.login-wrap { min-height: 100vh; display: flex; align-items: center; justify-content: center; }

.login-box {
    background: var(--panel);
    border: 1px solid var(--border);
    border-radius: 12px;
    padding: 32px;
    width: 340px;
}

.login-box h1 { margin: 0 0 24px; font-size: 18px; text-align: center; }
.login-box input { width: 100%; margin-bottom: 14px; }
.login-box button { width: 100%; }

/* ---------- 杂项 ---------- */

.muted { color: var(--muted); }
.mono { font-family: "Cascadia Mono", Consolas, monospace; font-size: 12.5px; }
.empty { text-align: center; color: var(--muted); padding: 36px 0; }
.err { color: var(--danger); }
