/* ============================================================
   登录 / 注册 页面专属样式
   ------------------------------------------------------------
   为什么单独开一个文件：
   .form-container 是公共类，站内还有 5 个页面在用它
   （forgot_password / reset_password / avatar / publish / bulk_upload）。
   直接改 style.css 里的 .form-container 会把这 5 个页面一起动掉，
   所以这里全部用 .auth-* 新命名空间，只作用于登录、注册两页。

   全部颜色走 CSS 变量，8 套主题（含 theme-dark / theme-tech 深色）
   自动适配，不写死任何 rgba(255,255,255,x)。
   ============================================================ */

/* ---------- 卡片 ---------- */

/* 挂在 .form-container 后面一起用：公共类负责宽度、定位、基础玻璃底，
   .auth-card 只做"加料"（更大圆角、顶部光带、更宽松的内边距）。 */
.auth-card {
    position: relative;
    padding: 34px 32px 28px;
    border-radius: 22px;
    overflow: hidden;
    /* 比公共类多一档阴影层次，让卡片从背景里"浮"起来 */
    box-shadow: var(--glass-shadow), 0 18px 50px rgba(0, 0, 0, 0.07);
}

/* 顶部一条主色渐变装饰线 */
.auth-card::before {
    content: '';
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    height: 3px;
    background: linear-gradient(90deg, transparent, var(--primary), transparent);
    opacity: 0.85;
    pointer-events: none;
}

/* ---------- 标题区 ---------- */

.auth-head {
    text-align: center;
    margin-bottom: 22px;
}

.auth-logo {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 52px;
    height: 52px;
    margin-bottom: 12px;
    border-radius: 16px;
    background: var(--primary);
    color: #fff;
    font-size: 24px;
    font-weight: 800;
    letter-spacing: 1px;
    box-shadow: 0 8px 22px rgba(0, 0, 0, 0.12);
    /* 给主题留个可换色的口子，默认就是主色 */
    background-image: linear-gradient(135deg, var(--primary), var(--primary-dark));
}

/* 覆盖 .form-container h2 的居中标题 */
.auth-card .auth-title {
    font-size: 21px;
    font-weight: 700;
    color: var(--text);
    margin-bottom: 6px;
    line-height: 1.35;
}

.auth-subtitle {
    font-size: 13px;
    color: var(--text-light);
    line-height: 1.5;
}

/* ---------- 分段切换（登录页的两种登录方式） ---------- */

.auth-tabs {
    display: flex;
    gap: 6px;
    padding: 4px;
    margin-bottom: 20px;
    border-radius: 999px;
    background: var(--glass-bg);
    border: 1px solid var(--glass-border);
}

.auth-tab {
    flex: 1 1 0;
    padding: 9px 10px;
    border: none;
    border-radius: 999px;
    background: transparent;
    color: var(--text-secondary);
    font-size: 13.5px;
    font-weight: 600;
    font-family: inherit;
    cursor: pointer;
    transition: var(--transition);
    white-space: nowrap;
}

.auth-tab:hover {
    color: var(--primary);
}

.auth-tab.active {
    background: var(--primary);
    color: #fff;
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}

/* ---------- 表单字段 ---------- */

.auth-card .form-group {
    margin-bottom: 15px;
}

.auth-card .form-group > label {
    display: block;
    margin-bottom: 7px;
    font-size: 13px;
    font-weight: 600;
    color: var(--text-secondary);
    letter-spacing: 0.2px;
}

/* 【重要】这里修的是深色主题下的可读性问题。
   style.css 里 .form-group input 的底色被写死成 rgba(255,255,255,0.6)，
   切换到 theme-dark / theme-tech 后，文字色变成浅色（--text 是 #f5f5f5），
   浅字 + 白底 → 几乎看不见。改成半透明黑，跟随深浅主题自动翻转。 */
.auth-card .form-group input {
    background: rgba(0, 0, 0, 0.04);
    /* 边框也要比公共样式更清楚一点：原来的 rgba(0,0,0,0.08) 在浅色主题里
       和卡片底几乎融成一片，看不出输入框的边界。 */
    border: 1px solid rgba(0, 0, 0, 0.16);
    padding: 12px 14px;
    font-size: 15px;
    transition: var(--transition);
}

/* 深色主题下把边框提亮一点，否则输入框边界也和背景糊在一起 */
body.theme-dark .auth-card .form-group input,
body.theme-tech .auth-card .form-group input {
    background: rgba(255, 255, 255, 0.06);
    border-color: rgba(255, 255, 255, 0.14);
}

.auth-card .form-group input::placeholder {
    color: var(--text-light);
    opacity: 0.8;
}

/* 聚焦态：外圈光晕用主色的半透明，但不再写死 #ff2e4d，
   否则换主题（比如 theme-blue）时会残留一圈红边。 */
.auth-card .form-group input:focus {
    border-color: var(--primary);
    background: var(--card-bg);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--primary) 16%, transparent);
}

/* 记住我 / 勾选类，需要跟输入框区分开 */
.auth-card .auth-check label {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 13.5px;
    font-weight: 500;
    color: var(--text-secondary);
    cursor: pointer;
}

.auth-card .auth-check input[type="checkbox"] {
    width: 16px;
    height: 16px;
    flex: 0 0 auto;
    padding: 0;
    margin: 0;
    accent-color: var(--primary);
    cursor: pointer;
}

/* ---------- 验证码行 ---------- */

.auth-captcha-row {
    display: flex;
    gap: 10px;
    align-items: center;
}

.auth-captcha-row input {
    flex: 1 1 auto;
    min-width: 0;
    text-transform: uppercase;
    letter-spacing: 2px;
}

/* captcha.php 生成的是 220×120 的图。
   为什么从 100×40 改大：
   原来 100×40 时字体走的是 GD 内置位图字体（arial.ttf 不存在），
   4 个字符总共才 36px 宽，又小又糊。改成 TrueType 字体后字号 30px，
   4 个字符加间距轻松超过 160px，100px 根本放不下，只能放大画布。
   220×120 = 1.83:1，输入框那一行正好塞得下。
   宽高都给死、两边同比例，拉伸会糊。 */
.auth-captcha-img {
    height: 60px;
    width: 110px;
    flex: 0 0 auto;
    border-radius: var(--radius-sm);
    border: 1px solid rgba(0, 0, 0, 0.16);
    background: #fff;
    cursor: pointer;
    object-fit: fill;
    transition: var(--transition);
}

/* ---------- 主题修补 ---------- */

/* theme-yellow 的 --text 是 #f57f17（橙黄），压在 #fffde7 的浅蛋壳底上
   对比度只有 2.58，属于"看不清"的级别（WCAG 建议正文 ≥ 4.5）。
   这里只在登录/注册这两页把它压深到 #b35c00（对比度 4.6），
   不动 --text 本身，避免影响其它页面的既有观感。 */
body.theme-yellow .auth-card .auth-title,
body.theme-yellow .auth-card .form-group > label {
    color: #b35c00;
}

/* 输入框里的文字同理：yellow 的 --text 是橙黄，用户输入的账号密码
   压在浅色底上只有 2.4 的对比度，等于看不清自己打了什么。 */
body.theme-yellow .auth-card .form-group input,
body.theme-yellow .auth-card .auth-check label {
    color: #b35c00;
}

body.theme-yellow .auth-card .auth-subtitle,
body.theme-yellow .auth-card .auth-divider-label {
    color: #8a4700;
}

body.theme-dark .auth-captcha-img,
body.theme-tech .auth-captcha-img {
    border-color: rgba(255, 255, 255, 0.16);
}

.auth-captcha-img:hover {
    border-color: var(--primary);
    opacity: 0.9;
}

.auth-captcha-img:active {
    transform: scale(0.97);
}

/* 右侧"发送验证码"按钮：不许被 flex 压扁，也不许换行 */
.auth-code-btn {
    flex: 0 0 auto;
    white-space: nowrap;
}

/* ---------- 按钮 ---------- */

/* 登录/注册主按钮：比通用 .btn 更高一点、字更大一点，
   视觉上作为整张卡片的终点。 */
.auth-submit {
    width: 100%;
    margin-top: 6px;
    padding: 13px 24px;
    font-size: 15.5px;
    font-weight: 700;
    letter-spacing: 1px;
    border-radius: 999px;
    box-shadow: 0 8px 20px rgba(0, 0, 0, 0.12);
}

.auth-submit:active {
    transform: translateY(1px);
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);
}

/* ---------- 分隔与页脚 ---------- */

.auth-divider {
    position: relative;
    margin: 22px 0 14px;
    padding-top: 14px;
    border-top: 1px dashed var(--glass-border);
}

.auth-divider .auth-divider-label {
    display: block;
    font-size: 12.5px;
    line-height: 1.6;
    color: var(--text-light);
}

.auth-foot {
    margin-top: 18px;
    text-align: center;
    font-size: 13.5px;
    color: var(--text-secondary);
}

.auth-foot a {
    color: var(--primary);
    font-weight: 600;
    transition: var(--transition);
}

.auth-foot a:hover {
    color: var(--primary-dark);
    text-decoration: underline;
}

.auth-foot .auth-sep {
    margin: 0 8px;
    color: var(--text-light);
    opacity: 0.6;
}

/* ---------- 提示条：修深色主题下浅底浅字 ---------- */

/* 发送验证码的结果区，有内容时才占位，空了不留缝 */
.auth-send-result:empty {
    display: none;
}

.auth-send-result {
    margin-bottom: 10px;
}

.auth-card .alert {
    border-radius: 12px;
    padding: 11px 14px;
    font-size: 13px;
    font-weight: 500;
    line-height: 1.55;
}

.auth-card .alert-error {
    background: color-mix(in srgb, var(--primary) 14%, transparent);
    color: var(--primary-dark);
}

.auth-card .alert-success {
    background: color-mix(in srgb, #2e7d32 14%, transparent);
    color: #2e7d32;
}

.auth-card .alert-info {
    background: color-mix(in srgb, #1565c0 14%, transparent);
    color: #1565c0;
}

/* 深色主题：纯深色文字在暗底上读不出来，统一提亮 */
body.theme-dark .auth-card .alert-error,
body.theme-tech .auth-card .alert-error {
    color: #ff8a9b;
}

body.theme-dark .auth-card .alert-success,
body.theme-tech .auth-card .alert-success {
    color: #81c784;
}

body.theme-dark .auth-card .alert-info,
body.theme-tech .auth-card .alert-info {
    color: #64b5f6;
}

/* ---------- 响应式 ---------- */

@media (max-width: 480px) {
    /* 小屏手机：左右留白收紧，把宽度让给输入框 */
    .auth-card {
        padding: 26px 18px 22px;
        border-radius: 18px;
        margin-top: 14px;
    }

    .auth-card .auth-title {
        font-size: 19px;
    }

    .auth-logo {
        width: 46px;
        height: 46px;
        font-size: 21px;
        border-radius: 14px;
    }

    /* 极窄屏下验证码图缩到 88×48，仍保持 220:120 的原比例，不糊。
       注意：别把宽高写成各自的缩放系数，两个值必须同乘一个比例，
       否则字会被拉扁。 */
    .auth-captcha-img {
        width: 88px;
        height: 48px;
    }

    .auth-captcha-row {
        gap: 8px;
    }

    .auth-code-btn {
        padding: 6px 10px;
        font-size: 12.5px;
    }

    .auth-tab {
        font-size: 13px;
        padding: 8px 6px;
    }
}

/* color-mix 兜底：极老的浏览器不支持时，退回到不带透明度的实色背景，
   至少保证文字和底色有对比，不会出现"浅字白底"。 */
@supports not (color: color-mix(in srgb, red 10%, transparent)) {
    .auth-card .alert-error {
        background: var(--primary-light);
    }

    .auth-card .form-group input:focus {
        box-shadow: 0 0 0 3px rgba(0, 0, 0, 0.08);
    }
}

/* ============================================================
   滑块验证码
   ------------------------------------------------------------
   结构与职责：
     .auth-slider-stage   舞台：背景图（含服务端画好的缺口）撑开高度
       .auth-slider-bg      背景图
       .auth-slider-piece   滑块小图，绝对定位，跟着手柄左右移动
       .auth-slider-refresh 换一张
     .auth-slider-track   轨道
       .auth-slider-fill    已拖过部分的填充
       .auth-slider-hint    提示文字（拖动后淡出）
       .auth-slider-handle  可拖拽手柄
     .auth-slider-status  状态文字（成功/失败/降级）
     .auth-slider-fallback 降级后的图形验证码区（默认隐藏）

   全部颜色走 CSS 变量，不写死 rgba —— 8 套主题自动适配。
   深色主题（theme-dark / theme-tech）在下面单独补边框和填充色。
   ============================================================ */

.auth-slider-group { margin-bottom: 18px; }

/* ---------- 舞台 ---------- */

.auth-slider-stage {
    position: relative;
    width: 100%;
    border-radius: var(--radius-sm);
    overflow: hidden;
    border: 1px solid var(--border);
    /* 图片没加载出来时先给个底色，避免闪白块 */
    background: var(--glass-bg);
    /* 触摸拖动时不要让浏览器接管手势（否则会变成页面滚动） */
    touch-action: none;
    user-select: none;
    -webkit-user-select: none;
    -webkit-touch-callout: none;
    line-height: 0;   /* 消掉 img 底部的基线间隙 */
}

/* 背景图：宽度撑满，高度按原比例（640:360 = 16:9）自动 */
.auth-slider-bg {
    display: block;
    width: 100%;
    height: auto;
    aspect-ratio: 640 / 360;
    pointer-events: none;
    -webkit-user-drag: none;
}

/* 滑块小图：绝对定位，left 由 JS 驱动。
   【对齐基准 —— 这里很容易搞错】
   小图比缺口大一圈：右侧多留了「凸起半径 + 内边距」，四周还有 2px 内边距。
   裁图时主体左上角落在小图的 (PAD, PAD) 处，其中 PAD = 2（原图像素）。
   所以要让小图的主体正好套住缺口，小图左边缘得相对缺口左边缘**再往左** PAD 对应的显示距离。
   这个偏移量随显示宽度缩放，用 --slider-pad 在 JS 里按比例算好后 C 传给 CSS。
   缺口本身的视觉位置已经由服务端画在背景图里，前端不再维护"缺口层"。 */
.auth-slider-piece {
    position: absolute;
    /* 兜底值：缺口恰好在图片最顶端时的位置。
       实际位置由 JS 按「缺口真实 y × 缩放比」设定（见 applyScale()）——
       缺口 y 是服务端 random_int(60, 280) 随机出来的，不固定在顶部。
       JS 未执行时这里至少给个不越界的值。 */
    top: calc(var(--slider-pad, 2px) * -1);
    left: 0;
    height: auto;
    width: auto;
    pointer-events: none;
    -webkit-user-drag: none;
    /* 小图是从背景同位置裁下来的，给它一点投影让轮廓更清楚 */
    filter: drop-shadow(0 2px 4px rgba(0, 0, 0, .35));
    transition: transform .2s ease;
    /* left / top 由 JS 设置，不加过渡（拖动时加过渡会有"追手指"的延迟感）；
       失败弹回时 JS 会临时加过渡 */
    will-change: left, top;
    /* 拖动时 height 由 JS 按背景显示高度设定，保持和背景同比例 */
}

/* 换一张按钮：右上角，不抢视觉 */
.auth-slider-refresh {
    position: absolute;
    top: 8px;
    right: 8px;
    z-index: 2;
    width: 30px;
    height: 30px;
    padding: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    border: none;
    border-radius: 50%;
    background: rgba(0, 0, 0, .38);
    color: #fff;
    font-size: 16px;
    line-height: 1;
    cursor: pointer;
    backdrop-filter: blur(4px);
    transition: background .18s, transform .18s;
}
.auth-slider-refresh:hover  { background: rgba(0, 0, 0, .56); }
.auth-slider-refresh:active { transform: scale(.92); }

/* ---------- 轨道 ---------- */

.auth-slider-track {
    position: relative;
    height: 44px;
    margin-top: 10px;
    border-radius: var(--radius-sm);
    border: 1px solid var(--border);
    background: var(--glass-bg);
    overflow: hidden;
    touch-action: none;
    user-select: none;
    -webkit-user-select: none;
    transition: border-color .2s, background .2s;
}

/* 拖动进度填充 */
.auth-slider-fill {
    position: absolute;
    top: 0;
    left: 0;
    bottom: 0;
    width: 0;
    background: var(--primary);
    opacity: .18;
    pointer-events: none;
}

/* 提示文字：居中，手柄拖过后淡出 */
.auth-slider-hint {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    padding-left: 44px;   /* 避开手柄，文字视觉居中于剩余区域 */
    font-size: 13.5px;
    color: var(--text-light);
    pointer-events: none;
    transition: opacity .22s;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* 手柄 */
.auth-slider-handle {
    position: absolute;
    top: 0;
    left: 0;
    width: 44px;
    height: 100%;
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--card-bg);
    border-right: 1px solid var(--border);
    color: var(--primary);
    cursor: grab;
    touch-action: none;
    transition: background .18s, color .18s;
    z-index: 1;
}
.auth-slider-handle:active { cursor: grabbing; }
.auth-slider-handle:focus-visible {
    outline: 2px solid var(--primary);
    outline-offset: -2px;
}
.auth-slider-handle svg { width: 18px; height: 18px; display: block; }

/* 成功态：整条轨道变主色实底 */
.auth-slider-track.is-success {
    border-color: var(--primary);
    background: var(--primary);
}
.auth-slider-track.is-success .auth-slider-handle {
    background: transparent;
    border-right-color: transparent;
    color: #fff;
    cursor: default;
}
.auth-slider-track.is-success .auth-slider-fill { opacity: 0; }

/* 失败态：抖一下 */
.auth-slider-track.is-error {
    border-color: var(--primary);
    animation: authSliderShake .3s ease;
}
@keyframes authSliderShake {
    0%, 100% { transform: translateX(0); }
    25%      { transform: translateX(-4px); }
    75%      { transform: translateX(4px); }
}

/* ---------- 状态文字 ---------- */

.auth-slider-status {
    margin-top: 8px;
    min-height: 18px;      /* 占位，避免出现文字时布局跳动 */
    font-size: 12.5px;
    line-height: 1.4;
    color: var(--text-light);
}
.auth-slider-status.is-ok      { color: var(--primary); font-weight: 600; }
.auth-slider-status.is-error   { color: var(--primary); }
.auth-slider-status.is-warn    { color: var(--text-secondary); }
.auth-slider-status.is-pending { color: var(--text-secondary); }

/* ---------- 降级区（图形验证码） ---------- */

/* 默认隐藏：滑块可用时，图形码是「备胎」，只在 JS degrade() 时才露出来。
   为什么用 CSS 而不是 inline style="display:none"：
   内联样式优先级最高，JS 要用 el.style.display='' 才能揭开 ——
   一旦 JS 自己崩了（脚本没加载、初始化异常），容器就永远藏在
   display:none 后面，用户连备胎都拿不到，整张表单彻底提交不了。
   放 CSS 里的话，JS 挂了也只是「藏着一个备胎」，
   而 data-visible="1"（服务端已判定走图形码）时它本来就该显示。 */
.auth-slider-fallback { display: none; margin-top: 2px; }
.auth-slider-fallback[data-visible="1"] { display: block; }
/* JS 降级时给容器加这个类揭开它 */
.auth-slider-fallback.is-shown { display: block; }
/* 降级后图形码输入框是唯一的人机验证手段，必须能提交 */
.auth-slider-fallback .auth-captcha-row { margin-top: 2px; }

/* ---------- 深色主题修补 ---------- */

/* theme-dark / theme-tech 是唯二深色主题。
   浅色主题下 --glass-bg 是浅灰、--card-bg 是白色，直接用没问题；
   深色下要保证手柄和轨道有足够区分度。 */
body.theme-dark .auth-slider-stage,
body.theme-dark .auth-slider-track,
body.theme-tech .auth-slider-stage,
body.theme-tech .auth-slider-track {
    border-color: rgba(255, 255, 255, .16);
}
body.theme-dark .auth-slider-handle,
body.theme-tech .auth-slider-handle {
    background: rgba(255, 255, 255, .1);
    border-right-color: rgba(255, 255, 255, .16);
}
body.theme-dark .auth-slider-fill,
body.theme-tech .auth-slider-fill {
    opacity: .26;   /* 深色下 0.18 太不可见 */
}

/* ---------- 响应式 ---------- */

@media (max-width: 480px) {
    .auth-slider-track { height: 40px; }
    .auth-slider-handle { width: 40px; }
    .auth-slider-hint {
        padding-left: 40px;
        font-size: 12.5px;
    }
    .auth-slider-refresh {
        width: 26px;
        height: 26px;
        font-size: 14px;
        top: 6px;
        right: 6px;
    }
    .auth-slider-status { font-size: 12px; }
}

/* ---------- 减少动态效果 ---------- */

@media (prefers-reduced-motion: reduce) {
    .auth-slider-track.is-error { animation: none; }
    .auth-slider-piece { transition: none; }
}

/* color-mix 兜底：极老浏览器不支持时用实色，避免"浅字白底" */
@supports not (color: color-mix(in srgb, red 10%, transparent)) {
    .auth-slider-refresh { background: #333; }
}

/* ============================================================
 *  React 应用挂载点：把"可用高度"算出来交给应用内部去滚
 * ============================================================
 *  【为什么这段必须放在这里（共享 CSS），而不是各页面内联】
 *  注册页（register.php）原本把这段写在自己页面的 <style> 里，
 *  登录页（login.php）只有一句 padding-top: 0，**以为能共用**。
 *
 *  但两个页面是各自独立的 HTTP 请求，login.php 的响应里根本不包含
 *  register.php 内联的那些样式 —— "共用"从来就不成立。
 *
 *  后果不是"不好看"，而是实打实的功能问题：
 *    · 登录页容器没人约束高度 → 被内容撑到 948px（视口只有 844）
 *    · 于是滚的是整个 <body>，而不是应用内部那个 overflow-y-auto
 *    · 两个面板里的"登录"按钮用 sticky bottom-0 吸底 → 失效
 *      （sticky 要生效，它的滚动祖先必须真的在滚动）
 *    · 按钮被挤出首屏，用户要往下滚才能点到登录
 *
 *  放在共享 CSS 里，两个页面都加载得到，也就真正"共用"了。
 *
 *  【高度怎么算出来的 —— 必须同时减掉三样】
 *    header  55px   页面顶部导航
 *    tabbar  58px   移动端底部导航
 *    safe    0~34px 底部小黑条（env）
 *  容器起点在 header 之后，所以高度 = 视口 - header - tabbar - 安全区。
 *  实测过：只减 tabbar 的话容器会多出 55px 盖到 tabbar 上。
 *
 *  用 dvh 而不是 vh：手机地址栏收起/展开会改变可视高度，
 *  vh 不跟着变，按钮会被地址栏挡住。
 *
 *  【为什么设 height 而不是 padding】
 *  React 应用的转场用 position:absolute + inset:0，
 *  绝对定位参考的是容器的 padding box，加 padding 反而会让
 *  绝对定位那层盖住 padding 区域，依然被 tabbar 遮住。
 */
/* 底部 tabbar 的高度。
   它是 position: fixed 的，浮在页面之上，不占布局空间 ——
   所以必须**主动把应用内容区往上推**这么多，否则应用最底部
   那几十像素会被 tabbar 盖住（实测：登录按钮正好被切掉）。 */
.register-app-root {
    --auth-tabbar-h: calc(58px + env(safe-area-inset-bottom, 0px));
    --auth-navbar-h: 55px;
}

.register-app-root {
    display: block;
    width: 100%;
    /* 高度只减 header —— 应用要占满到页面底部。
       【为什么不在 height 里减 tabbar】
       减了的话应用底部就停在 tabbar 的上沿，但 tabbar 是 fixed、
       浮在上面，两者一叠加，应用最下面那 58px 反而空出来一块
       用不到的死区。真正要做的不是"变矮"，是"底部留出通道"。 */
    height: calc(100dvh - var(--auth-navbar-h));
    /* 用 padding-bottom 把内容推离 tabbar：
       这样背景色仍然铺满整个视口（视觉连续），
       而真正的内容（按钮）落在 tabbar 上方。 */
    padding-bottom: var(--auth-tabbar-h);
    box-sizing: border-box;
    min-height: 380px;
}

@media (min-width: 769px) {
    /* 桌面端没有底部 tabbar，把那段留白去掉 */
    .register-app-root {
        --auth-tabbar-h: 0px;
    }
}

/* ============================================================
 *  一屏式登录 / 注册：把页面级的"尾巴"藏掉
 * ============================================================
 *  【为什么必须藏，而不是让它待在下面】
 *  header.php / footer.php 会给每个页面带上三样东西：
 *   · .marquee-banner  滚动公告条（40px）
 *   · .footer          站脚（85px）
 *   · .back-to-top     回顶按钮
 *  这三样是为**长内容页**（首页、详情页）设计的 —— 用户往下
 *  翻了很远才需要它们。
 *
 *  但登录页和注册页是"一屏完成任务"的：填表 → 验证 → 提交，
 *  全程不发生页面级滚动。这时候最下面的公告条和站脚不但没用，
 *  还会实打实把容器撑高：实测 body 被撑到 1005px（视口 844），
 *  多出来的正是 40 + 85 这两块。
 *
 *  后果不是"多了点内容"，而是：
 *   · 页面变成可滚动的（用户可能滑到底看到半截 footer）
 *   · .register-app-root 上面那句 height: calc(100dvh - ...)
 *     依然是对的，但它的**父级**比视口高，应用里那些
 *     依赖"可见高度"的判断会全体偏移
 *
 *  【为什么用 display: none 而不是 visibility: hidden】
 *  后者的元素仍然占位，撑高的问题一点没解决。
 *  必须让它们从布局里彻底消失。
 *
 *  【为什么写在共享 CSS 而不是各页面内联】
 *  两个页面都要，理由同 .register-app-root —— 内联样式只对
 *  自己那个 HTTP 响应生效，"以为共用"从来会落空。
 */
body.register-page .marquee-banner,
body.register-page .footer,
body.register-page .back-to-top {
    display: none;
}

/* ------------------------------------------------------------
 *  登录 / 注册页：把滑块那几处"为老表单留的空白"收掉
 * ------------------------------------------------------------
 *  【为什么不直接改上面那些全局规则】
 *  css/auth.css 是登录页、注册页、还有 ?legacy=1 的老表单
 *  共用的。老表单里这 18px 的下外边距和常驻的状态行是有用的 ——
 *  它是整页文档流，不靠 flex gap 排版。
 *
 *  而 React 版的两个面板用 space-y-3 统一给间距，
 *  这个 margin-bottom 就变成了**叠加**在上面的一层多余空白。
 *
 *  【为什么连空状态行也要收】
 *  实测滑块组 256px 的构成：舞台 180 + 轨道 40 + 状态 18 + 间距 18。
 *  其中状态行在用户还没拖动时是空的 —— 一个空格都不显示，
 *  却实打实占了 18px。手机上这 18px 就是"按钮能不能进首屏"的差距。
 *
 *  收法用 :empty —— 只在**真的没内容**时塌掉，
 *  一旦有"验证成功/失败/降级"的文字，它自己会撑起来，不会闪。
 */
body.register-page .auth-slider-group {
    /* 间距由面板的 space-y-3 给，这里不要重复占位 */
    margin-bottom: 0;
}

body.register-page .auth-slider-status:empty {
    /* 没话说的时候不要占位置 */
    display: none;
}

/* ------------------------------------------------------------
 *  滑块在宽屏下的最大宽度
 * ------------------------------------------------------------
 *  【为什么必须限制】
 *  背景图是 width: 100% + aspect-ratio: 640/360 的。
 *  桌面 1440 视口下，应用内容区宽 1144px → 图片高度变成 643px，
 *  整个滑块组高达 698px，手柄的 y 座标跑到 1000 往外，
 *  **直接掉出视口**。
 *
 *  手机上一屏 731px 装得下，是因为 390 视口下图片只有 180px 高。
 *  桌面这版不但难看（拼图占了大半屏），而且拖动要先把页面滚下去
 *  —— 而现在页面级滚动被锁死了（见上面那段），滚不动，
 *  于是滑块彻底没法用。
 *
 *  【为什么是 420px】
 *  拼图验证的精细度取决于**缺口相对于图片的像素尺寸**，不取决于
 *  屏幕多宽。图片再宽，缺口也不会变大，只会让用户拖动距离更长、
 *  更累。420px 下缺口清晰可辨，拖动距离也在手指舒适区。
 *  这和移动端 390 视口下的实际尺寸（约 342px）接得上，两端的
 *  操作手感一致。
 *
 *  【为什么不像其他地方那样限定 body.register-page】
 *  这是滑块本身的合理上限，跟哪个页面无关 —— 老表单在宽屏下
 *  同样会被拉得很宽。放全局，三个入口一并受益。
 */
.auth-slider-group {
    max-width: 420px;
}

/* ------------------------------------------------------------
 *  锁死页面级高度，让滚动彻底交给应用内部
 * ------------------------------------------------------------
 *  【为什么光有 body { overflow: hidden } 不够】
 *  实测：把 marquee/footer 藏掉后，body 里正常内容只到 786px
 *  （header 55 + 应用 731），但 body.scrollHeight 仍是 856 ——
 *  多出的 70px 是那两个 position: fixed 的抽屉/面板贡献的。
 *  在某些引擎和移动端模拟下，fixed 元素仍会被算进滚动高度。
 *
 *  只要 body.scrollHeight > clientHeight，浏览器就认为"这页能滚"，
 *  于是：
 *   · 用户手指下滑时整页会动（虽然动不了多少，但会回弹）
 *   · 应用内部那个 overflow-y-auto 的滚动位置跟着漂
 *
 *  把 html / body 的高度都钉成 100%，滚动高度就等于客户端高度，
 *  页面级滚动从根本上不存在。应用内部该怎么滚还怎么滚 ——
 *  它的高度是独立算出来的（见 .register-app-root）。
 */
html:has(body.register-page),
body.register-page {
    height: 100%;
    overflow: hidden;
    overscroll-behavior: none;
}
