/* ==========================================================================
   无限画布 · 前端样式
   站点：jerryai.top     文件：/assets/wxhb/app.css

   两套主题：
     · :root                     —— 深色（默认），画布是深灰网格底，作品最亮
     · html[data-theme="light"]  —— 浅色，白底 + 柔和阴影

   主题由 index.html 里的首屏脚本决定（读 localStorage），app.js 只负责切换时写属性。
   所有颜色都必须走变量 —— 页面里再出现写死的色值，深色下就会露馅。
   ========================================================================== */

:root {
    /* ---- 深色（默认）=========== 「暗色科幻」皮肤（2026-10-16 第六轮改版）============
       ⚠️ 这一套是**整块换掉**的，不是微调：旧版深色是「深灰底 + 照片 + 蓝 accent」，
       新版是「纯黑底 + 三团霓虹 + 88px 网格 + 白玻璃 + 粉 accent」。
       对齐物是一套外部参考站的登录页（风格参考，不写站名），用户点名要这个"暗色科幻风"。

       三条不能破的规矩：
         ① 底色必须是**纯黑系**（#030306），不能是深灰 —— 霓虹光要压在黑上才发光。
         ② 卡片一律**白色低透明度**（white .055 ~ .09），不再用"深灰实块"：
            深灰块压在黑底上等于隐身，白玻璃才分得出层次。
         ③ accent 换**粉色系**（对应参考站 pink-300 的 focus 环 / 粉色辉光），
            旧的蓝 #3b82f6 跟暖色霓虹打架，会显得脏。
       浅色主题（html[data-theme="light"]）**一个字都没动**，仍是照片 + 毛玻璃。 */
    --bg: #030306;                          /* 深色兜底色：纯黑系，不能再是深灰 */
    /* 画布底不再自己铺色 —— 改由 .workspace 那层玻璃负责。 */
    --canvas-bg: transparent;
    --grid-dot: rgba(255, 255, 255, .09);
    /* ⚠️ 整站「透明感」的总开关：卡片 / 面板全走 var(--panel)，一透一起透。 */
    --panel: rgba(255, 255, 255, .055);
    --panel-2: rgba(255, 255, 255, .085);
    --surface-3: rgba(255, 255, 255, .10);
    --hover: rgba(255, 255, 255, .12);
    --float-bg: var(--glass-bg);
    --input-bg: rgba(0, 0, 0, .28);
    --border: rgba(255, 255, 255, .10);
    --border-strong: rgba(255, 255, 255, .17);
    --text: #f4f4f7;
    /* 底现在是纯黑 + 霓虹，不再是照片 —— 灰字可以比上一版收敛一点，
       但仍然不能压太暗：玻璃层后面是"会发光"的底，太暗的灰字会被辉光吃掉。 */
    --text-2: #cbccd5;
    --muted: #9b9dab;
    /* 气泡提示（工具条 hover 弹出的那条）必须跟正文反着来：深色主题下浅底深字。
       ⚠️ 这两行是**独立变量**，别写成复用 --text / --canvas-bg ——
          --canvas-bg 现在是 transparent（画布底交给 .workspace 那层玻璃了），
          拿来当前景色会让提示文字整个透明掉（2026-10-15 就是这么踩的）。 */
    --tip-bg: #f3f5fa;
    --tip-fg: #171a21;
    /* 缩放手柄那块底：深色主题下反而要**提亮**（压在深色卡片上才分得出前后），
       白折线才是主角 —— 具体见 .cn-resize。 */
    --handle-bg: rgba(255, 255, 255, .18);
    /* 粉色系 accent（对位参考站 pink-300 的 focus 环 + 粉色辉光） */
    --accent: #ec4899;
    --accent-hover: #db2777;
    --accent-soft: rgba(236, 72, 153, .16);
    --ok: #34d399;
    --ok-soft: rgba(52, 211, 153, .16);
    --warn: #fbbf24;
    --warn-soft: rgba(251, 191, 36, .16);
    --danger: #f87171;
    --danger-soft: rgba(248, 113, 113, .16);
    --danger-line: rgba(248, 113, 113, .38);
    /* 卡片也留一点透 —— 只透白 7%，不影响看图，但整页不再是"实心块"。
       ⚠️ 这里**不给卡片加 backdrop-filter**：一屏几十个卡片都做背景模糊
          会直接拖垮画布缩放的帧率。要通透感，用半透明就够了。 */
    --node-bg: rgba(255, 255, 255, .07);
    --node-border: rgba(255, 255, 255, .13);
    --node-head: rgba(255, 255, 255, .06);
    --map-bg: rgba(10, 10, 16, .88);
    --port-bg: #e6e6e6;
    /* 连线得看得见 —— 深色底下太暗的话，"流动的虚线"这个效果等于白做。
       换偏紫的白，才能从粉/青两团霓虹里认出来。 */
    --edge: #8f86b8;
    /* 透明作品的底：不能用纯黑，不然 PNG 的透明区跟"没加载出来"一个样 */
    --media-bg: #0b0b11;
    --checker: rgba(255, 255, 255, .065);
    /* 参数面板里「被选中」的那一格：中性高亮（亮描边 + 加粗白字），不走品牌色 —— 跟参考站一致 */
    --pick-bg: rgba(255, 255, 255, .035);
    --pick-border: rgba(255, 255, 255, .10);
    --pick-text: #9aa1ad;
    --pick-bg-on: rgba(255, 255, 255, .13);
    --pick-border-on: rgba(255, 255, 255, .92);
    --pick-text-on: #ffffff;
    --accent-glow: rgba(236, 72, 153, .5);
    --scroll-thumb: rgba(255, 255, 255, .16);
    --scroll-thumb-hover: rgba(255, 255, 255, .26);
    --auth-glow-1: rgba(236, 72, 153, .16);
    --auth-glow-2: rgba(34, 211, 238, .10);
    /* 登录页背景上的蒙版（注意：是"压一层色"，**不是模糊**）。
       深色主题要压得比浅色重一档，否则登录卡片的白字压在亮背景上读不清。 */
    --auth-bg-veil: rgba(3, 3, 6, .54);
    /* 登录卡片自己的底 —— 比内容页的玻璃（--glass-bg）实一档。
       理由：它背后可能是**任意一张照片**（v28 起后台可配背景图/视频），包括深色照片；
       白的太透会整张糊进背景，边界只剩一圈描边（2026-10-15 用一张深蓝测试图实测到的）。 */
    --auth-card-bg: rgba(10, 10, 18, .62);
    --shadow-sm: 0 1px 2px rgba(0, 0, 0, .40), 0 1px 3px rgba(0, 0, 0, .30);
    --shadow-md: 0 4px 12px rgba(0, 0, 0, .45), 0 1px 3px rgba(0, 0, 0, .30);
    --shadow-lg: 0 18px 48px rgba(0, 0, 0, .62);
    --radius: 10px;
    --radius-lg: 16px;

    /* ---- 霓虹底（暗色科幻的核心）----
       三团径向光，直接照参考站登录页的三团（粉 / 橙 / 青）：
       粉在左上（主色，也是 accent 的来源）、橙在右上、青压在下方中间。
       ⚠️ 别再往这两组里塞第四团光 —— 三团是"有主次"的，加一团立刻糊成彩虹。 */
    --aurora-1: rgba(236, 72, 153, .28);
    --aurora-2: rgba(249, 115, 22, .22);
    --aurora-3: rgba(34, 211, 238, .16);

    /* ---- 整站背景：深色下**没有照片** ----
       旧版深色是"照片当底"，新皮肤要的是纯黑 + 霓虹。照片是浅色主题的观感资产，
       深色下留着只会把霓虹染灰（实测：照片中间调会把粉色辉光压成脏紫）。
       置 none 之后 body::before 那层就是空的，背景全是 body::after 的霓虹 + 网格。 */
    --page-bg-img: none;
    /* 深色不盖蒙版（原来那层黑蒙版是给照片用的），改给一个极深的蓝紫黑当基底 ——
       纯 #030306 会在某些屏幕上显得"闷"，掺一点冷紫立刻有科技感。 */
    --page-bg-veil: #050409;
    /* 整站背景**视频**那层的专属蒙版（v37；v38 起浓度也搬进了后台）。
       深色主题根本不显示视频层（见下面 `html[data-theme="dark"] .site-bg`），
       这里只是兜个值。
       ⚠️ 后台填了「背景视频蒙版浓度」（bg_video_veil）时，app.js 会**直接把颜色
          写到那条 <i class="sb-veil"> 的 style 上** —— 元素内联样式优先级最高，
          盖得过任何主题块。所以这一行只在"没配 / 配了个非法值"时生效，
          改默认值记得和 app.js 里那个 34 一起改。 */
    --page-bg-video-veil: rgba(5, 4, 9, .5);
    /* 墙面网格（参考站那层 88px 方格）。浅色主题置 transparent = 不画。 */
    --page-grid-line: rgba(255, 255, 255, .045);
    --page-grid-size: 88px;
    /* 工作区那层底 —— 比卡片透得多，霓虹 + 网格主要靠它透出来，见「三档透明度」注释。
       比卡片只低一档（.05 vs .055）：深色下给太透的话，"浮动窗格"的边界会整块消失，
       整页糊成一片没有层次。 */
    --pane-bg: rgba(255, 255, 255, .05);
    /* 两套主题的模糊半径必须一致（只改一边 = 硬坑 82 那个坑的翻版） */
    --page-bg-blur: 24px;

    /* ---- 悬浮面板圆角 ---- */
    --pane-radius: 18px;

    /* ---- 玻璃层 ----
       所有浮层 / 面板 / 弹窗统一走这一组，别再各写各的背景色。
       玻璃 = 半透明底 + backdrop-blur + 一道内侧白描边（模拟玻璃边缘的高光）+ 外阴影。
       ⚠️ 只给小面积、数量少的元素用（侧栏、工具条、弹层、弹窗）。
          节点卡片不要用 —— 卡片可能几十上百个，每个都模糊会直接拖垮缩放帧率。 */
    --glass-bg: rgba(255, 255, 255, .06);
    --glass-bg-strong: rgba(255, 255, 255, .09);
    /* 「裸文字托底」—— 专门给那些**没有卡片可依附、直接落在背景上的**文字用：
       空态块（.empty-state）、页头那行小字（.page-head .sub）、会员页底部注释（.glass-note）。
       深色下这一层用**黑**而不是白：白玻璃托底 + 白字 = 越托越糊，黑底才压得住霓虹。
       它比 --glass-bg 实一档：玻璃可以透，字不能透。
       ⚠️ 别拿它去换掉卡片们自己的 --panel —— 那会把整页拉回"实心块"。 */
    --note-bg: rgba(9, 9, 15, .74);
    --glass-border: rgba(255, 255, 255, .12);
    --glass-hair: rgba(255, 255, 255, .09);
    --glass-blur: 26px;
    /* 饱和度只提 125%：深色底下侧栏正压在左上那团粉色霓虹上，
       150% 会把玻璃染成一块相当扎眼的品红板，反而"便宜"了。 */
    --glass-sat: 125%;
    --glass-shadow: 0 12px 36px rgba(0, 0, 0, .58), 0 1px 2px rgba(0, 0, 0, .40);

    --font: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Hiragino Sans GB",
            "Microsoft YaHei", "Helvetica Neue", Arial, sans-serif;
}

html[data-theme="light"] {
    /* 层级同深色：底 → 面板 → 卡片。
       画布底走半透明，好让 body 上的极光透上来（否则玻璃层没东西可透）。 */
    --bg: #eef1f7;                          /* 背景图没加载出来时的兜底色 */
    /* 画布底不再自己铺色 —— 改由 .workspace 那层玻璃负责，
       否则「玻璃叠玻璃」会把背景图糊成一块实色（见文件末尾「悬浮面板」段）。 */
    --canvas-bg: transparent;
    --grid-dot: rgba(24, 32, 51, .16);
    /* ⚠️ 这一行是整站「透明感」的总开关：
       所有卡片 / 面板走的都是 var(--panel)，它一透，全站一起透。
       数值是试出来的：白 > .60 就回到"白块拼盘"，< .35 文字开始压不住背景。 */
    --panel: rgba(255, 255, 255, .52);
    --panel-2: rgba(252, 253, 255, .64);
    --surface-3: rgba(255, 255, 255, .60);
    --hover: rgba(255, 255, 255, .74);
    --float-bg: var(--glass-bg);
    --input-bg: rgba(255, 255, 255, .78);
    /* 工具条 hover 气泡：浅色主题下深底白字（同深色主题那条注释） */
    --tip-bg: #232733;
    --tip-fg: #ffffff;
    --handle-bg: rgba(18, 20, 26, .42);

    --border: #e2e7f0;
    --border-strong: #d0d7e5;
    --text: #1f2430;
    /* ⚠️ 同深色主题那条注释：浅色主题的蒙版只有 .06（几乎等于没压），
       照片是**原样**透上来的。原来那对 #48505f / #7b8496 在纯白底上好看，
       压在照片的中间调上就彻底看不见了 ⇒ 各自再深一档。 */
    --text-2: #414a5a;
    --muted: #5a6474;
    --accent: #4f6ef7;
    --accent-hover: #4059e0;
    --accent-soft: #eef1ff;
    --ok: #12a150;
    --ok-soft: #e8f7ee;
    --warn: #c2740a;
    --warn-soft: #fdf3e2;
    --danger: #dc2626;
    --danger-soft: #fdecec;
    --danger-line: #f3c9c9;
    /* 卡片半透明（同深色那套，只透 14%）：整页不再是"实心白块"，极光能垫在底下 */
    --node-bg: rgba(255, 255, 255, .90);
    --node-border: #e2e7f0;
    --node-head: #f3f6fb;
    --map-bg: #eef1f7;
    --port-bg: #ffffff;
    --edge: #8b94a9;
    --media-bg: #ffffff;
    --checker: rgba(24, 32, 51, .055);
    /* 浅色底下同一套：选中 = 深描边 + 加粗深字 */
    --pick-bg: #ffffff;
    --pick-border: rgba(24, 32, 51, .14);
    --pick-text: #5a6474;
    --pick-bg-on: rgba(24, 32, 51, .05);
    --pick-border-on: rgba(24, 32, 51, .78);
    --pick-text-on: #101724;
    --accent-glow: rgba(79, 110, 247, .34);
    --scroll-thumb: #ccd2de;
    --scroll-thumb-hover: #b5bdcb;
    --auth-glow-1: #e8ecff;
    --auth-glow-2: #e6f6ee;
    /* 登录页背景上的蒙版（浅色版）—— 只把亮部稍微压一点，保证深色文字读得清。
       想更"通透"就往下调；调到 0 也能看，只是背景花的时候文字会飘。 */
    --auth-bg-veil: rgba(250, 251, 255, .14);
    /* 登录卡片自己的底（见深色主题那条注释）。浅色下给白得实一点，
       否则换一张深色背景图，卡片就整张糊进背景里了。 */
    --auth-card-bg: rgba(255, 255, 255, .76);

    /* 极光底（浅色版）：压在背景图之上做色温，浓度比深色稍高一点 */
    --aurora-1: rgba(112, 148, 255, .34);
    --aurora-2: rgba(186, 142, 245, .24);
    --aurora-3: rgba(255, 168, 172, .20);

    /* 整站背景图 + 蒙版（蒙版越浓，照片越淡；太淡则文字对比度不够）
       ⚠️ 浅色蒙版是白的、深色蒙版是黑的，两边力度必须分开调：
          浅色下白蒙版 .24 就已经很"干净"，深色下黑蒙版要 .7 上下才压得住，
          否则白色文字压在中间调的背景上根本读不清。 */
    /* 浅色：内置那张 + 后台可覆盖（--site-bg-img 由 applySiteBg() 写）。
       ⚠️ 这一行**必须留在浅色块里**：深色块把 --page-bg-img 定成 none，
          后台配的图只该在浅色生效（暗色科幻皮肤下铺照片会把霓虹染灰）。
          写成 `var(--site-bg-img, …)` 的好处是主题自己决定用不用，
          JS 那边只写一个 --site-bg-img，不用管当前是哪个主题（切换主题也不用重写）。 */
    --page-bg-img: var(--site-bg-img, url("/assets/wxhb/bg.jpg?v=1"));
    --page-bg-veil: rgba(250, 251, 255, .06);
    /* 整站背景**视频**那层的蒙版（v37）。比 --page-bg-veil(.06) 重得多 ——
       整屏实拍视频的细节比一张静态照片多得多，光靠 blur 压不住，
       要这一层把对比度拉回"文字读得清"的程度。
       嫌背景太花 / 太抢戏：**后台「站点与联系」里有两个旋钮**——
       「背景视频模糊」（bg_video_blur，px）和「背景视频蒙版浓度」（bg_video_veil，%），
       改完 Ctrl+F5 就能看到。下面这个值只在后台没配 / 配了个非法值时生效，
       改动时记得把后台那两项的默认值一起同步。 */
    --page-bg-video-veil: rgba(250, 251, 255, .34);
    --page-bg-blur: 24px;
    /* 墙面网格：浅色主题**不画**（底是一张照片，白格子压上去只有脏，没有科技感）。
       变量在这里显式声明成 transparent，是为了让 body::after 那一层规则的
       结构在两套主题里完全一致 —— 少一个变量就会退回 :root 的白色网格。 */
    --page-grid-line: transparent;
    --page-grid-size: 88px;
    /* 工作区那层底 —— 比卡片透得多，背景图主要靠它透出来，见「三档透明度」注释 */
    --pane-bg: rgba(255, 255, 255, .20);

    /* 玻璃层（浅色版）—— 对齐参考图：白三成半 + 厚模糊，背景图才透得出来。
       .60 以上的白一压，整页立刻退回"白块拼盘"，那就又是"差别好远"了。 */
    --glass-bg: rgba(255, 255, 255, .52);
    --glass-bg-strong: rgba(255, 255, 255, .66);
    /* 裸文字托底（见深色主题那条注释）。浅色下给到 .74 —— 比卡片玻璃实，
       但还留着两成半的照片透过来，不会变成一块白板。 */
    --note-bg: rgba(255, 255, 255, .74);
    --glass-border: rgba(255, 255, 255, .92);
    --glass-hair: rgba(24, 32, 51, .10);
    --glass-blur: 26px;
    --glass-sat: 170%;
    --glass-shadow: 0 12px 36px rgba(24, 32, 51, .12), 0 1px 2px rgba(24, 32, 51, .05);

    --shadow-sm: 0 1px 2px rgba(24, 32, 51, .06), 0 1px 3px rgba(24, 32, 51, .04);
    --shadow-md: 0 4px 12px rgba(24, 32, 51, .08), 0 1px 3px rgba(24, 32, 51, .06);
    --shadow-lg: 0 18px 48px rgba(24, 32, 51, .16);
}

* { box-sizing: border-box; }

html, body {
    height: 100%;
    margin: 0;
    padding: 0;
}

/* ==========================================================================
   整站背景：三层叠出来（2026-10-15 第二轮视觉精修；v37 加了第三层，v38 给它加了模糊）

     ① body::before —— 背景图。向外扩 90px 再重度模糊：
        不往外扩的话，模糊会把四条边"糊秃"，露出底下的兜底色（白边 / 黑边）。
     ② body::after  —— 极光 + 明暗蒙版。让玻璃「有东西可透」，
        同时把照片压到合适的对比度，文字才立得住。
     ③ .site-bg     —— **背景视频**（v37；后台配了地址才由 JS 建出来）。
        夹在 ① 和 ② 之间：盖住图片当主角，又被 ② 的极光 / 蒙版压着。
        模糊程度由后台「背景视频模糊」控制（v38，见下面 .site-bg 那一节）。

   ⚠️ ③ 是真实的 body 子元素，而 ① 是 body 的伪元素 ——
      伪元素永远绘制在真实子元素**之前**，所以同层级下视频天然盖在图上，
      不需要给它额外抬 z-index（两者都待在 -2）。
   ⚠️ 这三层能待在负 z-index，是因为 **html 没有设 background**：
      CSS 背景传播规则会把 body 的 `--bg` 提到 canvas 上、body 自身不再画背景，
      于是负 z-index 层全部露在兜底色之上、#app 之下。
      哪天给 html 加一行 background，这三层会被整片盖死。
   ⚠️ 两套主题的「底」必须一起改（--page-bg-veil / --aurora-*）：
      只改浅色的话，深色下毛玻璃会整个失效。
   ========================================================================== */
body::before {
    content: '';
    position: fixed;
    inset: -90px;
    z-index: -2;
    pointer-events: none;
    background-image: var(--page-bg-img);
    background-size: cover;
    background-position: center;
    background-repeat: no-repeat;
    filter: blur(var(--page-bg-blur)) saturate(115%);
}
/* 暗色科幻皮肤底下**没有照片**（--page-bg-img 是 none）。
   变量已经把图去掉了，但那一层 backdrop 级别的 blur 还挂在那儿白烧 GPU ——
   整屏 filter 是会强制建合成层的，直接 display 掉最干净。 */
html[data-theme="dark"] body::before { display: none; }

/* 整站背景视频层（v37，v38 加模糊）。由 app.js 的 applySiteBg() 建出来 ——
   后台「站点背景视频」留空的话这个元素根本不存在，站上一切照旧走 ① 那张图。
   ⚠️ **轻微模糊是有意的，但可调**：整屏实拍视频的细节比一张静态照片多得多，
      完全不糊会让背景"抢戏"，正文压不住。模糊程度由后台「背景视频模糊」
      （bg_video_blur，默认 8px，0 = 完全不模糊）经 JS 写到元素级变量
      --sb-blur 上；压对比度再叠一层 .sb-veil 蒙版（浓度同样后台可调）。
   ⚠️ 播放由 JS 管（syncSiteBgVideo）：切深色、切窄屏、页面不可见时都会 pause ——
      display:none 的 <video> 并不会自己停下来，照样解码下载。 */
.site-bg {
    position: fixed;
    /* ⚠️ 这里**必须外扩**，跟 ① 那条 `inset: -90px` 是同一个道理：
       视频被 blur 之后，四条边会有一圈羽化的"半透明带"，不扩出去就会露出
       底下的背景图 / 兜底色 —— 看着像四周有一圈脏边。
       扩多少由模糊半径决定（≈4×blur，见 app.js 那边的注释），所以只能用 calc
       跟着 --sb-blur 走；没设置时 = 0，行为跟 v37 的 `inset: 0` 完全一样。 */
    inset: calc(-1 * var(--sb-blur, 0px) * 4);
    z-index: -2;
    pointer-events: none;
    overflow: hidden;
}
.site-bg video {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
    /* 淡彩化 + 模糊（v38）。整屏视频比静态图更需要淡彩化：画面在动，亮部随时
       会飙上来把上面的深色文字吃掉（背景图当初也是先降对比降饱和、才敢整屏铺的，
       同一个道理）。这里没有 LUT 可用，用 CSS filter 等效。
       ⚠️ blur 写在**最前面**、且认元素级变量 --sb-blur（默认 0）：
          那个值是后台「背景视频模糊」下发的，只有 app.js 会写。
          顺序不能乱 —— 先糊再调色，和先调色再糊，观感上没差别，但 blur 放前面
          语义上更清楚：这一条就是"先把画面糊掉，再做淡彩化"。 */
    filter: blur(var(--sb-blur, 0px)) saturate(.86) brightness(.9) contrast(.94);
    /* 就绪前不占画面：① 那张图先顶着，canplay 之后再淡入，避免闪一下黑 */
    opacity: 0;
    transition: opacity .5s ease;
}
.site-bg video.on { opacity: 1; }
.site-bg .sb-veil {
    position: absolute;
    inset: 0;
    background: var(--page-bg-video-veil);
}
/* 暗色科幻皮肤底下跟 ① 同规矩：不铺实拍。视频不铺在那儿更说得过去 ——
   那套皮肤的底是「纯黑 + 霓虹 + 网格」，一段实拍视频压上去会把霓虹染灰。 */
html[data-theme="dark"] .site-bg { display: none; }
/* ≤720px 直接不显示。JS 侧本来就不建这个元素（省流量省电）；
   这条是兜底：窗口从宽拖窄、或走了 JS 没覆盖到的路径时，至少画面是干净的。 */
@media (max-width: 720px) { .site-bg { display: none; } }
/* 用户在系统里开了「减少动态效果」→ 别上视频。纯 CSS 管不了（浏览器会照播），
   但至少在这一档下把它藏掉；JS 侧同样是直接不建元素。 */
@media (prefers-reduced-motion: reduce) { .site-bg { display: none; } }

/* 背景层第二张（霓虹 + 网格 + 基底色）。六层从下往上：基底色 → 三团霓虹 → 两向网格。
   ⚠️ background-size 必须**逐层给**（逗号对应），少给一层会让网格的 88px 尺寸
      错位套到径向光上，把光斑切成碎块。 */
body::after {
    content: '';
    position: fixed;
    inset: 0;
    z-index: -1;
    pointer-events: none;
    background-image:
        linear-gradient(90deg, var(--page-grid-line) 1px, transparent 1px),
        linear-gradient(0deg, var(--page-grid-line) 1px, transparent 1px),
        radial-gradient(1100px 720px at 8% -12%, var(--aurora-1) 0%, transparent 64%),
        radial-gradient(940px 640px at 96% 2%, var(--aurora-2) 0%, transparent 60%),
        radial-gradient(900px 780px at 52% 108%, var(--aurora-3) 0%, transparent 62%);
    background-size:
        var(--page-grid-size) var(--page-grid-size),
        var(--page-grid-size) var(--page-grid-size),
        auto,
        auto,
        auto;
    background-color: var(--page-bg-veil);
}

body {
    font-family: var(--font);
    font-size: 14px;
    line-height: 1.6;
    color: var(--text);
    background: var(--bg);
    -webkit-font-smoothing: antialiased;
    overflow: hidden;
}

.hidden { display: none !important; }

button, input, textarea, select { font-family: inherit; font-size: inherit; color: inherit; }

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

::-webkit-scrollbar { width: 8px; height: 8px; }
::-webkit-scrollbar-thumb { background: var(--scroll-thumb); border-radius: 8px; }
::-webkit-scrollbar-thumb:hover { background: var(--scroll-thumb-hover); }
::-webkit-scrollbar-track { background: transparent; }

/* ==========================================================================
   通用控件
   ========================================================================== */

.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    height: 34px;
    padding: 0 14px;
    border: 1px solid var(--border-strong);
    border-radius: 8px;
    background: var(--panel);
    color: var(--text);
    cursor: pointer;
    white-space: nowrap;
    transition: background .16s, border-color .16s, color .16s, box-shadow .16s;
    user-select: none;
}
.btn:hover:not(:disabled) { background: var(--hover); }
.btn:active:not(:disabled) { transform: translateY(1px); }
.btn:disabled { opacity: .5; cursor: not-allowed; }

.btn-primary {
    background: var(--accent);
    border-color: var(--accent);
    color: #fff;
    font-weight: 500;
}
.btn-primary:hover:not(:disabled) { background: var(--accent-hover); border-color: var(--accent-hover); }

.btn-ghost { border-color: transparent; background: transparent; }
.btn-ghost:hover:not(:disabled) { background: var(--hover); }

.btn-danger { color: var(--danger); border-color: var(--danger-line); background: var(--panel); }
.btn-danger:hover:not(:disabled) { background: var(--danger-soft); }

.btn-sm { height: 28px; padding: 0 10px; font-size: 13px; border-radius: 8px; }
.btn-lg { height: 42px; padding: 0 20px; font-size: 15px; border-radius: 12px; }
.btn-block { width: 100%; }

.field { margin-bottom: 14px; }
.field label {
    display: block;
    margin-bottom: 6px;
    font-size: 13px;
    font-weight: 500;
    color: var(--text-2);
}
.field .hint { margin-top: 6px; font-size: 12px; color: var(--muted); }

.input, .textarea, .select {
    width: 100%;
    padding: 9px 12px;
    border: 1px solid var(--border-strong);
    border-radius: 8px;
    background: var(--input-bg);
    outline: none;
    transition: border-color .16s, box-shadow .16s;
}
.input:focus, .textarea:focus, .select:focus {
    border-color: var(--accent);
    box-shadow: 0 0 0 3px var(--accent-soft);
}
.textarea { resize: vertical; min-height: 84px; line-height: 1.55; }

/* 红字错误提示（邀请码必填等） */
.err-text {
    margin-top: 6px;
    font-size: 12.5px;
    color: var(--danger);
    display: flex;
    align-items: flex-start;
    gap: 4px;
    line-height: 1.5;
}
.err-text::before { content: "!"; font-weight: 700; flex: none; }
.input.is-error, .textarea.is-error { border-color: var(--danger); box-shadow: 0 0 0 3px var(--danger-soft); }

.tag {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    height: 20px;
    padding: 0 8px;
    border-radius: 999px;
    font-size: 12px;
    background: var(--surface-3);
    color: var(--text-2);
}
.tag-ok { background: var(--ok-soft); color: var(--ok); }
.tag-warn { background: var(--warn-soft); color: var(--warn); }
.tag-danger { background: var(--danger-soft); color: var(--danger); }
.tag-accent { background: var(--accent-soft); color: var(--accent); }

.divider { height: 1px; background: var(--border); margin: 18px 0; }

/* ==========================================================================
   登录 / 注册屏
   ========================================================================== */

.auth-screen {
    position: fixed;
    inset: 0;
    /* ⚠️ 这个 z-index 不是"抬高层级"用的，是给内部的 .auth-bg(z-index:-1)
       建一个层叠上下文 —— 父级 z-index 为 auto 时，负 z 子元素会一路沉到页面更底下，
       跑到 body 的背景下面去，整层就看不见了。 */
    z-index: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 24px;
    /* 这层极光底是"没配背景"时的兜底；配了背景会被 .auth-bg 盖住，留着不碍事 */
    background:
        radial-gradient(900px 500px at 12% 0%, var(--auth-glow-1) 0%, rgba(232, 236, 255, 0) 60%),
        radial-gradient(800px 480px at 92% 100%, var(--auth-glow-2) 0%, rgba(230, 246, 238, 0) 62%),
        var(--bg);
    overflow: auto;
}

/* ---------- 登录页背景层（2026-10-15 第三轮） ----------
   和整站背景（body::before，整屏 blur 24px）**故意反着来**：这里不模糊。
   毛玻璃感由登录卡片自己提供 —— 卡片 backdrop-filter 只糊它背后那一小块，
   卡片之外的背景保持清晰，才有"透过一块玻璃看世界"的观感。
   ⚠️ z-index:-1 让它画在 .auth-screen 自己那层极光底**之上**、登录卡片**之下**
      （负 z 子元素排在父级背景之后、普通流内容之前）。
   ⚠️ 用 fixed 而不是 absolute：登录屏是 overflow:auto，小屏内容超出时会滚动，
      absolute 会跟着滚走，fixed 始终钉在视口上。 */
.auth-bg {
    position: fixed;
    inset: 0;
    z-index: -1;
    overflow: hidden;
    background: var(--bg);          /* 图还没到时的兜底，免得白闪一下 */
    pointer-events: none;           /* 纯装饰，别抢输入框 / 按钮的点击 */
}
.auth-bg img,
.auth-bg video {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;              /* 不管什么比例都铺满，不裁变形 */
    object-position: center;
    display: block;
    border: 0;
}
/* 视频盖在垫底图上面，能播了才淡入 —— 视频没下完时先看那张图，不会黑屏或白屏 */
.auth-bg video { opacity: 0; transition: opacity .6s ease; }
.auth-bg video.on { opacity: 1; }
/* 一层极淡蒙版：只用来把背景压到卡片能立住的对比度。
   ⚠️ 这只是"压色"，不是模糊 —— 背景该清楚还是清楚。 */
.auth-bg::after {
    content: '';
    position: absolute;
    inset: 0;
    background: var(--auth-bg-veil);
}

.auth-card {
    width: 100%;
    max-width: 400px;
    background: var(--panel);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-lg);
    padding: 30px 28px 26px;
}

.auth-brand { text-align: center; margin-bottom: 22px; }
.auth-brand .logo {
    width: 46px; height: 46px;
    margin: 0 auto 12px;
    border-radius: 16px;
    background: linear-gradient(135deg, var(--accent), var(--accent-hover));
    color: #fff;
    display: flex; align-items: center; justify-content: center;
    font-size: 22px; font-weight: 700;
    box-shadow: 0 8px 20px rgba(79, 110, 247, .3);
}
.auth-brand h1 { margin: 0; font-size: 20px; font-weight: 600; }
.auth-brand p { margin: 6px 0 0; font-size: 13px; color: var(--muted); }

.auth-tabs {
    display: flex;
    gap: 4px;
    padding: 4px;
    background: var(--surface-3);
    border-radius: 12px;
    margin-bottom: 20px;
}
.auth-tabs button {
    flex: 1;
    height: 34px;
    border: 0;
    border-radius: 8px;
    background: transparent;
    color: var(--text-2);
    cursor: pointer;
    font-weight: 500;
    transition: background .16s, color .16s, box-shadow .16s;
}
.auth-tabs button.active { background: var(--panel); color: var(--text); box-shadow: var(--shadow-sm); }

.auth-foot { margin-top: 18px; font-size: 12.5px; color: var(--muted); text-align: center; line-height: 1.7; }
.invite-note {
    display: flex; gap: 8px;
    padding: 10px 12px;
    border-radius: 12px;
    background: var(--accent-soft);
    color: var(--accent);
    font-size: 12.5px;
    line-height: 1.6;
    margin-bottom: 16px;
}

/* ==========================================================================
   主应用骨架
   ========================================================================== */

#app { height: 100%; display: flex; flex-direction: column; }


/* 「显示模式」小菜单（点站点名右边那个钮弹出来） */
.theme-pop { min-width: 148px; }
.theme-pop button .ic { flex: none; width: 16px; display: flex; align-items: center; justify-content: center; }
.theme-pop button .txt { flex: 1; }
.theme-pop button .ck { flex: none; color: var(--accent); }
.theme-pop button.on { color: var(--text); }

.menu-pop {
    position: fixed;
    z-index: 90;
    min-width: 168px;
    padding: 6px;
    background: var(--float-bg);
    border: 1px solid var(--border);
    border-radius: 12px;
    box-shadow: var(--shadow-lg);
}
.menu-pop button {
    display: flex; align-items: center; gap: 8px;
    width: 100%; height: 32px; padding: 0 10px;
    border: 0; border-radius: 8px;
    background: transparent; text-align: left;
    cursor: pointer; color: var(--text-2);
}
.menu-pop button:hover { background: var(--hover); color: var(--text); }
.menu-pop .sep { height: 1px; background: var(--border); margin: 5px 4px; }

main { flex: 1; position: relative; overflow: hidden; }

.view { position: absolute; inset: 0; overflow: auto; }

/* ==========================================================================
   无限画布
   ========================================================================== */

#view-canvas { overflow: hidden; }

.viewport {
    position: absolute;
    inset: 0;
    overflow: hidden;
    cursor: default;
    background-color: var(--canvas-bg);
    /* 细点阵底：一个径向渐变就能铺出一颗颗小圆点。
       background-size / background-position 由 applyTransform() 按当前缩放和平移实时改，
       所以点阵是「跟着画布走」的 —— 缩放时点距跟着变，视觉上才有拖动的实感。 */
    background-image: radial-gradient(circle, var(--grid-dot) 1.1px, transparent 1.1px);
    background-size: 24px 24px;
    touch-action: none;
}
.viewport.panning { cursor: grabbing; }
.viewport.space-ready { cursor: grab; }
.viewport.dragging-node { cursor: move; }

.world {
    position: absolute;
    top: 0; left: 0;
    transform-origin: 0 0;
    will-change: transform;
}

/* ---------- 画布节点：卡片 = 头部栏 + 内容区（生成节点还多一条底部提示区） ---------- */

.cn {
    position: absolute;
    display: flex;
    flex-direction: column;
    width: 400px;
    height: 380px;
    background: var(--node-bg);
    border: 1px solid var(--node-border);
    border-radius: 12px;
    box-shadow: var(--shadow-md);
    /* 必须 visible：连线口挂在左右边缘外侧、chip 弹层也是挂在节点里的 —— 
       一旦改成 hidden 会连着一起被剪掉。卡片内部的圆角裁切交给子元素各自处理。 */
    overflow: visible;
    transition: border-color .16s, box-shadow .16s;
}
.cn.selected {
    /* 选中时要盖住邻居 —— 参数面板和顶部功能栏都是探出卡片外的 */
    z-index: 20;
    border-color: var(--accent);
    box-shadow: 0 0 0 1px var(--accent), var(--shadow-lg);
}

/* 拖连线时，指针落在哪个节点上哪个就亮起来 —— 给一点「光感」的呼吸描边 */
.cn.hot {
    border-color: var(--accent);
    animation: cn-hot .85s ease-in-out infinite alternate;
}
.cn.hot .port.in { border-color: var(--accent); background: var(--accent); }
@keyframes cn-hot {
    from { box-shadow: 0 0 0 3px var(--accent-soft), 0 0 12px 0 var(--accent-glow), var(--shadow-md); }
    to   { box-shadow: 0 0 0 4px var(--accent-soft), 0 0 30px 3px var(--accent-glow), var(--shadow-md); }
}

/* 连线口：左边进（小圆点）、右边出（蓝色小三角，跟参考站一致） */
.cn .ports { position: absolute; inset: 0; pointer-events: none; z-index: 5; }
.cn .port {
    position: absolute;
    top: 50%;
    pointer-events: auto;
    transition: border-color .16s, background .16s, transform .16s, filter .16s;
}
.cn .port.in {
    left: -6px;
    width: 11px; height: 11px;
    margin-top: -5.5px;
    border-radius: 50%;
    background: var(--port-bg);
    border: 1.5px solid var(--node-border);
}
.cn .port.out {
    right: -9px;
    width: 0; height: 0;
    margin-top: -7px;
    border-left: 9px solid var(--accent);
    border-top: 7px solid transparent;
    border-bottom: 7px solid transparent;
    cursor: crosshair;
}
.cn .port.out:hover { filter: brightness(1.18); transform: scale(1.16); }
.cn.selected .port.in { border-color: var(--accent); }

/* 连线层：和节点同在 world 里，平移缩放自动跟随 */
.edge-layer {
    position: absolute;
    left: 0; top: 0;
    overflow: visible;
    pointer-events: none;
}
/* 连成动态虚线：dashoffset 一直往负方向跑，看起来就是数据在「上游 → 下游」流动。
   注意：renderEdges() 必须复用 path 元素（只改 d），一重建动画就从头开始，会看出跳帧。 */
.edge-line {
    fill: none;
    stroke: var(--edge);
    stroke-width: 1.9;
    stroke-linecap: round;
    stroke-dasharray: 7 6;
    animation: edge-flow 1s linear infinite;
}
@keyframes edge-flow { to { stroke-dashoffset: -13; } }

.edge-temp {
    fill: none;
    stroke: var(--accent);
    stroke-width: 2;
    stroke-linecap: round;
    stroke-dasharray: 6 5;
    animation: edge-flow 1s linear infinite;
}
.edge-hit {
    fill: none;
    stroke: transparent;
    stroke-width: 16;
    pointer-events: stroke;
    cursor: pointer;
}
.edge-hit:hover { stroke: var(--accent-soft); }

/* 鼠标搭在连线上时冒出来的小剪刀 —— 点一下就断开（挂在 .viewport 里，用屏幕坐标） */
.edge-cut {
    position: absolute;
    z-index: 60;
    width: 30px; height: 30px;
    margin: -15px 0 0 -15px;
    display: none;
    align-items: center; justify-content: center;
    padding: 0;
    border: 1px solid var(--border-strong);
    border-radius: 50%;
    background: var(--float-bg);
    color: var(--text-2);
    box-shadow: var(--shadow-md);
    cursor: pointer;
    backdrop-filter: blur(6px);
}
.edge-cut.on { display: flex; }
.edge-cut svg { width: 16px; height: 16px; }
.edge-cut:hover { background: var(--danger); border-color: var(--danger); color: #fff; }

/* ---------- 内容区（图片 / 视频 / 文本 / 音频 / 素材 / 成品都装这里） ---------- */

.cn-body {
    position: relative;
    flex: 1;
    min-height: 0;
    overflow: hidden;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 9px;
    border-radius: 0 0 12px 12px;
    cursor: grab;
    user-select: none;
}
.cn-body:active { cursor: grabbing; }

/* 空态：图标 + 一行字 */
.cn-empty { display: flex; flex-direction: column; align-items: center; gap: 8px; pointer-events: none; }
.cn-empty-ico {
    width: 42px; height: 42px;
    border-radius: 12px;
    display: flex; align-items: center; justify-content: center;
    background: var(--surface-3);
    color: var(--text-2);
}
.cn-empty-ico svg { width: 21px; height: 21px; }
.cn-empty-text { font-size: 12.5px; color: var(--muted); }

/* 虚线上传框（音频节点 / 素材节点的空态） */
.cn-drop {
    position: absolute;
    inset: 9px;
    display: flex; flex-direction: column;
    align-items: center; justify-content: center; gap: 9px;
    border: 1.5px dashed var(--border-strong);
    border-radius: 12px;
    color: var(--muted);
    pointer-events: none;
    transition: border-color .16s;
}
.cn:hover .cn-drop { border-color: var(--accent); }
.cn-drop .plus { width: 34px; height: 34px; display: flex; align-items: center; justify-content: center; color: var(--text-2); }
.cn-drop .plus svg { width: 24px; height: 24px; }
.cn-drop .tip { font-size: 12px; }

/* 作品区 / 素材区的底一律铺棋盘格。
   透明 PNG 铺在纯色（尤其是纯黑）上会被当成"加载失败"的一块黑，棋盘格才看得出来是透明的。 */
.cn-media,
.cn-asset .cn-body {
    background-color: var(--media-bg);
    background-image:
        linear-gradient(45deg, var(--checker) 25%, transparent 25%, transparent 75%, var(--checker) 75%),
        linear-gradient(45deg, var(--checker) 25%, transparent 25%, transparent 75%, var(--checker) 75%);
    background-size: 18px 18px;
    background-position: 0 0, 9px 9px;
}

/* 有内容时：铺满、留边不裁切（生成图和参考图都不能被切掉一块） */
.cn-media { position: absolute; inset: 0; }
.cn-media img, .cn-media video { display: block; width: 100%; height: 100%; object-fit: contain; }
/* 点图不再弹全屏了（要放大看走功能栏那枚「全屏预览」）—— 别给「可点击」的放大镜光标 */
.cn-media img { cursor: default; }

/* 一次出了好几张时，内容区底部那条「‹ 2 / 4 ›」翻页。
   压在图上，所以底色写死半透明黑 —— 深色浅色两套主题下都看得清。 */
.cn-switch {
    position: absolute;
    left: 50%; bottom: 8px;
    transform: translateX(-50%);
    display: flex; align-items: center; gap: 2px;
    padding: 2px;
    border-radius: 999px;
    background: rgba(0, 0, 0, .55);
    backdrop-filter: blur(6px);
    z-index: 3;
}
.cn-switch .sw-btn {
    width: 20px; height: 20px;
    display: flex; align-items: center; justify-content: center;
    border: 0; border-radius: 50%;
    background: transparent; color: rgba(255, 255, 255, .85);
    font-size: 13px; line-height: 1; cursor: pointer;
    transition: background .16s;
}
.cn-switch .sw-btn:hover { background: rgba(255, 255, 255, .18); }
.cn-switch .sw-n { padding: 0 4px; color: rgba(255, 255, 255, .85); font-size: 11px; }

/* ---------- 视频节点上的「就地播放」按钮 + 播放进度条 ----------
   为什么不用原生 controls：一加就把指针事件全吃走，节点拖不动，长相也跟卡片不是一个语言。
   所以自做一个 —— 播放 / 暂停 / 播完归位，就这三件事。
   压在媒体上，底色写死深色半透明（浅色深色两套下都压得住）。
   ⚠️ 这里**不加 backdrop-filter**：视频节点可能在画布上摆一堆，
      每个都做一次背景模糊是白烧帧率（这条跟硬坑 87 里"卡片不给 blur"同一个道理）。 */
.cn-play {
    position: absolute;
    left: 50%; top: 50%;
    transform: translate(-50%, -50%);
    width: 46px; height: 46px;
    padding: 0;
    display: flex; align-items: center; justify-content: center;
    border: 1px solid rgba(255, 255, 255, .5);
    border-radius: 50%;
    background: rgba(16, 18, 24, .58);
    color: #fff;
    cursor: pointer;
    z-index: 4;
    box-shadow: 0 6px 18px rgba(0, 0, 0, .38);
    transition: transform .16s, opacity .16s, background .16s;
}
.cn-play svg { width: 20px; height: 20px; margin-left: 2px; }
.cn-play:hover { transform: translate(-50%, -50%) scale(1.07); background: rgba(16, 18, 24, .74); }
/* 播放中把按钮让开（鼠标移回卡片上再淡淡露出来，方便暂停） */
.cn-play.playing { opacity: 0; }
.cn-play.playing svg { margin-left: 0; }
.cn:hover .cn-play.playing, .cn.selected .cn-play.playing { opacity: .42; }

.cn-prog {
    position: absolute;
    left: 0; right: 0; bottom: 0;
    height: 3px;
    background: rgba(255, 255, 255, .22);
    z-index: 4;
    pointer-events: none;
}
.cn-prog i { display: block; height: 100%; width: 0; background: var(--accent); }

/* ---------- 节点头部栏：类型图标 + 名字 + 改名 + 关闭 ---------- */

.cn-head {
    flex: none;
    display: flex;
    align-items: center;
    gap: 7px;
    height: 36px;
    padding: 0 6px 0 10px;
    background: var(--node-head);
    border-bottom: 1px solid var(--node-border);
    border-radius: 12px 12px 0 0;
    cursor: grab;
    user-select: none;
}
.cn-head:active { cursor: grabbing; }
.cn-head .k-ico { flex: none; display: flex; align-items: center; justify-content: center; }
.cn-head .k-ico svg { width: 15px; height: 15px; }
.cn-head .nm {
    flex: 1; min-width: 0;
    font-size: 12.5px; color: var(--text);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    cursor: text;
}
.cn-head .nm input {
    width: 100%; height: 22px;
    padding: 0 4px;
    border: 1px solid var(--accent); border-radius: 8px;
    background: var(--input-bg); color: var(--text);
    font-size: 12.5px; outline: none;
}
.cn-head .hb {
    flex: none;
    width: 24px; height: 24px;
    display: flex; align-items: center; justify-content: center;
    border: 0; border-radius: 8px;
    background: transparent;
    color: var(--muted);
    cursor: pointer;
    opacity: .78;
    transition: background .16s, color .16s, opacity .16s;
}
.cn-head .hb svg { width: 13px; height: 13px; }
.cn-head .hb:hover { background: var(--hover); color: var(--text); opacity: 1; }
.cn-head .hb.danger:hover { color: var(--danger); }

/* 头栏最右边那行参数：尺寸 / 分辨率 / 时长 / 文件大小（内容见 app.js 的 nodeMetaText）。
   摆这儿不压在画面上 —— 参数是"看的时候顺手核对"的信息，压在图里会跟作品抢视线。
   宽度给到 62% 就够：左边名字一般只有三四个字，右边这串最长也就是「1080p · 横屏 · 6s」。 */
.cn-head .nm-meta {
    flex: none;
    max-width: 62%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: 11px;
    color: var(--muted);
    font-variant-numeric: tabular-nums;     /* 数字等宽，几张卡片的尺寸对得齐 */
    padding-left: 2px;
}

/* ---------- 右下角的缩放手柄 ----------
   平时藏着，鼠标搭上卡片或选中时才露出来 —— 每个节点角上都趴一个方块太吵。
   光标 nwse-resize 是"右下角"的通用约定。

   为什么不是两条灰线完事：卡片里铺的是作品（可能是纯白线稿，也可能是深色视频），
   任何单色线条都有被背景吃掉的时候。所以给一个半透明深色底 + 白折线 ——
   压在什么画面上都看得见；选中时底换成品牌蓝。 */
.cn-resize {
    position: absolute;
    right: 0; bottom: 0;
    width: 24px; height: 24px;
    z-index: 6;
    cursor: nwse-resize;
    opacity: 0;
    transition: opacity .16s;
}
.cn-resize::before {
    content: '';
    position: absolute;
    right: 2px; bottom: 2px;
    width: 18px; height: 18px;
    border-radius: 7px;
    background: var(--handle-bg);
    transition: background .16s;
}
.cn-resize::after {
    content: '';
    position: absolute;
    right: 5px; bottom: 5px;
    width: 9px; height: 9px;
    border-right: 1.8px solid #fff;
    border-bottom: 1.8px solid #fff;
    border-radius: 0 0 3px 0;
}
.cn:hover .cn-resize { opacity: .92; }
.cn.selected .cn-resize { opacity: 1; }
.cn.selected .cn-resize::before, .cn-resize:hover::before { background: var(--accent); }
/* 拖手柄的时候整个画布都是缩放光标，不会中途滑出去变成一个箭头 */
.viewport.resizing-node, .viewport.resizing-node * { cursor: nwse-resize !important; }

/* 类型小图标的配色 */
.k-image  { color: var(--accent); }
.k-video  { color: #a78bfa; }
.k-text   { color: #34d399; }
.k-audio  { color: #f59e0b; }
.k-asset  { color: #fbbf24; }
.k-task   { color: var(--warn); }
.k-result { color: var(--ok); }
.k-fail   { color: var(--danger); }
.k-done   { color: var(--ok); }

/* ---------- 输入面板：浮在卡片下方的一块，**选中节点才出现** ----------
   以前是焊在卡片里的下半截，卡片永远鼓着一块输入框。现在收起来，
   不点节点就看不见输入框 / 模型 / 参数，卡片回归纯粹的"作品框"。

   宽度刻意做得**比卡片宽**（左右各探出去 80px）：
   卡片只负责"看作品"，写提示词的地方才是干活的主场，不能比作品框还窄。
   卡片是 overflow:visible 的，探出去的部分不会被剪（见硬坑 20）。 */
.cn-foot {
    position: absolute;
    left: 50%;
    top: calc(100% + 10px);
    width: calc(100% + 180px);
    min-width: 420px;
    transform: translateX(-50%);
    z-index: 24;
    display: none;
    padding: 12px 14px 13px;
    border: 1px solid var(--node-border);
    border-radius: 12px;
    background: var(--float-bg);
    box-shadow: var(--shadow-lg);
    backdrop-filter: blur(10px);
    cursor: default;
}
.cn.selected .cn-foot { display: block; }

/* ---------- 选中时浮在卡片正上方的那条功能栏 ----------
   头栏里那三个小图标按钮全挪到这儿：不选中时卡片上干干净净，
   选中了才把「信息 / 编辑 / 删除 / 该类型专属动作」一次摊开。 */

.cn-float {
    position: absolute;
    left: 50%;
    bottom: calc(100% + 9px);
    transform: translateX(-50%);
    z-index: 26;
    display: none;
    align-items: center;
    gap: 1px;
    padding: 4px;
    border: 1px solid var(--border);
    border-radius: 12px;
    background: var(--float-bg);
    box-shadow: var(--shadow-lg);
    backdrop-filter: blur(10px);
    white-space: nowrap;
}
.cn.selected .cn-float { display: flex; }
.cn-float button {
    display: inline-flex; align-items: center; gap: 6px;
    height: 29px; padding: 0 10px;
    border: 0; border-radius: 8px;
    background: transparent; color: var(--text-2);
    font-size: 12.5px; cursor: pointer;
    white-space: nowrap;
    transition: background .16s, color .16s;
}
.cn-float button:hover { background: var(--hover); color: var(--text); }
.cn-float button.danger:hover { background: var(--danger-soft); color: var(--danger); }
.cn-float button svg { flex: none; width: 15px; height: 15px; }
.cn-float .fsep { flex: none; width: 1px; height: 16px; margin: 0 4px; background: var(--border); }

/* 提示词书写区：给了足够的高度（约 4 行起步），写长稿子不用在一条缝里滚。
   min-height 撑开，max-height 兜住 —— 超过就交给 textarea 自己滚。 */
.cp-write { position: relative; }
.cp-ta {
    display: block;
    width: 100%;
    min-height: 120px;
    max-height: 260px;
    /* 右边留一条，给右上角那个「全屏写」的钮让位，别让字钻到它底下 */
    padding: 0 26px 0 0;
    border: 0;
    background: transparent;
    color: var(--text);
    font-size: 13px;
    line-height: 1.65;
    resize: none;
    outline: none;
}
.cp-ta::placeholder { color: var(--muted); }

/* 右上角「全屏编辑」：写长稿子时把这一个框铺满整屏（见 .fs-* 那套） */
.cp-full {
    position: absolute; right: -2px; top: -2px;
    width: 24px; height: 24px;
    display: flex; align-items: center; justify-content: center;
    border: 0; border-radius: 8px;
    background: transparent; color: var(--muted);
    cursor: pointer;
    opacity: .85;
    transition: background .16s, color .16s, opacity .16s;
}
.cp-full:hover { background: var(--hover); color: var(--accent); opacity: 1; }

/* ---------- 全屏编辑（.cp-full / 文本节点那个钮按出来的） ----------
   是"同一个值的放大视图"，不是另一个副本：打字就实时写回节点，
   所以没有确定 / 取消，关掉就等于写完了 —— 跟小输入框的行为一致，不会丢稿。 */

.fs-mask {
    position: fixed;
    inset: 0;
    z-index: 300;
    display: flex; align-items: center; justify-content: center;
    padding: 34px;
    background: rgba(8, 10, 16, .56);
    backdrop-filter: blur(3px);
}
.fs-card {
    position: relative;
    display: flex; flex-direction: column;
    width: min(1080px, 100%);
    height: min(720px, 100%);
    border: 1px solid var(--node-border);
    border-radius: 16px;
    background: var(--panel);
    box-shadow: var(--shadow-lg);
    overflow: hidden;
}
.fs-head {
    flex: none;
    display: flex; align-items: center; gap: 9px;
    padding: 13px 14px;
    border-bottom: 1px solid var(--border);
}
.fs-head .fs-ico { flex: none; display: flex; color: var(--accent); }
.fs-head .fs-t { font-size: 14px; font-weight: 600; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.fs-head .fs-sp { flex: 1; }
.fs-head .fs-n { flex: none; font-size: 12px; color: var(--muted); }
.fs-x {
    flex: none; width: 28px; height: 28px;
    display: flex; align-items: center; justify-content: center;
    border: 0; border-radius: 8px;
    background: transparent; color: var(--text-2);
    cursor: pointer;
}
.fs-x:hover { background: var(--hover); color: var(--text); }
.fs-ta {
    flex: 1; width: 100%; min-height: 0;
    padding: 20px 22px;
    border: 0; background: transparent;
    color: var(--text);
    font-size: 14.5px; line-height: 1.85;
    resize: none; outline: none;
}
.fs-ta::placeholder { color: var(--muted); }
.fs-foot {
    flex: none;
    display: flex; align-items: center; gap: 10px;
    padding: 11px 14px;
    border-top: 1px solid var(--border);
}
.fs-foot .fs-sp { flex: 1; }
.fs-foot .fs-hint { font-size: 12px; color: var(--muted); }
.fs-btn {
    height: 33px; padding: 0 20px;
    border: 1px solid var(--border); border-radius: 12px;
    background: transparent; color: var(--text-2);
    font-size: 13px; cursor: pointer;
    transition: background .16s, color .16s;
}
.fs-btn:hover { background: var(--hover); color: var(--text); }
.fs-btn.primary { border-color: var(--accent); background: var(--accent); color: #fff; }
.fs-btn.primary:hover { background: var(--accent-hover); color: #fff; }
/* 窄屏就把留白收掉，让编辑区尽量大 */
@media (max-width: 720px) {
    .fs-mask { padding: 10px; }
    .fs-foot .fs-hint { display: none; }
}

.cp-refs { display: flex; flex-wrap: wrap; gap: 6px; }
.cp-refs:empty { display: none; }
.cp-refs:not(:empty) { margin-bottom: 9px; }
.cp-ref {
    position: relative;
    width: 42px; height: 42px;
    border-radius: 8px;
    overflow: hidden;
    border: 1px solid var(--border);
    background-color: var(--media-bg);
    background-image:
        linear-gradient(45deg, var(--checker) 25%, transparent 25%, transparent 75%, var(--checker) 75%),
        linear-gradient(45deg, var(--checker) 25%, transparent 25%, transparent 75%, var(--checker) 75%);
    background-size: 12px 12px;
    background-position: 0 0, 6px 6px;
}
.cp-ref img { display: block; width: 100%; height: 100%; object-fit: cover; }
.cp-ref .x {
    position: absolute; right: 1px; top: 1px;
    width: 15px; height: 15px;
    border: 0; border-radius: 50%;
    background: rgba(0, 0, 0, .62);
    color: #fff; font-size: 10px; line-height: 1;
    cursor: pointer;
    display: flex; align-items: center; justify-content: center;
}

/* ---------- 「@ 引用了谁」的缩略图（紧贴提示词框上方那条） ----------
   跟上面 .cp-refs 是两回事，别混：
     · .cp-refs  = 要**传给上游**的参考图（带 × 能删）
     · .cp-mentions = 画布内的**引用关系**（带 @ 角标、点一下跳到那个节点）
   提示词里只写得出一个「@图片1」的干名字，看不出引的到底是哪张图 —— 这条补这个。
   空的时候整条收起（用 :empty，不用手切 hidden）。 */
.cp-mentions { display: flex; flex-wrap: wrap; gap: 6px; flex: none; }
.cp-mentions:empty { display: none; }
.cp-mentions:not(:empty) { margin-bottom: 9px; }
.cp-mn {
    position: relative;
    width: 40px; height: 40px;
    padding: 0;
    border: 1px solid var(--border);
    border-radius: 12px;
    overflow: hidden;
    background-color: var(--media-bg);
    background-image:
        linear-gradient(45deg, var(--checker) 25%, transparent 25%, transparent 75%, var(--checker) 75%),
        linear-gradient(45deg, var(--checker) 25%, transparent 25%, transparent 75%, var(--checker) 75%);
    background-size: 12px 12px;
    background-position: 0 0, 6px 6px;
    cursor: pointer;
    display: flex; align-items: center; justify-content: center;
}
.cp-mn img { display: block; width: 100%; height: 100%; object-fit: cover; }
.cp-mn .ic { color: var(--text-2); font-size: 15px; }
/* 左上角 @：跟 .cp-ref 的 × 一眼分开 */
.cp-mn::before {
    content: '@';
    position: absolute; left: 0; top: 0;
    padding: 0 3px;
    border-bottom-right-radius: 7px;
    background: var(--accent);
    color: #fff;
    font-size: 9px; line-height: 13px;
    z-index: 2;
}
/* 右下角序号：跟提示词里出现的先后一致 */
.cp-mn .ord {
    position: absolute; right: 0; bottom: 0;
    min-width: 13px; height: 13px;
    padding: 0 2px;
    border-top-left-radius: 7px;
    background: rgba(0, 0, 0, .58);
    color: #fff; font-size: 9px; line-height: 13px;
    text-align: center;
    z-index: 2;
}
.cp-mn:hover { border-color: var(--accent); }
/* 没连线的：文本里 @ 了它，但引用**不会生效**（resolveMentions 只认连过来的上游）。
   以前这种情况完全静默 —— 灰着摆出来，一眼就知道该去接哪根线。 */
.cp-mn.off { opacity: .62; border-style: dashed; border-color: var(--danger); }
.cp-mn.off img { filter: grayscale(1); }
.cp-mn.off::before { background: var(--danger); }

/* 全屏写里的那一条：左右留白跟 .fs-ta 对齐，别缩（外面是 flex 列） */
.fs-card > .cp-mentions { flex: none; padding: 16px 22px 0; }

/* 一排排不下时的兜底：只有模型 chip 会被压缩（名字打省略号），其余一律不缩。
   最外层再兜一道 overflow:hidden —— 无论如何都不许撑破卡片把发送键挤出去。
   （chip-pop / mention-pop 都挂在节点或提示区上，不在这一层里，所以裁不到。） */
.cp-bar { display: flex; align-items: center; gap: 4px; margin-top: 8px; min-width: 0; overflow: hidden; }

.cp-ico {
    flex: none;
    width: 26px; height: 26px;
    display: flex; align-items: center; justify-content: center;
    border: 0; border-radius: 8px;
    background: transparent;
    color: var(--text-2);
    cursor: pointer;
}
.cp-ico:hover { background: var(--hover); color: var(--text); }

.cp-chip {
    flex: none;
    display: inline-flex; align-items: center; gap: 5px;
    min-width: 0;
    height: 28px;
    padding: 0 8px;
    max-width: 200px;
    border: 0; border-radius: 8px;
    background: var(--surface-3);
    color: var(--text);
    font-size: 12.5px;
    cursor: pointer;
    white-space: nowrap;
}
.cp-chip:hover { background: var(--hover); }
.cp-chip svg { flex: none; color: var(--text-2); }
.cp-chip .nm { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cp-chip .pr { flex: none; color: var(--muted); font-size: 12px; }
.cp-chip .caret { flex: none; color: var(--muted); margin-left: -2px; }
/* 模型名长短不可控 → 让它一个人扛全部压缩；参数摘要和费用一个字都不许少 */
.cp-bar .cp-chip.model { flex: 0 1 auto; min-width: 86px; }

.cp-cost {
    flex: none;
    margin-left: auto;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    font-size: 12px; color: var(--muted);
}
/* 有会员档位可看时，价格加一条虚线下划线 —— 暗示「停一下有东西」 */
.cp-cost.has-plans {
    cursor: help;
    text-decoration: underline dotted;
    text-underline-offset: 3px;
}

/* 悬停展开的三档会员价（挂 body + fixed 定位，避免被卡片 overflow 裁掉） */
.cost-tip {
    position: fixed; z-index: 900;
    min-width: 178px; padding: 10px 12px;
    background: var(--panel); border: 1px solid var(--border); border-radius: 12px;
    box-shadow: var(--shadow-lg);
    font-size: 12px; color: var(--text);
    pointer-events: none;
}
.cost-tip .ct-head {
    font-size: 11px; color: var(--muted); margin-bottom: 7px;
}
.cost-tip .ct-row {
    display: flex; align-items: center; gap: 8px;
    padding: 4px 0;
}
.cost-tip .ct-row + .ct-row { border-top: 1px dashed var(--border); }
/* 「会员年卡」4 个字，写死 46px 会被挤成两行 —— 用 nowrap + min-width，
   短名字（如「月卡」）仍与长名字左对齐，价格靠 margin-left:auto 右对齐不受影响。 */
.cost-tip .ct-name { flex: none; min-width: 46px; white-space: nowrap; color: var(--text-2); }
.cost-tip .ct-off { flex: none; font-size: 11px; color: var(--muted); }
.cost-tip .ct-price { margin-left: auto; font-weight: 600; }
.cost-tip .ct-foot {
    margin-top: 7px; padding-top: 7px;
    border-top: 1px solid var(--border);
    font-size: 11px; color: var(--muted);
}

.cp-send {
    flex: none;
    width: 32px; height: 32px;
    margin-left: 4px;
    display: flex; align-items: center; justify-content: center;
    border: 0; border-radius: 50%;
    background: var(--accent);
    color: #fff;
    cursor: pointer;
    transition: transform .16s, background .16s;
}
.cp-send:hover:not(:disabled) { transform: scale(1.07); background: var(--accent-hover); }
.cp-send:disabled { background: var(--surface-3); color: var(--muted); cursor: not-allowed; }

/* ---------- 参数 / 模型弹层（点 chip 展开） ---------- */

.chip-pop {
    position: absolute;
    z-index: 40;
    min-width: 208px;
    max-width: 320px;
    padding: 11px;
    background: var(--float-bg);
    border: 1px solid var(--border);
    border-radius: 12px;
    box-shadow: var(--shadow-lg);
}
.chip-pop .cp-title { font-size: 12px; color: var(--muted); margin-bottom: 8px; }
.chip-pop .cp-row { display: flex; align-items: center; gap: 8px; }
.chip-pop .cp-row + .cp-row { margin-top: 9px; }
.chip-pop .cp-row .pk { flex: none; width: 56px; font-size: 12.5px; color: var(--text-2); }
.chip-pop .cp-row select, .chip-pop .cp-row input[type=range] { flex: 1; min-width: 0; }
.chip-pop .cp-row .pv { flex: none; font-size: 12.5px; color: var(--text); }

.model-opt {
    display: flex; align-items: center; gap: 8px;
    width: 100%;
    padding: 8px 9px;
    border: 0; border-radius: 8px;
    background: transparent;
    color: var(--text);
    font-size: 13px;
    text-align: left;
    cursor: pointer;
}
.model-opt:hover { background: var(--hover); }
.model-opt.active { background: var(--accent-soft); color: var(--accent); }
.model-opt .nm { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.model-opt .pr { font-size: 12px; color: var(--muted); white-space: nowrap; }
.model-opt.active .pr { color: var(--accent); }

/* 模型下拉的两行结构：主行＝模型标识，副行＝前台展示名。
   两行都得能省略号截断 —— 上游的标识动辄二十几个字符。 */
.model-opt .opt-txt {
    flex: 1; min-width: 0;
    display: flex; flex-direction: column; gap: 1px;
}
.model-opt .opt-txt .nm {
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.model-opt .opt-txt .sub {
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    font-size: 11.5px; color: var(--muted);
}
.model-opt.active .opt-txt .sub { color: var(--accent); opacity: .75; }

/* ---------- 双击画布弹出的「要新增的画布节点」菜单 ---------- */
.node-menu {
    position: absolute;
    z-index: 40;
    min-width: 156px;
    padding: 6px;
    background: var(--float-bg);
    border: 1px solid var(--border);
    border-radius: 12px;
    box-shadow: var(--shadow-lg);
    animation: nm-in .12s ease-out;
}
@keyframes nm-in {
    from { opacity: 0; transform: translateY(-4px); }
    to { opacity: 1; transform: none; }
}
.node-menu .nm-item {
    display: flex; align-items: center; gap: 10px;
    width: 100%;
    padding: 7px 9px;
    border: 0; border-radius: 8px;
    background: transparent;
    text-align: left;
    cursor: pointer;
}
.node-menu .nm-item:hover { background: var(--accent-soft); }
.node-menu .nm-ico {
    flex: none;
    width: 28px; height: 28px;
    display: flex; align-items: center; justify-content: center;
    border-radius: 8px;
    background: var(--hover);
    border: 1px solid var(--border);
}
.node-menu .nm-item:hover .nm-ico { background: var(--float-bg); border-color: var(--accent); }
.node-menu .nm-label { font-size: 13px; color: var(--text); white-space: nowrap; }

/* ---------- 文本节点：内容区本身就是输入框 ---------- */
/* position:relative 是给角上那枚「全屏写」当定位基准的 —
   不写的话它会一路挂到 .cn 上，跑到卡片头部去 */
.cn-text .cn-body { position: relative; align-items: stretch; justify-content: flex-start; padding: 0; gap: 0; }
.cn-text .cn-body textarea {
    width: 100%;
    height: 100%;
    padding: 11px 12px;
    border: 0;
    background: transparent;
    color: var(--text);
    font-size: 13px;
    line-height: 1.62;
    resize: none;
    outline: none;
}
.cn-text .cn-body textarea::placeholder { color: var(--muted); }
/* 文本节点也一样：没选中时当"只读文本"看，选中了才让改（readOnly 由 JS 切） */
.cn-text:not(.selected) .cn-body textarea { cursor: default; }

/* 角上那枚「全屏写」：只在选中时露出来（没选中时这块就是一段只读文本，不该有按钮） */
.cn-text .cn-body .cp-full {
    right: 8px; top: 8px;
    background: var(--node-bg);
    opacity: 0; pointer-events: none;
}
.cn-text.selected .cn-body .cp-full { opacity: .85; pointer-events: auto; }
.cn-text.selected .cn-body .cp-full:hover { opacity: 1; }
/* 让第一行的尾巴躲开那枚钮 */
.cn-text.selected .cn-body textarea { padding-right: 38px; }

/* ---------- 素材节点：内容区里就是那张图 ---------- */
.cn-asset .cn-body img { width: 100%; height: 100%; object-fit: contain; cursor: zoom-in; }

/* ---------- 音频节点：上传框 / 迷你播放器 ---------- */
.cn-audio .cn-body { padding: 0; }
.cn-audio .cn-media { background: var(--node-bg); }
.aplayer {
    position: absolute;
    inset: 0;
    display: flex; flex-direction: column;
    align-items: center; justify-content: center;
    gap: 11px;
    padding: 14px;
}
.aplayer .aplay {
    width: 46px; height: 46px;
    display: flex; align-items: center; justify-content: center;
    border: 0; border-radius: 50%;
    background: var(--accent); color: #fff;
    cursor: pointer;
    transition: transform .16s, background .16s;
}
.aplayer .aplay:hover { transform: scale(1.06); background: var(--accent-hover); }
.aplayer .aplay svg { width: 20px; height: 20px; }
.aplayer .aname {
    max-width: 100%;
    font-size: 12px; color: var(--text-2);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.aplayer .abar {
    width: 100%;
    height: 4px; border-radius: 4px;
    background: var(--surface-3);
    overflow: hidden;
}
.aplayer .abar i { display: block; height: 100%; width: 0; background: var(--accent); transition: width .2s linear; }
.aplayer .atime { font-size: 11.5px; color: var(--muted); }

/* ---------- 任务节点（生成中的临时节点） ---------- */
.cn-task .cn-body { cursor: default; padding: 18px; gap: 11px; }
.cn-task .tk-status { font-size: 13px; color: var(--text-2); }
.cn-task .tk-pct { font-size: 12px; color: var(--muted); }
.cn-task .tk-cancel {
    height: 28px; padding: 0 12px;
    border: 1px solid var(--border-strong); border-radius: 8px;
    background: transparent; color: var(--text-2);
    font-size: 12.5px; cursor: pointer;
}
.cn-task .tk-cancel:hover { background: var(--hover); color: var(--text); }

/* ---------- 成品节点（生成出来的图 / 视频） ---------- */
.cn-result .cn-bar {
    position: absolute;
    left: 0; right: 0; bottom: 0;
    display: flex; align-items: center; gap: 8px;
    padding: 7px 10px;
    font-size: 11.5px;
    color: rgba(255, 255, 255, .78);
    background: linear-gradient(to top, rgba(0, 0, 0, .74), transparent);
    opacity: 0;
    transition: opacity .16s;
    pointer-events: none;
}
.cn-result:hover .cn-bar, .cn-result.selected .cn-bar { opacity: 1; }
.cn-result .cn-bar .spacer { flex: 1; }

/* ---------- 提示词里的 @ 引用弹层 ---------- */
.mention-pop {
    position: absolute;
    z-index: 30;
    width: 240px;
    max-height: 208px;
    overflow: auto;
    padding: 5px;
    background: var(--panel);
    border: 1px solid var(--border);
    border-radius: 12px;
    box-shadow: var(--shadow-lg);
}
.mention-pop .mn-item {
    display: flex; align-items: center; gap: 8px;
    width: 100%;
    padding: 6px 8px;
    border: 0; border-radius: 8px;
    background: transparent;
    text-align: left;
    cursor: pointer;
}
.mention-pop .mn-item.active,
.mention-pop .mn-item:hover { background: var(--accent-soft); }
.mention-pop .mn-name { flex: 1; font-size: 12.5px; color: var(--text); }
.mention-pop .mn-kind { font-size: 11.5px; color: var(--muted); }
.mention-pop .mn-empty { padding: 9px 8px; font-size: 12px; color: var(--muted); line-height: 1.5; }
/* 空态里的第二行（「也可以直接上传一张」）——比第一行弱一档，别抢主句 */
.mention-pop .mn-empty span { display: block; margin-top: 3px; font-size: 11.5px; opacity: .82; }

/* 菜单里的分组分隔与标题 */

input[type=range] {
    -webkit-appearance: none; appearance: none;
    width: 100%; height: 4px; border-radius: 4px;
    background: var(--border-strong); outline: none;
}
input[type=range]::-webkit-slider-thumb {
    -webkit-appearance: none; appearance: none;
    width: 14px; height: 14px; border-radius: 50%;
    background: var(--accent); border: 2px solid var(--node-bg);
    box-shadow: 0 1px 4px rgba(0, 0, 0, .3); cursor: pointer;
}
.progress { height: 5px; border-radius: 8px; background: var(--surface-3); overflow: hidden; }
.progress i { display: block; height: 100%; background: var(--accent); transition: width .5s ease; }
.progress.done i { background: var(--ok); }
.progress.fail i { background: var(--danger); }

.err-box {
    padding: 8px 10px;
    border-radius: 8px;
    background: var(--danger-soft);
    color: var(--danger);
    font-size: 12.5px;
    line-height: 1.55;
    word-break: break-word;
}

/* ==========================================================================
   画布外壳浮层：画布名 · 右上角 · 左侧工具条 · 小地图 · 自动布局 · 帮助
   ========================================================================== */

/* ---------- 左上：画布名（可改名） ---------- */
.cv-title {
    position: absolute; left: 14px; top: 14px; z-index: 20;
    display: flex; align-items: center; gap: 7px;
    height: 38px; padding: 0 6px 0 12px;
    background: var(--float-bg);
    border: 1px solid var(--border);
    border-radius: 12px;
    box-shadow: var(--shadow-md);
}
.cv-title .hm { flex: none; display: flex; color: var(--muted); }
.cv-title .hm svg { width: 15px; height: 15px; }
.cv-title .tt {
    font-size: 13.5px; color: var(--text);
    max-width: 240px;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    cursor: text;
}
.cv-title .tt input {
    width: 180px; height: 24px;
    padding: 0 5px;
    border: 1px solid var(--accent); border-radius: 8px;
    background: var(--input-bg); color: var(--text);
    font-size: 13px; outline: none;
}
.cv-title .pen {
    flex: none;
    width: 25px; height: 25px;
    display: flex; align-items: center; justify-content: center;
    border: 0; border-radius: 8px;
    background: transparent; color: var(--muted);
    cursor: pointer;
}
.cv-title .pen:hover { background: var(--hover); color: var(--text); }
.cv-title .pen svg { width: 13px; height: 13px; }

/* ---------- 右上：保存状态胶囊 + 圆钮 ---------- */
.cv-top {
    position: absolute; right: 14px; top: 14px; z-index: 20;
    display: flex; align-items: center; gap: 8px;
}
.cv-pill {
    display: flex; align-items: center; gap: 7px;
    height: 32px; padding: 0 13px;
    background: var(--float-bg);
    border: 1px solid var(--border);
    border-radius: 999px;
    box-shadow: var(--shadow-md);
    font-size: 12.5px; color: var(--muted);
    white-space: nowrap;
}
.cv-pill .dt { width: 6px; height: 6px; border-radius: 50%; background: var(--ok); }
.cv-pill.dirty .dt { background: var(--warn); }
.cv-pill.busy .dt { background: var(--accent); }

.cv-btn {
    flex: none;
    width: 36px; height: 36px;
    display: flex; align-items: center; justify-content: center;
    border: 1px solid var(--border);
    border-radius: 50%;
    background: var(--float-bg);
    color: var(--text-2);
    cursor: pointer;
    box-shadow: var(--shadow-md);
    transition: color .16s, border-color .16s, transform .16s;
}
.cv-btn svg { width: 17px; height: 17px; }
.cv-btn:hover { color: var(--accent); border-color: var(--accent); }
.cv-btn:active { transform: scale(.95); }

/* ---------- 左侧：竖排工具条 ----------
   ⚠️ 定位**不能**写成 `top:50%; translateY(-50%)`（2026-10-15 第九轮修）。
   那是「在整块工作区里居中」；而左下角那块「画布导航」小地图是 absolute 贴底边的，
   工具条一排 13 项 = 493px 高 ⇒ 工作区一矮于 ~945px 两者就撞上：
   笔记本（1366×768、1080p 全屏）上工具条最后那几个按钮直接被小地图盖掉。
   现在改成「居中在**小地图上方那条空带**里」：
     top（让开左上角那枚标题胶囊）+ bottom（让开小地图 212 + 底距 14 + 空隙 10）
     + margin: auto 0 —— 那一段就是可用空带，工具条自己垂直居中。
   ⚠️ 这条只在"装得下"时成立：装不下时会退回 top 对齐继续往下长（照样压到小地图），
      所以下面按视口高度补了两档紧凑布局（见「工具条 · 矮屏折两列」）。 */
.cv-rail {
    position: absolute; left: 14px;
    top: 62px; bottom: 236px;
    height: max-content;
    margin: auto 0;
    z-index: 20;
    display: flex; flex-direction: column; align-items: center;
    gap: 2px;
    padding: 6px;
    background: var(--float-bg);
    border: 1px solid var(--border);
    border-radius: 16px;
    box-shadow: var(--shadow-lg);
}
.cv-rail .rb {
    position: relative;
    width: 38px; height: 38px;
    display: flex; align-items: center; justify-content: center;
    border: 0; border-radius: 12px;
    background: transparent;
    color: var(--text-2);
    cursor: pointer;
    transition: background .16s, color .16s;
}
.cv-rail .rb svg { width: 19px; height: 19px; }
.cv-rail .rb:hover { background: var(--hover); color: var(--text); }
.cv-rail .rb.violet { color: #8b7cf6; }
.cv-rail .rb.violet:hover { background: rgba(139, 124, 246, .15); color: #8b7cf6; }
.cv-rail .rsep { width: 22px; height: 1px; margin: 4px 0; background: var(--border); }
.cv-rail .rpct {
    width: 38px; height: 28px;
    display: flex; align-items: center; justify-content: center;
    border: 0; border-radius: 8px;
    background: transparent; color: var(--muted);
    font-size: 11px; cursor: pointer;
}
.cv-rail .rpct:hover { background: var(--hover); color: var(--text); }

/* 工具条上的气泡提示（鼠标移上去才出现，往右弹） */
.cv-rail .rb::after {
    content: attr(data-tip);
    position: absolute;
    left: calc(100% + 10px);
    top: 50%;
    transform: translateY(-50%) scale(.96);
    padding: 5px 9px;
    border-radius: 8px;
    background: var(--tip-bg);
    color: var(--tip-fg);
    font-size: 11.5px;
    white-space: nowrap;
    opacity: 0;
    pointer-events: none;
    transition: opacity .16s, transform .16s;
    z-index: 30;
}
.cv-rail .rb:hover::after { opacity: 1; transform: translateY(-50%) scale(1); }

/* ---------- 工具条 · 矮屏折两列（2026-10-15 第九轮新增） ----------
   竖排 13 项 = 493px。要让它跟左下角小地图、左上角标题胶囊三者互不打架，
   工作区高度得 ≥ 791px（≈ 浏览器视口 819px）—— 1080p 全屏刚好够，
   1366×768 的笔记本、半屏窗口全都不够。这一档不再让工具条"压上去"，
   改成**折成两列**：按钮不做小（点击面积保住），只是整体宽出一格（52 → 92）。
   两列下 13 项 = 6 行 38px + 一行分隔线 ≈ 332px，981 以下的视口都稳。 */
@media (max-height: 860px) {
    .cv-rail {
        display: grid;
        grid-template-columns: repeat(2, 38px);
        grid-auto-rows: 38px;
        justify-items: center;
        gap: 2px;
    }
    .cv-rail .rb { width: 38px; height: 38px; }
    /* 比例那一格横跨两列：13 项两列排下来最后会剩半格，
       让它横跨正好把那一行填满（顺带点击面积也大一倍）。 */
    .cv-rail .rpct { width: 38px; height: 38px; grid-column: 1 / -1; }
    /* 分隔线横跨两列，否则会缩成半条 */
    .cv-rail .rsep { grid-column: 1 / -1; width: auto; margin: 1px 5px; }
    /* ⚠️ 气泡提示必须改挂**按钮下方**：挂在右边的话，左列按钮的气泡
       正好压在右列按钮上（两列只隔 2px），读不了也点不了。 */
    .cv-rail .rb::after {
        left: 50%; top: calc(100% + 7px);
        transform: translateX(-50%) scale(.96);
    }
    .cv-rail .rb:hover::after { transform: translateX(-50%) scale(1); }
}

/* 再矮一档（视口 ≤ 680px）：两列 38px 也塞不下了（小地图还要留着），
   按钮整体收一档 38 → 32（图标 19 → 17），两列高度降到 ~255px。 */
@media (max-height: 680px) {
    .cv-rail { grid-template-columns: repeat(2, 32px); grid-auto-rows: 32px; }
    .cv-rail .rb { width: 32px; height: 32px; border-radius: 10px; }
    .cv-rail .rb svg { width: 17px; height: 17px; }
    .cv-rail .rpct { width: 32px; height: 32px; font-size: 10.5px; }
}

/* 最矮一档（视口 ≤ 620px）：跟「窄屏」那条同一个道理 —— 小地图整个让位，
   工具条这才用得上整条左边。窗口矮到这个程度，本来就放不下画布 + 面板两块。 */
@media (max-height: 620px) {
    .cv-map { display: none; }
    .cv-map-toggle { display: none; }
    .cv-rail { bottom: 14px; }
}

/* 极限矮（视口 ≤ 480px，比如把浏览器拖成一条）：两列也放不下标题 + 工具条了，
   再折一列成三列（13 项 → 6 行）。这一档本来就快没法用画布了，只求别互相压。 */
@media (max-height: 480px) {
    .cv-rail { grid-template-columns: repeat(3, 32px); }
}

/* ---------- 左下：小地图 ---------- */
.cv-map {
    position: absolute; left: 14px; bottom: 14px; z-index: 20;
    width: 210px;
    background: var(--float-bg);
    border: 1px solid var(--border);
    border-radius: 12px;
    box-shadow: var(--shadow-lg);
    overflow: hidden;
}
.cv-map .mh {
    display: flex; align-items: center; gap: 6px;
    height: 34px; padding: 0 6px 0 11px;
    border-bottom: 1px solid var(--border);
    font-size: 12px; color: var(--text-2);
}
.cv-map .mh .sp { flex: 1; }
.cv-map .mh button {
    width: 24px; height: 24px;
    display: flex; align-items: center; justify-content: center;
    border: 0; border-radius: 8px;
    background: transparent; color: var(--muted);
    cursor: pointer;
}
.cv-map .mh button:hover { background: var(--hover); color: var(--text); }
.cv-map .mh button svg { width: 13px; height: 13px; }
.cv-map .mc {
    position: relative;
    height: 132px;
    margin: 8px 8px 0;
    border: 1px solid var(--border);
    border-radius: 8px;
    background: var(--map-bg);
    overflow: hidden;
    cursor: pointer;
}
.cv-map .mc .mn { position: absolute; z-index: 2; border-radius: 2px; background: var(--border-strong); }
.cv-map .mc .mn.image  { background: var(--accent); }
.cv-map .mc .mn.video  { background: #a78bfa; }
.cv-map .mc .mn.text   { background: #34d399; }
.cv-map .mc .mn.audio  { background: #f59e0b; }
.cv-map .mc .mn.asset  { background: #fbbf24; }
.cv-map .mc .mn.result { background: var(--ok); }
.cv-map .mc .mn.task   { background: var(--warn); }
.cv-map .mc .mv {
    position: absolute;
    z-index: 1;
    border: 1.5px solid var(--accent);
    /* 这里必须用半透明色：浅色主题的 --accent-soft 是不透明的，
       一整块铺上去会把底下所有节点色块盖得一干二净。 */
    background: rgba(79, 110, 247, .12);
    border-radius: 4px;
    pointer-events: none;
}
.cv-map .mf { padding: 8px 11px 10px; font-size: 11.5px; color: var(--muted); }

/* 小地图**收起后**留在左下角的那枚开关 —— 收起不等于"消失"。
   它跟 .cv-map 占的是同一个坑位（left/bottom 一模一样），
   所以展开 / 收起时视觉上不会跳。开合互斥由 JS 管（见 toggleMiniMap）。 */
.cv-map-toggle {
    position: absolute; left: 14px; bottom: 14px; z-index: 20;
    width: 40px; height: 40px;
    display: flex; align-items: center; justify-content: center;
    border: 1px solid var(--glass-hair); border-radius: 12px;
    background: var(--glass-bg); color: var(--text-2);
    box-shadow: inset 0 1px 0 var(--glass-border), var(--glass-shadow);
    -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-sat));
    backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-sat));
    cursor: pointer;
    transition: color .16s, transform .16s;
}
.cv-map-toggle svg { width: 19px; height: 19px; }
.cv-map-toggle:hover { color: var(--accent); transform: translateY(-1px); }
.cv-map-toggle:active { transform: scale(.95); }

/* ---------- 底部中间：自动布局 ---------- */
.cv-bottom {
    position: absolute; left: 50%; bottom: 14px;
    transform: translateX(-50%);
    z-index: 20;
}
.cv-layout {
    display: flex; align-items: stretch;
    height: 34px;
    background: var(--float-bg);
    border: 1px solid var(--border);
    border-radius: 999px;
    box-shadow: var(--shadow-md);
    overflow: hidden;
}
.cv-layout .lb {
    display: flex; align-items: center; gap: 7px;
    padding: 0 12px;
    border: 0;
    background: transparent;
    color: var(--text-2);
    font-size: 12.5px;
    cursor: pointer;
}
.cv-layout .lb:hover { background: var(--hover); color: var(--text); }
.cv-layout .lb svg { width: 15px; height: 15px; }
.cv-layout .caret {
    display: flex; align-items: center; justify-content: center;
    width: 30px;
    border: 0; border-left: 1px solid var(--border);
    background: transparent; color: var(--muted);
    cursor: pointer;
}
.cv-layout .caret:hover { background: var(--hover); color: var(--text); }
.cv-layout .caret svg { width: 12px; height: 12px; }

/* ---------- 右下：帮助 ---------- */
.cv-help {
    position: absolute; right: 14px; bottom: 14px; z-index: 20;
    width: 40px; height: 40px;
    display: flex; align-items: center; justify-content: center;
    border: 0; border-radius: 50%;
    background: var(--accent);
    color: #fff;
    cursor: pointer;
    box-shadow: var(--shadow-lg);
    transition: background .16s, transform .16s;
}
.cv-help:hover { background: var(--accent-hover); }
.cv-help:active { transform: scale(.95); }
.cv-help svg { width: 19px; height: 19px; }

/* ---------- 空画布提示 ---------- */
/* 画布空态：文字是直接压在网格 + 背景图上的，同样要托一层玻璃
   （同 .empty-state，见文件下方那条注释） */
.canvas-empty {
    position: absolute;
    left: 50%; top: 42%;
    transform: translate(-50%, -50%);
    z-index: 5;
    text-align: center;
    color: var(--muted);
    padding: 26px 34px;
    border-radius: var(--radius-lg);
    background: var(--note-bg);
    border: 1px solid var(--glass-hair);
    box-shadow: inset 0 1px 0 var(--glass-border), var(--shadow-md);
    -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-sat));
    backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-sat));
    pointer-events: none;
}
.canvas-empty .big { font-size: 15px; color: var(--text); margin-bottom: 6px; font-weight: 600; }

/* ==========================================================================
   列表页（我的作品 / 会员 / 钱包）
   ========================================================================== */

/* 2026-10-15：上限从 1080 提到 1680。
   原来在宽屏上（1900+）内容被锁在一条居中窄栏里，左右各空出 400 多像素 ——
   就是被「留白太严重」点名的那个大空档。窄屏仍然由 padding 兜着，不会贴边。 */
.page { max-width: 1680px; margin: 0 auto; padding: 26px 26px 60px; }
.page-head { display: flex; align-items: center; gap: 12px; margin-bottom: 20px; }
.page-head h2 { margin: 0; font-size: 19px; font-weight: 600; }
/* 页头这行小字（"当前不是会员" / "共 12 件"）也是**直接落在背景图上**的：
   原来只给一档灰字，照片一提亮就彻底读不清 ⇒ 补一枚淡玻璃药丸托住它。
   `:empty` 那条是给「共 N 件」在列表为空时兜底的 —— 那会儿它是个空 span，
   不藏起来，标题旁边就会多一小坨没字的圆角块。 */
.page-head .sub {
    font-size: 12.5px; color: var(--text-2);
    padding: 4px 11px;
    border-radius: 999px;
    background: var(--note-bg);
    border: 1px solid var(--glass-hair);
    -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-sat));
    backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-sat));
}
.page-head .sub:empty { display: none; }
.page-head .spacer { flex: 1; }

.grid-works {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
    gap: 14px;
}
.work-card {
    background: var(--panel);
    border: 1px solid var(--border);
    border-radius: 12px;
    overflow: hidden;
    box-shadow: var(--shadow-sm);
    transition: box-shadow .16s, transform .16s;
}
.work-card:hover { box-shadow: var(--shadow-md); transform: translateY(-1px); }
.work-card .thumb { position: relative; aspect-ratio: 1 / 1; background: var(--surface-3); overflow: hidden; }
.work-card .thumb img, .work-card .thumb video { width: 100%; height: 100%; object-fit: cover; display: block; }
.work-card .thumb .badge {
    position: absolute; left: 8px; top: 8px;
    padding: 2px 7px; border-radius: 8px;
    background: rgba(0, 0, 0, .6); color: #fff; font-size: 11px;
}
.work-card .meta { padding: 9px 11px; }
.work-card .meta .p {
    font-size: 12.5px; color: var(--text-2);
    display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
    overflow: hidden; min-height: 38px;
}
.work-card .meta .t { margin-top: 5px; font-size: 11.5px; color: var(--muted); display: flex; justify-content: space-between; }

.plan-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); gap: 16px; }
.plan-card {
    position: relative;
    background: var(--panel);
    border: 1px solid var(--border);
    border-radius: 16px;
    padding: 22px 20px;
    box-shadow: var(--shadow-sm);
    transition: transform .18s, border-color .18s, box-shadow .18s;
}
.plan-card.featured { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft), var(--shadow-sm); }
/* 角标（最划算 …）：2026-10-16 从「绝对定位钉右上角」改成**流内小标签**。
   右上角那枚位置让给「选中 ✓」了 —— 两个都钉在同一个角上必然打架。 */
.plan-card .badge {
    display: inline-flex; align-items: center;
    margin: 0 0 10px;
    padding: 2px 9px; border-radius: 999px;
    background: var(--accent); color: #fff; font-size: 11.5px;
}
.plan-card h3 { margin: 0 0 4px; font-size: 16px; }
.plan-card .sub { font-size: 12.5px; color: var(--muted); min-height: 19px; }
.plan-card .price { margin: 14px 0 4px; font-size: 28px; font-weight: 700; letter-spacing: -.5px; }
.plan-card .price small { font-size: 13px; font-weight: 500; color: var(--muted); }
.plan-card .rate { font-size: 13px; color: var(--accent); font-weight: 500; }
.plan-card ul { list-style: none; margin: 16px 0 0; padding: 0; }
.plan-card li { display: flex; gap: 8px; font-size: 13px; color: var(--text-2); padding: 5px 0; }
.plan-card li::before { content: "✓"; color: var(--ok); font-weight: 700; flex: none; }

/* ---- 套餐卡可点选：鼠标搭上去浮一点，选中的那张「跳」起来 ----
   会员中心三张卡 + 提示词反推的档位卡是**同一套** .plan-card，所以这两条写在这儿。
   ⚠️ 选中态的 transform 必须比 hover 那条更靠后（同权重，靠顺序取胜），
      否则鼠标一搭上去，选中的卡会被 hover 规则拉回 -3px、看着像"掉了一下"。 */
.plan-grid .plan-card { cursor: pointer; }
.plan-grid .plan-card:hover { transform: translateY(-3px); }
.plan-grid .plan-card.sel {
    transform: translateY(-8px);
    border-color: var(--accent);
    box-shadow: 0 0 0 3px var(--accent-soft), 0 14px 30px rgba(24, 32, 51, .16);
}

/* ---- 会员页英雄区（版式照参考站的会员中心：居中皇冠 + 主标题 + 副标题） ---- */
.plan-hero { text-align: center; padding: 2px 0 22px; }
.plan-hero .hero-crown {
    width: 46px; height: 46px; margin: 0 auto 14px;
    display: flex; align-items: center; justify-content: center;
    border-radius: 15px; color: var(--accent);
    background: var(--glass-bg);
    border: 1px solid var(--glass-hair);
    box-shadow: inset 0 1px 0 var(--glass-border), var(--glass-shadow);
    -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-sat));
    backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-sat));
}
.plan-hero .hero-crown svg { width: 22px; height: 22px; }
.plan-hero .ht { margin: 0; font-size: 23px; font-weight: 700; letter-spacing: -.5px; color: var(--text); }
.plan-hero .hs { margin: 9px 0 0; font-size: 13px; color: var(--text-2); }

.stat-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 14px; margin-bottom: 22px; }
.stat-box {
    background: var(--panel);
    border: 1px solid var(--border);
    border-radius: 12px;
    padding: 16px 18px;
    box-shadow: var(--shadow-sm);
}
.stat-box .lb { font-size: 12.5px; color: var(--muted); }
.stat-box .vl { margin-top: 4px; font-size: 22px; font-weight: 700; letter-spacing: -.4px; }
.stat-box .vl small { font-size: 13px; font-weight: 500; color: var(--muted); }

.card {
    background: var(--panel);
    border: 1px solid var(--border);
    border-radius: 12px;
    box-shadow: var(--shadow-sm);
}
.card-head { padding: 14px 18px; border-bottom: 1px solid var(--border); font-weight: 600; }
.card-body { padding: 6px 0; }

.ledger-row {
    display: flex; align-items: center; gap: 12px;
    padding: 11px 18px;
    border-bottom: 1px solid var(--surface-3);
    font-size: 13px;
}
.ledger-row:last-child { border-bottom: 0; }
.ledger-row .what { flex: 1; color: var(--text-2); }
.ledger-row .when { font-size: 12px; color: var(--muted); }
.ledger-row .amt { font-weight: 600; min-width: 84px; text-align: right; }
.amt.in { color: var(--ok); }
.amt.out { color: var(--text); }

/* 空态是「裸文字直接落在背景图上」的重灾区（用户截图点名的灵感中心就是它：
   整页一张卡片都没有，两行字就那么浮在模糊照片上）。整块托一层玻璃，
   文字立刻立得住；宽度收一收，别在宽屏上拉成一条横贯全屏的板子。 */
.empty-state {
    padding: 48px 28px;
    text-align: center;
    color: var(--muted);
    max-width: 640px;
    margin: 0 auto;
    background: var(--note-bg);
    border: 1px solid var(--glass-hair);
    border-radius: var(--radius-lg);
    box-shadow: inset 0 1px 0 var(--glass-border), var(--shadow-md);
    -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-sat));
    backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-sat));
}
.empty-state .big { font-size: 15px; color: var(--text); margin-bottom: 6px; font-weight: 600; }

/* 通用「裸文字托底」小卡：会员页底部那两条注释、以及以后任何
   没有卡片依附的说明文字，套这一个类就够了。 */
.glass-note {
    padding: 14px 16px;
    border-radius: 14px;
    background: var(--note-bg);
    border: 1px solid var(--glass-hair);
    box-shadow: inset 0 1px 0 var(--glass-border), var(--shadow-sm);
    -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-sat));
    backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-sat));
}

/* richText() 那个 <b>（原 `**强调**`）—— 加粗的同时把颜色提一档，
   否则在一堆灰字里"加粗了还是灰的"，等于没强调。 */
.glass-note b,
.card-body b,
.empty-state b {
    color: var(--text);
    font-weight: 600;
}

/* ==========================================================================
   Toast / 灯箱 / 弹层
   ========================================================================== */

#toast-host {
    position: fixed;
    left: 50%; top: 18px;
    transform: translateX(-50%);
    z-index: 999;
    display: flex; flex-direction: column; gap: 8px; align-items: center;
    pointer-events: none;
}
.toast {
    padding: 9px 16px;
    border-radius: 12px;
    background: rgba(31, 36, 48, .92);
    color: #fff;
    font-size: 13px;
    box-shadow: var(--shadow-lg);
    animation: toast-in .22s ease;
    max-width: 76vw;
}
.toast.ok { background: rgba(18, 161, 80, .95); }
.toast.err { background: rgba(200, 38, 38, .95); }
.toast.warn { background: rgba(194, 116, 10, .95); }
@keyframes toast-in { from { opacity: 0; transform: translateY(-8px); } to { opacity: 1; transform: none; } }

.lightbox {
    position: fixed; inset: 0; z-index: 900;
    background: rgba(12, 16, 24, .82);
    display: flex; align-items: center; justify-content: center;
    padding: 40px;
    /* 背后的画布 / 列表也糊一层，前景那张图才有"浮在全屏之上"的层次 */
    -webkit-backdrop-filter: blur(8px);
    backdrop-filter: blur(8px);
}
.lightbox img, .lightbox video { max-width: 100%; max-height: 100%; border-radius: 8px; }
.lightbox .close {
    position: absolute; right: 22px; top: 18px;
    width: 38px; height: 38px; border: 0; border-radius: 50%;
    background: rgba(255, 255, 255, .16); color: #fff; font-size: 20px;
    cursor: pointer;
    -webkit-backdrop-filter: blur(10px);
    backdrop-filter: blur(10px);
}
.lightbox .close:hover { background: rgba(255, 255, 255, .28); }
.lightbox .dl {
    position: absolute; right: 70px; top: 18px;
    height: 38px; padding: 0 16px; border: 0; border-radius: 999px;
    background: rgba(255, 255, 255, .16); color: #fff; font-size: 13px;
    cursor: pointer; display: flex; align-items: center; gap: 6px;
    -webkit-backdrop-filter: blur(10px);
    backdrop-filter: blur(10px);
}
.lightbox .dl:hover { background: rgba(255, 255, 255, .28); }
/* 底部那行退出口诀。Esc 是这轮才接上的，不写出来没人会去试。 */
.lightbox .esc {
    position: absolute; left: 50%; bottom: 18px;
    transform: translateX(-50%);
    padding: 6px 14px;
    border-radius: 999px;
    background: rgba(255, 255, 255, .14);
    color: rgba(255, 255, 255, .92);
    font-size: 12.5px; line-height: 1.5; text-align: center;
    max-width: calc(100vw - 40px);
    pointer-events: none;
}

.modal-mask {
    position: fixed; inset: 0; z-index: 800;
    background: rgba(12, 16, 24, .42);
    display: flex; align-items: center; justify-content: center;
    padding: 24px;
}
.modal {
    width: 100%; max-width: 460px;
    background: var(--panel); border-radius: 16px;
    box-shadow: var(--shadow-lg);
    overflow: hidden;
}
.modal-head {
    display: flex; align-items: center; gap: 10px;
    padding: 15px 18px; border-bottom: 1px solid var(--border);
    font-weight: 600;
}
.modal-head .spacer { flex: 1; }
.modal-body { padding: 18px; max-height: 62vh; overflow: auto; }
.modal-foot { display: flex; gap: 10px; padding: 14px 18px; border-top: 1px solid var(--border); }
.modal-foot .btn { flex: 1; }

/* 生成前的时长提醒：主文案 + 本次参数 + 三个按钮（窄屏可换行） */
.dur-tip .dur-tip-main { font-size: 14px; line-height: 1.75; color: var(--text); }
.dur-tip .dur-tip-sub { margin-top: 10px; font-size: 12.5px; color: var(--text-2); }
.dur-tip .dur-tip-note { margin-top: 10px; font-size: 12px; color: var(--muted); }
.dur-tip-foot { flex-wrap: wrap; align-items: center; justify-content: space-between; }
.dur-tip-acts { display: flex; gap: 10px; }
.dur-tip-acts .btn { flex: 0 0 auto; min-width: 104px; }
/* 「今日不再提醒」是一个勾选框，不是第三个按钮 —— 按钮只负责决定动作 */
.dur-tip-chk {
    display: inline-flex; align-items: center; gap: 8px;
    position: relative; cursor: pointer; user-select: none;
    font-size: 13px; color: var(--text-2);
}
.dur-tip-chk input {
    position: absolute; left: 0; top: 0; width: 0; height: 0;
    margin: 0; opacity: 0; pointer-events: none;
}
.dur-tip-chk .box {
    position: relative; flex: none; box-sizing: border-box;
    width: 17px; height: 17px; border-radius: 4px;
    border: 1.5px solid var(--border-strong);
    transition: background .16s, border-color .16s;
}
.dur-tip-chk:hover .box { border-color: var(--accent); }
.dur-tip-chk input:checked + .box { background: var(--accent); border-color: var(--accent); }
.dur-tip-chk input:checked ~ .dur-tip-chk-txt { color: var(--text); }
.dur-tip-chk .box::after {
    content: ''; position: absolute; inset: 0; margin: auto;
    width: 4px; height: 8px; border: solid #fff; border-width: 0 2px 2px 0;
    transform: rotate(45deg) scale(0); transition: transform .16s;
}
.dur-tip-chk input:checked + .box::after { transform: rotate(45deg) scale(1); }
.dur-tip-chk input:focus-visible + .box { box-shadow: 0 0 0 2px var(--accent-soft); }

/* 首屏加载遮罩 */
#boot {
    position: fixed; inset: 0; z-index: 1000;
    display: flex; align-items: center; justify-content: center;
    background: var(--bg);
    color: var(--muted); font-size: 13px;
}
.spinner {
    width: 18px; height: 18px;
    border: 2px solid var(--border-strong); border-top-color: var(--accent);
    border-radius: 50%;
    animation: spin .7s linear infinite;
    margin-right: 10px;
}
@keyframes spin { to { transform: rotate(360deg); } }

/* 首屏连不上服务器（域名被拦 / 断网）——三次自动重连都失败后才出现 */
.boot-offline { max-width: 320px; padding: 0 24px; text-align: center; }
.boot-offline .bo-title { color: var(--text); font-size: 15px; font-weight: 600; margin-bottom: 8px; }
.boot-offline .bo-sub { color: var(--muted); font-size: 13px; line-height: 1.7; margin-bottom: 18px; }
.spin-inline {
    display: inline-block;
    width: 13px; height: 13px;
    border: 2px solid rgba(255, 255, 255, .45);
    border-top-color: #fff;
    border-radius: 50%;
    animation: spin .7s linear infinite;
}

/* ==========================================================================
   格局：左侧功能列表 + 右侧工作区
   ========================================================================== */

/* 原先是纵向 flex（顶栏 + main），现在改成横向（侧栏 + 工作区）。
   2026-10-15：外面留一圈 10px 内边距，让侧栏和工作区变成两块**浮起来的圆角玻璃**，
   中间那道缝透出背景图 —— 参考图那种「浮动窗格」的观感就是这么来的。 */
#app { flex-direction: row; padding: 14px; gap: 12px; }

.sidebar {
    flex: none;
    width: 238px;
    /* 收 / 放的时候把溢出来的文字裁掉，别搭到画布上；
       宽度变化本身是「鼠标移进移出」触发的，加个过渡才不像抽风 */
    overflow: hidden;
    display: flex;
    flex-direction: column;
    background: var(--panel);
    /* 从「贴边的一条右边框」改成「浮起来的圆角面板」（末尾玻璃段会覆盖底色与描边） */
    border: 1px solid var(--border);
    border-radius: var(--pane-radius);
    z-index: 40;
    transition: width .2s cubic-bezier(.4, 0, .2, 1);
}

.sb-head {
    flex: none;
    display: flex; align-items: center; gap: 10px;
    padding: 16px 14px 13px;
    border-bottom: 1px solid var(--border);
}
/* ---------- 站点品牌图：icon（正方形）+ logo（横版字标） ----------
   2026-10-15 第二十九轮。两张都是**设计稿原图**（透明底 PNG），
   所以 CSS 只管尺寸和位置、**不替它们画底** —— 深浅两套主题用的是同一张图，
   图里的颜色不会被主题改掉（品牌色本来就该固定）。
   ⚠️ 图挂了 app.js 的 brandImg() 会摘掉 img 并给容器挂 .is-fallback，
      那时才用品牌色兜底、露出原来那个字。别删这段 —— 否则图标一挂就是一块空白。 */
.sb-head .logo {
    flex: none;
    width: 34px; height: 34px;
    display: grid; place-items: center;
    border-radius: 10px;
    color: #fff; font-size: 14px; font-weight: 700;
}
.sb-head .logo-lg {
    flex: 1; min-width: 0;
    display: grid; place-items: center start;
}
.sb-head .logo > *,
.sb-head .logo-lg > *,
.auth-brand .logo > *,
.ah-logo > * { grid-area: 1 / 1; }
/* ⚠️ 兜底字默认必须是 display:none。
   一开始想靠「img 盖在字上面」来兜底，结果图是**透明底**的 ——
   字从图形的空隙里透出来，浅色下看着就像糊了一行重影（第一版就是这样）。
   所以改成显式切换：图挂了 JS 挂 .is-fallback，那时才把字露出来。 */
.logo-fb { display: none; }
.is-fallback .logo-fb { display: block; }
.logo-img {
    display: block;
    object-fit: contain;
    max-width: 100%; max-height: 100%;
    border-radius: inherit;
}
.sb-head .logo .logo-img { width: 100%; height: 100%; }
/* 横版字标：**按高度定尺**，别再让它撑满宽度。
   实测（2026-10-15 第三十轮）：logo.png 是 690×320，但内容只占 x32~575 / y20~293
   —— 四周有一大片透明留白，所以 `width:100%` 撑满侧栏时 img 高 57px，
   可肉眼看到的两行字才 29px / 14px（第一行「杰瑞Ai」29px 已经比 icon 的 34px 还显眼），
   用户反馈「logo 有点大」。
   ⇒ 定 height 42px：可见内容高 ≈ 36px，跟 icon(34px) 一档；第二行副标题
      「Ai船长邓杰瑞」还剩约 10px，138 分屏 / Retina 下都清楚，1x 屏也不至于糊。
   ⚠️ 别再改回 `width: 100%` —— 那等于把高度绑到侧栏宽度上（窄屏变小、宽屏变大）。 */
.sb-head .logo-lg .logo-img { height: 42px; width: auto; max-width: 100%; }
/* 深色主题：字标那两行是**深棕 + 深蓝**，压在深色底上几乎看不见。
   试过描白晕，结果是字边缘发毛、"Ai船长邓杰瑞"那行糊成一团；
   最后直接反成纯白 —— 深色模式用白色 logo 是惯例，最干净也最好认。
   icon（彩色老鼠）不动，深色下照样清楚。 */
html[data-theme="dark"] .sb-head .logo-lg .logo-img {
    filter: brightness(0) invert(1);
}
/* 只有图片加载失败时才画底 */
.sb-head .logo.is-fallback {
    background: linear-gradient(135deg, var(--accent), var(--accent-hover));
}
.ah-logo.is-fallback {
    background: linear-gradient(135deg, var(--accent), var(--accent-hover));
}
/* 字标挂了就退回站点名，按正文色排一行 */
.sb-head .logo-lg.is-fallback {
    font-size: 15px; font-weight: 600; color: var(--text);
}
.sb-head .tt { min-width: 0; flex: 1; }
.sb-head .nm { font-size: 15px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; letter-spacing: .2px; }
.sb-head .sb2 { font-size: 11.5px; color: var(--muted); margin-top: 2px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* 站点名右边的「显示模式」钮 */
.sb-theme {
    flex: none;
    width: 31px; height: 31px; padding: 0;
    display: flex; align-items: center; justify-content: center;
    border: 1px solid var(--border); border-radius: 12px;
    background: transparent; color: var(--text-2);
    cursor: pointer;
    transition: background .16s, color .16s, border-color .16s;
}
.sb-theme:hover { background: var(--hover); color: var(--text); border-color: var(--border-strong); }
.sb-theme svg { display: block; }

.sb-body { flex: 1; overflow-y: auto; overscroll-behavior: contain; padding: 6px 11px 12px; }

.sb-group { margin-top: 18px; }
.sb-group:first-child { margin-top: 4px; }
.sb-group > .gt {
    font-size: 11.5px; color: var(--muted);
    padding: 0 11px 7px; letter-spacing: .4px;
}

.sb-item {
    display: flex; align-items: center; gap: 11px;
    width: 100%; height: 42px; padding: 0 11px;
    border: 0; border-radius: 12px;
    background: transparent; color: var(--text-2);
    font-size: 14px; text-align: left; cursor: pointer;
    transition: background .16s, color .16s;
}
.sb-item svg { flex: none; width: 18px; height: 18px; opacity: .8; }
.sb-item .txt { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* 名字是用 display:none 切的，展开时是"啪"一下蹦出来。
   试过用 animation 淡入 —— 不行：动画时间线一旦不推进（无头截图、极端省电模式都算），
   fill-mode:both 会把 opacity 钉在 0，菜单上只剩图标没有字，代价太大。宁可不淡。 */
.sb-item .tag {
    flex: none; font-size: 11px; line-height: 1;
    padding: 2px 6px; border-radius: 999px;
    background: var(--surface-3); color: var(--muted);
}
.sb-item:hover { background: var(--hover); color: var(--text); }
.sb-item.active { background: var(--accent-soft); color: var(--accent); font-weight: 500; }
.sb-item.active svg { opacity: 1; }

.sb-foot {
    flex: none;
    border-top: 1px solid var(--border);
    padding: 12px 12px 13px;
    display: flex; flex-direction: column; gap: 9px;
}

/* 余额：整块收进一张圆角玻璃卡，金额顶到右边。
   2026-10-16 之前是「一行小灰字 + 21px 小圆钮」直接躺在侧栏底上，
   用户截图点名「太小家子气」——现在按参考站那条「账户余额 + 大金额」重排。 */
.sb-wallet {
    padding: 11px 13px 12px;
    border: 1px solid var(--glass-hair);
    border-radius: 14px;
    background: var(--glass-bg);
    box-shadow: inset 0 1px 0 var(--glass-border);
}
.sb-balance {
    display: flex; align-items: center; gap: 8px;
    font-size: 12.5px; color: var(--muted);
}
.sb-balance b {
    margin-left: auto;
    color: var(--text); font-size: 20px; font-weight: 700; letter-spacing: -.6px;
}
.sb-balance .plus {
    flex: none; margin-left: 8px;
    width: 26px; height: 26px; border-radius: 50%;
    border: 0; background: var(--accent); color: #fff;
    cursor: pointer;
    display: flex; align-items: center; justify-content: center;
    box-shadow: 0 3px 9px var(--accent-glow);
    transition: background .16s, transform .16s;
}
.sb-balance .plus:hover { background: var(--accent-hover); transform: scale(1.07); }
.sb-balance .plus svg { width: 13px; height: 13px; }

/* 用户卡：头像 32 → 38px，昵称加粗一档，第二行 = 小皇冠 + 身份。
   ⚠️ 头像本身是后端用昵称首字渲染的 data-URI（固定浅蓝底 + 近黑字，见 User::getAvatarAttr），
      这里的 .ava 只是「接口没给图」时的兜底，配色跟后端那枚严格对齐 ——
      绝不要再按用户名算色相（那正是"赤橙黄绿青蓝紫都有"的来源）。 */
.sb-user {
    display: flex; align-items: center; gap: 11px;
    width: 100%; padding: 10px 11px;
    border: 1px solid transparent; border-radius: 14px;
    background: var(--hover); text-align: left; cursor: pointer;
    transition: background .16s, border-color .16s;
}
.sb-user:hover { background: var(--surface-3); border-color: var(--glass-hair); }
.sb-user img,
.sb-user .ava {
    flex: none; width: 38px; height: 38px; border-radius: 50%;
    object-fit: cover; background: var(--surface-3);
}
.sb-user .ava {
    display: flex; align-items: center; justify-content: center;
    background: #dbeafe; color: #0f172a;
    font-size: 17px; font-weight: 600; line-height: 1;
    user-select: none;
}
.sb-user .uu { min-width: 0; }
.sb-user .un {
    font-size: 14px; font-weight: 600; color: var(--text);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.sb-user .ur {
    display: flex; align-items: center; gap: 5px;
    margin-top: 3px; font-size: 11.5px; color: var(--muted);
}
/* 皇冠常驻（参考站对 Free User 也挂着皇冠），会员时转品牌色 */
.sb-user .crown { flex: none; color: var(--muted); opacity: .85; }
.sb-user .crown svg { display: block; width: 13px; height: 13px; }
.sb-user .crown.on { color: var(--accent); opacity: 1; }
.sb-user .chev { margin-left: auto; flex: none; color: var(--muted); }

/* 最底下那把锁：锁上 = 侧栏保持展开；没锁 = 进了功能页自己收成一条图标栏 */
.sb-lock {
    display: flex; align-items: center; gap: 9px;
    width: 100%; height: 36px; margin-top: 0; padding: 0 11px;
    border: 0; border-radius: 13px;
    background: transparent; color: var(--muted);
    font-size: 12.5px; text-align: left; cursor: pointer;
    transition: background .16s, color .16s;
}
.sb-lock:hover { background: var(--hover); color: var(--text); }
.sb-lock svg { flex: none; width: 16px; height: 16px; }
.sb-lock.on { color: var(--accent); background: var(--accent-soft); }

/* ---------- 收起态：只剩图标（默认就这样，鼠标搭上去靠 title 认名字） ---------- */

.sidebar.mini { width: 72px; }
.sidebar.mini .sb-head { flex-direction: column; gap: 0; justify-content: center; padding: 15px 0 13px; }
/* 收起态只有 72px 宽，横版字标塞不下 —— 留着会挤成一根糊条，直接让位给 icon */
.sidebar.mini .sb-head .logo-lg { display: none; }
.sidebar.mini .sb-head .tt,
.sidebar.mini .sb-head .sb-theme { display: none; }
.sidebar.mini .sb-body { padding: 6px 10px 12px; }
.sidebar.mini .sb-group { margin-top: 9px; }
.sidebar.mini .sb-group > .gt { display: none; }
.sidebar.mini .sb-item { justify-content: center; gap: 0; padding: 0; }
.sidebar.mini .sb-item .txt { display: none; }
.sidebar.mini .sb-item svg { width: 20px; height: 20px; opacity: .92; }
.sidebar.mini .sb-foot { padding: 11px 10px 12px; gap: 8px; }
.sidebar.mini .sb-wallet { padding: 9px 0; }
.sidebar.mini .sb-balance { justify-content: center; }
.sidebar.mini .sb-balance span,
.sidebar.mini .sb-balance b { display: none; }
.sidebar.mini .sb-balance .plus { margin-left: 0; }
.sidebar.mini .sb-user { justify-content: center; padding: 8px 0; }
.sidebar.mini .sb-user .uu,
.sidebar.mini .sb-user .chev { display: none; }
.sidebar.mini .sb-lock { justify-content: center; padding: 0; }
.sidebar.mini .sb-lock .txt { display: none; }

/* 工作区也是一块浮起来的圆角玻璃：画布 / 各个页面视图都铺在它里面。
   overflow:hidden 会把里面方角的 .viewport 一起裁成圆角 —— 这正是我们要的。

   ★ 三档透明度，别合并（合并过一次，整页立刻"糊成一片"）：
       --pane-bg        （工作区底，最透）—— 背景图主要靠它透出来，这一层负责"透明感"
       --glass-bg       （卡片 / 浮层  ）—— 要读字，比底亮一大截，靠亮度差立起来
       --glass-bg-strong（侧栏 / 弹窗  ）—— 文字最密，最实
     如果三者用一个值，卡片和底就分不出前后，看着像一整块玻璃板。

   ⚠️ 这里**不加 backdrop-filter**：这是一整屏的大面积元素，模糊一次的成本远高于收益
      （底下的背景图本来就已经糊过了），加一层只是白烧 GPU。 */
.workspace {
    flex: 1; min-width: 0; position: relative;
    display: flex; flex-direction: column; overflow: hidden;
    background: var(--pane-bg);
    border: 1px solid var(--glass-hair);
    border-radius: var(--pane-radius);
    box-shadow: var(--shadow-md);
}

/* ==========================================================================
   概览页（控制台首页）
   ========================================================================== */

/* 同上：1120 → 1680，宽屏不再留大片空档。 */
.ov { max-width: 1680px; margin: 0 auto; padding: 22px 26px 60px; }

.ov-hello {
    display: flex; align-items: flex-start; gap: 14px;
    padding: 18px 20px;
    background: var(--panel);
    border: 1px solid var(--border);
    border-radius: 16px;
}
.ov-hello .hi { font-size: 13px; color: var(--muted); }
.ov-hello .who { font-size: 22px; font-weight: 700; margin: 3px 0 5px; }
.ov-hello .spacer { flex: 1; }

.ov-stats {
    display: grid; grid-template-columns: repeat(4, 1fr); gap: 14px;
    margin: 16px 0;
}
.ov-stat {
    position: relative;
    padding: 16px 18px;
    background: var(--panel);
    border: 1px solid var(--border);
    border-radius: 16px;
}
.ov-stat .lb { font-size: 12.5px; color: var(--muted); }
.ov-stat .vl { font-size: 27px; font-weight: 700; margin-top: 5px; line-height: 1.1; }
.ov-stat .ico {
    position: absolute; right: 14px; top: 14px;
    width: 30px; height: 30px; border-radius: 12px;
    background: var(--surface-3); color: var(--text-2);
    display: flex; align-items: center; justify-content: center;
}

.ov-quick { display: grid; grid-template-columns: repeat(2, 1fr); gap: 14px; }
.ov-card {
    display: flex; align-items: center; gap: 14px;
    padding: 18px;
    background: var(--panel);
    border: 1px solid var(--border);
    border-radius: 16px;
    cursor: pointer;
    transition: border-color .16s, background .16s;
}
.ov-card:hover { border-color: var(--border-strong); background: var(--hover); }
.ov-card .ic {
    flex: none; width: 38px; height: 38px; border-radius: 12px;
    background: var(--surface-3); color: var(--text-2);
    display: flex; align-items: center; justify-content: center;
}
.ov-card .ttl { font-size: 15px; font-weight: 600; }
.ov-card .des { font-size: 12.5px; color: var(--muted); margin-top: 3px; }
.ov-card .go { font-size: 12.5px; color: var(--accent); margin-top: 6px; }

.ov-recent { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; margin-top: 16px; }
.ov-panel {
    background: var(--panel);
    border: 1px solid var(--border);
    border-radius: 16px;
    overflow: hidden;
}
.ov-panel .ph {
    display: flex; align-items: center; gap: 8px;
    padding: 13px 16px; border-bottom: 1px solid var(--border);
    font-size: 13.5px; font-weight: 600;
}
.ov-panel .ph .go { margin-left: auto; font-size: 12.5px; font-weight: 400; color: var(--muted); cursor: pointer; }
.ov-panel .ph .go:hover { color: var(--accent); }
.ov-panel .pb { padding: 12px; }
.ov-thumbs { display: grid; grid-template-columns: repeat(4, 1fr); gap: 9px; }
.ov-thumb {
    position: relative; aspect-ratio: 1 / 1;
    border-radius: 12px; overflow: hidden;
    background: var(--surface-3); cursor: pointer;
}
.ov-thumb img, .ov-thumb video { width: 100%; height: 100%; object-fit: cover; display: block; }
.ov-thumb .bd {
    position: absolute; left: 6px; bottom: 6px;
    font-size: 10.5px; padding: 1px 6px; border-radius: 999px;
    background: rgba(0, 0, 0, .6); color: #fff;
}

/* ==========================================================================
   参数设置面板（图像设置 / 视频设置）
   挂在 body 上、position:fixed —— 画布缩放不会把它一起缩掉，永远清晰可读
   ========================================================================== */

.set-pop {
    position: fixed;
    width: 322px;
    max-height: calc(100vh - 40px);
    overflow-y: auto;
    padding: 16px;
    background: var(--float-bg);
    border: 1px solid var(--border);
    border-radius: 16px;
    box-shadow: var(--shadow-lg);
    z-index: 95;
}
.set-pop::-webkit-scrollbar { width: 8px; }
.set-pop::-webkit-scrollbar-thumb { background: var(--scroll-thumb); border-radius: 4px; }

.set-title { font-size: 14px; font-weight: 600; margin-bottom: 15px; }

.set-group + .set-group { margin-top: 17px; }
.set-group > .gh {
    display: flex; align-items: center; gap: 8px;
    font-size: 12.5px; color: var(--text-2);
    margin-bottom: 9px;
}
.set-group > .gh .sp { flex: 1; }
.set-group > .gh .hint { font-size: 11px; color: var(--muted); }

.set-chips { display: flex; flex-wrap: wrap; gap: 8px; }
.set-chip {
    height: 34px; padding: 0 16px;
    border: 1px solid var(--pick-border); border-radius: 12px;
    background: var(--pick-bg); color: var(--pick-text);
    font-size: 13px; cursor: pointer;
    transition: border-color .16s, color .16s, background .16s;
}
.set-chip:hover { border-color: var(--pick-border-on); color: var(--pick-text-on); }
.set-chip.on {
    border-color: var(--pick-border-on); background: var(--pick-bg-on);
    color: var(--pick-text-on); font-weight: 600;
}
.set-chip.locked { cursor: default; }

.set-wh { display: flex; align-items: center; gap: 9px; }
.set-wh .box {
    flex: 1; min-width: 0;
    display: flex; align-items: center; gap: 7px;
    height: 34px; padding: 0 11px;
    border: 1px solid var(--border); border-radius: 12px;
    background: var(--input-bg);
}
.set-wh .box .k { font-size: 11.5px; color: var(--muted); }
.set-wh .box .v { font-size: 13.5px; color: var(--text); }
.set-wh .arw { flex: none; color: var(--muted); }

/* 比例格子：一行 3 个。视频要摆 6 个比例（1:1 / 3:4 / 4:3 / 9:16 / 16:9 / 21:9），
   一行 5 个会剩一个孤零零换行；一行 3 个刚好两排，图像那边的 3 个比例也是一排。 */
.set-ratios { display: grid; grid-template-columns: repeat(3, 1fr); gap: 7px; }
.set-ratio {
    display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 6px;
    padding: 9px 2px 7px;
    border: 1px solid var(--pick-border); border-radius: 12px;
    background: var(--pick-bg); color: var(--pick-text);
    cursor: pointer;
    transition: border-color .16s, color .16s, background .16s;
}
.set-ratio:hover { border-color: var(--pick-border-on); color: var(--pick-text-on); }
.set-ratio.on {
    border-color: var(--pick-border-on); background: var(--pick-bg-on);
    color: var(--pick-text-on); font-weight: 600;
}
.set-ratio .shape { width: 24px; height: 24px; display: flex; align-items: center; justify-content: center; }
.set-ratio .shape i {
    display: block;
    border: 1.6px solid currentColor;
    border-radius: 2.5px;
    box-sizing: border-box;
}
.set-ratio .lb { font-size: 11px; white-space: nowrap; }
/* 只有视频那边用 .px 补一行「横屏 / 竖屏」；图片的比例格子不写它 */
.set-ratio .px { font-size: 9.5px; color: var(--muted); white-space: nowrap; }

/* 下拉框（分辨率 / 时长）：跟同一面板的格子共用一套配色。
   用原生 select 是因为档位可能很长（16~30 秒共 15 档），摆成格子会把面板撑爆。 */
.set-select {
    width: 100%; height: 38px; padding: 0 34px 0 12px;
    border: 1px solid var(--pick-border); border-radius: 12px;
    background: var(--pick-bg); color: var(--pick-text);
    font-size: 13px; font-family: inherit; cursor: pointer;
    appearance: none; -webkit-appearance: none;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath d='M2.5 4.75L6 8.25l3.5-3.5' fill='none' stroke='%23888888' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 12px center;
    transition: border-color .16s, color .16s;
}
.set-select:hover { border-color: var(--pick-border-on); color: var(--pick-text-on); }
.set-select:focus { outline: none; border-color: var(--pick-border-on); }
/* 深色主题里原生下拉展开的列表是系统画的，文字会继承主题色，这里兜一下 */
.set-select option { color: #101724; }
[data-theme="dark"] .set-select option { color: #e8ebf2; background: #0a0a12; }
.set-ratio.on .px { color: var(--pick-text-on); opacity: .72; }

.set-nums { display: grid; grid-template-columns: repeat(4, 1fr); gap: 7px; }
.set-num {
    height: 34px;
    border: 1px solid var(--pick-border); border-radius: 12px;
    background: var(--pick-bg); color: var(--pick-text);
    font-size: 13px; cursor: pointer;
    transition: border-color .16s, color .16s, background .16s;
}
.set-num:hover { border-color: var(--pick-border-on); color: var(--pick-text-on); }
.set-num.on {
    border-color: var(--pick-border-on); background: var(--pick-bg-on);
    color: var(--pick-text-on); font-weight: 600;
}

.set-switch { display: flex; align-items: center; gap: 9px; cursor: pointer; }
.set-switch .sw {
    flex: none; width: 38px; height: 21px; border-radius: 999px;
    background: var(--surface-3); position: relative;
    transition: background .16s;
}
.set-switch .sw::after {
    content: ''; position: absolute; top: 2.5px; left: 2.5px;
    width: 16px; height: 16px; border-radius: 50%;
    background: #fff; box-shadow: 0 1px 3px rgba(0, 0, 0, .3);
    transition: transform .16s;
}
.set-switch.on .sw { background: var(--accent); }
.set-switch.on .sw::after { transform: translateX(17px); }
.set-switch .lb { font-size: 12.5px; color: var(--text-2); }

.set-refs { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 2px; }
.set-ref {
    position: relative; width: 54px; height: 54px;
    border-radius: 12px; overflow: hidden; background: var(--surface-3);
}
.set-ref img { width: 100%; height: 100%; object-fit: cover; display: block; }
.set-ref .x {
    position: absolute; right: 2px; top: 2px;
    width: 17px; height: 17px; border-radius: 50%;
    border: 0; background: rgba(0, 0, 0, .62); color: #fff;
    font-size: 12px; line-height: 1; cursor: pointer;
    display: flex; align-items: center; justify-content: center;
}
.set-add {
    width: 54px; height: 54px;
    display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 3px;
    border: 1px dashed var(--border-strong); border-radius: 12px;
    background: transparent; color: var(--muted);
    font-size: 10.5px; cursor: pointer;
}
.set-add:hover { color: var(--accent); border-color: var(--accent); }

/* 宽屏：概览那 4 个入口卡横排一行，把多出来的宽度真正用掉
   —— 光把容器放开、卡片还是 2×2，中间那条缝照样是空的。 */
@media (min-width: 1400px) {
    .ov-quick { grid-template-columns: repeat(4, 1fr); }
}

/* 窄屏 */
@media (max-width: 900px) {
    .ov-stats { grid-template-columns: repeat(2, 1fr); }
    .ov-quick, .ov-recent { grid-template-columns: 1fr; }
}
@media (max-width: 720px) {
    /* 小屏上那圈「浮动留白」要收窄，否则侧栏 + 工作区挤得没法用 */
    #app { padding: 6px; gap: 6px; }
    .sidebar { width: 206px; }
    .cv-title { left: 8px; top: 8px; }
    .cv-top { right: 8px; top: 8px; }
    .cv-rail { left: 8px; }
    /* 窄屏上小地图整个不提供服务（没有空间），那枚开关也不能单留着 —— 点了没反应 */
    .cv-map { display: none; }
    .cv-map-toggle { display: none; }
    .set-pop { width: calc(100vw - 24px); }
}

/* ==========================================================================
   毛玻璃层（2026-10-15 视觉精修；同日第二轮按参考图加厚）

   四要素：半透明底 + 背景模糊 + 内侧一道白描边（玻璃边缘的高光）+ 外阴影。
   放在文件最后，靠「同特异性、后来者优先」盖掉各处自己写的
   background / border-color / box-shadow。
   ⚠️ 所以这一块**只放这三样 + backdrop-filter**，绝不写尺寸 / 内边距 / 定位，
      否则会把布局搞乱（末尾的媒体查询只改宽度，跟这里不冲突，安全）。

   两条性能红线（别越）：
     ① 节点卡片 .cn 不在此列 —— 一屏可能几十上百个，每个都做背景模糊
        会直接拖垮画布缩放的帧率。它靠 --node-bg 半透明就够了。
     ② 图像库 / 视频库的列表卡 .work-card 也不在此列 —— 用户可能有几百张图，
        同理。它们同样靠 var(--panel) 半透明取得通透感（背景图本来就糊过，
        没有 backdrop-filter 也看不出差别）。
   ========================================================================== */

/* ---- 常规玻璃：浮层、工具条、小药丸 ---- */
.menu-pop,
.chip-pop,
.mention-pop,
.node-menu,
.cost-tip,
.set-pop,
.cn-float,
.cn-foot,
.edge-cut,
.cv-title,
.cv-rail,
.cv-map,
.cv-pill,
.cv-btn,
.cv-layout {
    background: var(--glass-bg);
    border-color: var(--glass-hair);
    box-shadow: inset 0 1px 0 var(--glass-border), var(--glass-shadow);
    -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-sat));
    backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-sat));
}

/* ---- 内容卡片：概览 / 会员 / 站点规则这类「数量可控」的卡片也上玻璃 ----
   2026-10-15 第二轮补的。参考图里连数据表都是透的，光让浮层透、卡片还是实心白，
   整页就还是"白块拼盘"。这里全是每页十来块以内的卡片，上模糊不心疼。 */
.ov-hello,
.ov-stat,
.ov-card,
.ov-panel,
.stat-box,
.card,
.plan-card {
    background: var(--glass-bg);
    border-color: var(--glass-hair);
    box-shadow: inset 0 1px 0 var(--glass-border), var(--glass-shadow);
    -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-sat));
    backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-sat));
}

/* ---- 登录卡片：同一套玻璃，但底要实一档（--auth-card-bg）----
   内容页的卡片底下永远是主站那层「已糊过 + 已压过蒙版」的背景，透一点没关系；
   登录卡片底下可能是**用户随便换的任何一张图**，深色图一上来，
   白玻璃就整张糊进背景、只剩一圈描边能看出边界。所以它单独走一档更实的底。 */
.auth-card {
    background: var(--auth-card-bg);
    /* 描边用 --glass-hair（浅色＝淡灰细线）而不是 --glass-border（纯白）：
       背景可能是亮的也可能是暗的，淡灰线在亮背景上看得见，
       内侧那道白高光（下面 box-shadow 第一段）在暗背景上看得出 —— 两头顶上都留一手。 */
    border-color: var(--glass-hair);
    box-shadow: inset 0 1px 0 var(--glass-border), var(--glass-shadow);
    -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-sat));
    backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-sat));
}

/* 玻璃卡片里的分隔线要跟着变淡，否则像拿马克笔在玻璃上画了一道 */
.ov-panel .ph,
.card-head,
.sb-head,
.sb-foot {
    border-color: var(--glass-hair);
}

/* ---- 实玻璃：侧栏和弹窗里要读字，底给实一点 ---- */
.sidebar,
.fs-card {
    background: var(--glass-bg-strong);
    border-color: var(--glass-hair);
    box-shadow: inset 0 1px 0 var(--glass-border), var(--glass-shadow);
    -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-sat));
    backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-sat));
}

/* 弹窗原本没有边框，单独补一道，否则 "border-color" 落不到实处 */
.modal {
    background: var(--glass-bg-strong);
    border: 1px solid var(--glass-hair);
    box-shadow: inset 0 1px 0 var(--glass-border), var(--glass-shadow);
    -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-sat));
    backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-sat));
}

/* ---- 遮罩也糊一层：背景一糊，前景的玻璃才有层次 ---- */
.modal-mask,
.fs-mask {
    -webkit-backdrop-filter: blur(10px);
    backdrop-filter: blur(10px);
}

/* ---- 节点上那颗黑色小药丸（切图 / 计数）：保持深色，只把模糊加厚 ---- */
.cn-switch {
    -webkit-backdrop-filter: blur(12px) saturate(150%);
    backdrop-filter: blur(12px) saturate(150%);
}

/* ---- 提示条在任何背景上都要看得清：保持深色，加层模糊让它"浮"起来 ---- */
.toast {
    background: rgba(28, 32, 42, .84);
    -webkit-backdrop-filter: blur(14px) saturate(160%);
    backdrop-filter: blur(14px) saturate(160%);
}

/* ---- 玻璃上的分隔线与 hover 要跟着变淡，否则像贴了块实色上去 ---- */
.menu-pop .sep,
.cv-rail .rsep {
    background: var(--glass-hair);
}
.modal-foot,
.cv-map .mh {
    border-color: var(--glass-hair);
}
.menu-pop button:hover,
.sb-item:hover,
.sb-theme:hover,
.sb-lock:hover,
.sb-user:hover {
    background: var(--glass-hair);
}

/* ==========================================================================
   §A 图片生成 / 视频创作（线性页面版，2026-10-16 第六轮新增）

   背景：有兄弟反馈「用不惯无限画布」——画布要自己摆节点、连箭头、@ 引用，
   门槛在"搭结构"上。这两页把同一套后端能力（模型 / 比例 / 时长 / 张数 / 报价 /
   下单 / 轮询）收成一条直线：写提示词 → 选参数 → 点开始生成 → 看结果。

   ⚠️ 这两页**不碰画布**：不建节点、不写 wxhb_canvas，走的是同一对
      /api/wxhb/quote 和 /api/wxhb/create，所以计费 / 冻结 / 退款 / AIGC 标识
      全都是同一条链路，没有第二套账。

   两套皮肤：
     · 浅色 —— **整站浅色的一份引用**（品牌蓝主色 + 冷灰灰阶 + 透明页底 + 玻璃卡）。
               参考：整站 --accent #4f6ef7 / --border #e2e7f0 / --glass-bg 白 .52。
     · 深色 —— 跟整站暗色科幻皮肤同源（白低透明度玻璃卡 + 粉色主按钮）。
   ⚠️ 所以这一块的颜色**必须全部走 --gp-* 变量**，页面里不许再出现写死色值
      （硬坑 82 的同型风险：只改一边 = 深色下白底黑字糊成一块）。
   ========================================================================== */

:root {
    /* 深色：卡是白低透明度玻璃，跟整站暗色科幻一致 */
    --gp-page: transparent;
    --gp-card: rgba(255, 255, 255, .055);
    --gp-soft: rgba(255, 255, 255, .04);
    --gp-line: rgba(255, 255, 255, .09);
    --gp-line-2: rgba(255, 255, 255, .16);
    --gp-text: #f4f4f7;
    --gp-text-2: #cbccd5;
    --gp-muted: #9b9dab;
    --gp-accent: #ec4899;
    --gp-accent-hover: #db2777;
    --gp-accent-fg: #ffffff;
    --gp-accent-soft: rgba(236, 72, 153, .16);
    --gp-chip: rgba(255, 255, 255, .05);
    --gp-shadow: 0 10px 30px rgba(0, 0, 0, .44);
    --gp-radius: 14px;
}

html[data-theme="light"] {
    /* ⚠️ 2026-10-15 第九轮：这一套**整块换掉**了。
       原来那套是照用户给的参考图 1:1 抄下来的「暖白页底 + 卡其主按钮 #8a7f5c」，
       结果有兄弟一眼看出问题：「视频创作 / 图片生成两页的色调跟左侧蓝色功能栏不搭，
       像两个系统的东西」。原因很实在 —— 暖米色按钮 + 暖灰描边压在整站的
       冷蓝（--accent #4f6ef7）+ 冷灰（#e2e7f0）+ 照片毛玻璃上，色温是反的。

       现在这套**不再是"自成一套"的皮肤**，而是**整站浅色变量的一份引用**：
         主色 = 整站品牌蓝（跟左侧功能栏、主按钮、聚焦环同一个色）
         灰阶 = 整站那三档（--text / --text-2 / --muted）
         描边 = --border / --border-strong
         阴影 = --shadow-sm
       页底也不再自己铺一层暖白 —— 跟 .page / .ov 一样 `transparent`，
       直接坐在同一层工作区玻璃上（铺了底才真像"换了个网站"）。
       ⚠️ 改这里时**别再往卡其/米色方向调**，也别把值写成和上面那几档不一致的近似色：
          两套色温只要差一点点，整页就又会飘出去。 */
    --gp-page: transparent;
    /* 卡片不写死纯白：跟整站玻璃卡一个观感（整站 --glass-bg 是白 .52，
       这两页文字量大、正文多，给到 .72 才压得住，再透正文就发飘了） */
    --gp-card: rgba(255, 255, 255, .72);
    --gp-soft: rgba(255, 255, 255, .52);   /* 卡片里的"内嵌面板"（输入框 / 下拉 / 小方格） */
    --gp-line: #e2e7f0;                    /* = --border */
    --gp-line-2: #d0d7e5;                  /* = --border-strong */
    --gp-text: #1f2430;                    /* = --text */
    --gp-text-2: #414a5a;                  /* = --text-2 */
    --gp-muted: #5a6474;                   /* = --muted */
    /* 主色 = 整站品牌蓝（左栏 logo / 选中项 / 主按钮 / 聚焦环 全是这个色） */
    --gp-accent: #4f6ef7;
    --gp-accent-hover: #4059e0;
    --gp-accent-fg: #ffffff;
    --gp-accent-soft: #eef1ff;
    --gp-chip: rgba(255, 255, 255, .58);
    --gp-shadow: 0 1px 2px rgba(24, 32, 51, .06), 0 1px 3px rgba(24, 32, 51, .04);
    --gp-radius: 14px;
}

/* 页面容器：外层满幅铺平底（浅色那层暖白要从工作区左边缘一直铺到右边），
   内层才收宽度 —— 跟 .page 一样 1680px，免得宽屏上内容横贯一整条。 */
.gen { background: var(--gp-page); min-height: 100%; }
.gen-inner { max-width: 1680px; margin: 0 auto; padding: 26px 26px 60px; }

/* ---- 页头：圆角方图标 + 大标题 + 副标题 ---- */
.gen-head { display: flex; align-items: center; gap: 12px; margin-bottom: 18px; }
.gen-head .gi {
    width: 38px; height: 38px; flex: none;
    border-radius: 12px;
    display: flex; align-items: center; justify-content: center;
    background: var(--gp-soft);
    border: 1px solid var(--gp-line);
    color: var(--gp-text);
}
.gen-head h2 { margin: 0; font-size: 21px; font-weight: 700; letter-spacing: -.2px; color: var(--gp-text); }
.gen-head p { margin: 3px 0 0; font-size: 12.5px; color: var(--gp-muted); }

/* ---- 顶部参数摘要条：一格一条，跟参考图一样「一眼看全本次参数」 ---- */
.gen-sum {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: 1px;
    background: var(--gp-line);
    border: 1px solid var(--gp-line);
    border-radius: var(--gp-radius);
    overflow: hidden;
    margin-bottom: 18px;
}
.gs-cell { background: var(--gp-card); padding: 10px 14px; min-width: 0; }
.gs-k { font-size: 11.5px; color: var(--gp-muted); margin-bottom: 3px; }
.gs-v {
    font-size: 13px; font-weight: 600; color: var(--gp-text);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.gs-s { font-size: 11.5px; color: var(--gp-muted); margin-top: 3px; }

/* ---- 两列骨架：左（提示词 + 设置 + 结果）宽，右（任务面板）窄 ---- */
.gen-cols {
    display: grid;
    grid-template-columns: minmax(0, 1.5fr) minmax(0, 1fr);
    gap: 16px;
    align-items: start;
}
.gen-col { display: flex; flex-direction: column; gap: 16px; min-width: 0; }
@media (max-width: 1080px) {
    .gen-cols { grid-template-columns: minmax(0, 1fr); }
    .gen-sum { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* ---- 卡片 ---- */
.gen-card {
    background: var(--gp-card);
    border: 1px solid var(--gp-line);
    border-radius: var(--gp-radius);
    box-shadow: var(--gp-shadow);
    overflow: hidden;
}
.gc-head {
    display: flex; align-items: center; gap: 8px;
    padding: 13px 16px;
    border-bottom: 1px solid var(--gp-line);
}
.gc-head .ci { color: var(--gp-text-2); display: flex; }
.gc-head .ct { font-size: 13.5px; font-weight: 600; color: var(--gp-text); }
.gc-head .cg { flex: 1; }
.gc-head .cv { font-size: 13px; font-weight: 600; color: var(--gp-text); }
.gc-body { padding: 15px 16px 17px; }
.gc-note { font-size: 12px; color: var(--gp-muted); margin-bottom: 14px; }

/* ---- 提示词 ---- */
.gen-ta-wrap { position: relative; }
.gen-ta {
    width: 100%;
    min-height: 172px;
    resize: vertical;
    padding: 12px 14px 30px;
    border: 1px solid var(--gp-line);
    border-radius: 10px;
    background: var(--gp-soft);
    color: var(--gp-text);
    font-size: 13px;
    line-height: 1.7;
    outline: none;
    transition: border-color .16s, box-shadow .16s;
}
.gen-ta::placeholder { color: var(--gp-muted); }
.gen-ta:focus { border-color: var(--gp-accent); box-shadow: 0 0 0 3px var(--gp-accent-soft); }
.gen-ta-foot {
    position: absolute; left: 14px; bottom: 8px; right: 56px;
    font-size: 11.5px; color: var(--gp-muted);
    pointer-events: none;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.gen-ta-count { position: absolute; right: 14px; top: 10px; font-size: 11.5px; color: var(--gp-muted); }
.gen-lbl { display: flex; align-items: center; gap: 6px; margin-bottom: 8px; font-size: 12.5px; font-weight: 600; color: var(--gp-text-2); }
.gen-lbl .req { color: #d9534f; }
html[data-theme="dark"] .gen-lbl .req { color: #f0736d; }

/* 参考图缩略图条（@ 引用就靠它，跟画布里那条同源） */
.gen-refs { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 12px; }
.gen-ref {
    position: relative; width: 60px; height: 60px;
    border-radius: 9px; overflow: hidden;
    border: 1px solid var(--gp-line);
    background: var(--gp-soft);
}
.gen-ref img, .gen-ref video { width: 100%; height: 100%; object-fit: cover; display: block; }
.gen-ref .rn {
    position: absolute; left: 0; right: 0; bottom: 0;
    background: rgba(0, 0, 0, .58); color: #fff;
    font-size: 10.5px; text-align: center; padding: 2px 0;
}
.gen-ref .rx {
    position: absolute; right: 2px; top: 2px;
    width: 16px; height: 16px; border: 0; border-radius: 50%;
    background: rgba(0, 0, 0, .6); color: #fff;
    font-size: 11px; line-height: 16px; text-align: center;
    cursor: pointer; padding: 0;
}

/* ---- 快捷标签 chips ---- */
.gen-chips { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 14px; }
.gen-chip {
    height: 28px; padding: 0 12px;
    border: 1px solid var(--gp-line);
    border-radius: 999px;
    background: var(--gp-chip);
    color: var(--gp-text-2);
    font-size: 12px; cursor: pointer;
    transition: background .16s, border-color .16s, color .16s;
}
.gen-chip:hover { border-color: var(--gp-line-2); color: var(--gp-text); }
.gen-chip:disabled { opacity: .5; cursor: not-allowed; }

/* ---- 表单：两列下拉 ---- */
.gen-grid2 { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px 14px; }
.gen-grid2.one { grid-template-columns: minmax(0, 1fr); }
@media (max-width: 620px) { .gen-grid2 { grid-template-columns: minmax(0, 1fr); } }
.gen-fld { min-width: 0; }
.gen-fld label { display: flex; align-items: center; gap: 5px; font-size: 12px; font-weight: 600; color: var(--gp-text-2); margin-bottom: 6px; }
.gen-fld label .req { color: #d9534f; }
html[data-theme="dark"] .gen-fld label .req { color: #f0736d; }
.gen-sel { position: relative; }
.gen-sel select {
    width: 100%;
    height: 40px;
    padding: 0 32px 0 12px;
    border: 1px solid var(--gp-line);
    border-radius: 10px;
    background: var(--gp-soft);
    color: var(--gp-text);
    font-size: 12.5px;
    cursor: pointer;
    outline: none;
    -webkit-appearance: none; appearance: none;
    transition: border-color .16s, box-shadow .16s;
}
.gen-sel select:focus { border-color: var(--gp-accent); box-shadow: 0 0 0 3px var(--gp-accent-soft); }
.gen-sel select:disabled { opacity: .55; cursor: not-allowed; }
.gen-sel::after {
    content: '';
    position: absolute; right: 13px; top: 50%;
    width: 7px; height: 7px;
    border-right: 1.6px solid var(--gp-muted);
    border-bottom: 1.6px solid var(--gp-muted);
    transform: translateY(-70%) rotate(45deg);
    pointer-events: none;
}
/* 原生下拉面板在深色下会白底白字 —— 显式给一遍 */
.gen-sel select option { background: #ffffff; color: #1f2430; }
html[data-theme="dark"] .gen-sel select option { background: #0d0d16; color: #f0f0f4; }

/* ---- 生成数量：一排小格子，选中＝主色底 ---- */
.gen-qty { display: flex; flex-wrap: wrap; gap: 8px; }
.gen-qty button {
    width: 46px; height: 34px;
    border: 1px solid var(--gp-line);
    border-radius: 9px;
    background: var(--gp-soft);
    color: var(--gp-text-2);
    font-size: 12.5px; cursor: pointer;
    transition: background .16s, border-color .16s, color .16s;
}
.gen-qty button:hover { border-color: var(--gp-line-2); }
.gen-qty button.on {
    background: var(--gp-accent);
    border-color: var(--gp-accent);
    color: var(--gp-accent-fg);
    font-weight: 700;
}

/* ---- 参考图上传区 ---- */
.gen-drop {
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    gap: 5px;
    padding: 22px 12px;
    border: 1px dashed var(--gp-line-2);
    border-radius: 10px;
    color: var(--gp-muted);
    font-size: 12px;
    cursor: pointer;
    background: var(--gp-soft);
    transition: border-color .16s, color .16s;
}
.gen-drop:hover { border-color: var(--gp-accent); color: var(--gp-text-2); }
.gen-drop .dz { font-size: 19px; line-height: 1; }
.gen-btns { display: flex; gap: 8px; }

/* ---- 任务面板 ---- */
.gt-info { display: flex; gap: 10px; padding: 12px; border-radius: 10px; background: var(--gp-soft); }
.gt-info .ti {
    width: 30px; height: 30px; flex: none; border-radius: 9px;
    display: flex; align-items: center; justify-content: center;
    background: var(--gp-accent-soft); color: var(--gp-accent);
}
.gt-info .tt { font-size: 12.5px; font-weight: 600; color: var(--gp-text); }
.gt-info .td { font-size: 11.5px; color: var(--gp-muted); line-height: 1.6; margin-top: 2px; }
.gt-grid {
    display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 10px; margin-top: 14px;
}
.gt-cell { min-width: 0; }
.gt-cell .k { font-size: 11.5px; color: var(--gp-muted); }
.gt-cell .v { font-size: 12.5px; font-weight: 600; color: var(--gp-text); margin-top: 2px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.gt-bar { height: 5px; border-radius: 4px; background: var(--gp-soft); margin-top: 14px; overflow: hidden; }
.gt-bar > i { display: block; height: 100%; width: 0; border-radius: 4px; background: var(--gp-accent); transition: width .4s ease; }
.gt-bar.done > i { background: var(--ok); }
.gt-bar.fail > i { background: var(--danger); }
.gt-spec {
    margin-top: 14px;
    border: 1px solid var(--gp-line);
    border-radius: 10px;
    background: var(--gp-soft);
    overflow: hidden;
}
.gt-row {
    display: flex; align-items: baseline; gap: 10px;
    padding: 9px 13px;
    font-size: 12.5px;
    border-bottom: 1px solid var(--gp-line);
}
.gt-row:last-child { border-bottom: 0; }
.gt-row .k { color: var(--gp-muted); flex: 1; }
.gt-row .v { color: var(--gp-text); font-weight: 600; text-align: right; }

/* 主按钮：比全站 .btn 高一档，参考图里就是这么一块大按钮 */
.gen-run {
    width: 100%;
    height: 44px;
    margin-top: 16px;
    display: inline-flex; align-items: center; justify-content: center; gap: 8px;
    border: 1px solid var(--gp-accent);
    border-radius: 11px;
    background: var(--gp-accent);
    color: var(--gp-accent-fg);
    font-size: 13.5px; font-weight: 600;
    cursor: pointer;
    transition: background .16s, opacity .16s;
}
.gen-run:hover:not(:disabled) { background: var(--gp-accent-hover); border-color: var(--gp-accent-hover); }
.gen-run:disabled { opacity: .55; cursor: not-allowed; }

/* ==========================================================================
   v44 · 生成页新版式（2026-10-16 照用户给的参考站改）

   左窄栏 = 参数列，三段带序号（① 模型 ② 上传图 ③ 提示词），底部**吸底**一条；
   右宽栏 = 结果流，历史作品与进行中的任务排成一条。

   ⚠️ 吸底能成立的前提：滚动容器是 `.view`（`position:absolute; inset:0; overflow:auto`，
      见本文件 682 行）。sticky 的祖先里**不能有** overflow:hidden 的中间层 ——
      `.workspace` 就是 `overflow:hidden`，但它在 `.view` 外面，不影响。

   ⚠️ 颜色一律走 --gp-* 变量（浅色暖白 / 深色科幻两套），页面里不许写死色值。
      这里只额外补了「成功/失败」两个语义色，两套主题各给一份。
   ========================================================================== */

.gen {
    --gp-ok: #1d9e75;
    --gp-ok-soft: rgba(29, 158, 117, .16);
    --gp-danger: #e24b4a;
    --gp-danger-soft: rgba(226, 75, 74, .16);
}

html[data-theme="dark"] .gen {
    --gp-ok: #34d399;
    --gp-ok-soft: rgba(52, 211, 153, .18);
    --gp-danger: #f0736d;
    --gp-danger-soft: rgba(240, 115, 109, .18);
}

.gen-stage {
    display: grid;
    grid-template-columns: 336px minmax(0, 1fr);
    gap: 16px;
    align-items: start;
}

/* ---------- 左：参数列 ---------- */
.gp-lane {
    display: flex;
    flex-direction: column;
    gap: 12px;
    min-width: 0;
}

.gp-sec {
    background: var(--gp-card);
    border: 1px solid var(--gp-line);
    border-radius: var(--gp-radius);
    box-shadow: var(--gp-shadow);
    padding: 13px 14px 15px;
}

.gp-sec-h {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-bottom: 11px;
}
.gp-sec-h .no {
    flex: none;
    width: 19px; height: 19px;
    display: inline-flex; align-items: center; justify-content: center;
    border-radius: 6px;
    background: var(--gp-accent-soft);
    color: var(--gp-accent);
    font-size: 11.5px; font-weight: 700; line-height: 1;
}
.gp-sec-h .tt { font-size: 13px; font-weight: 600; color: var(--gp-text); }
.gp-sec-h .g { flex: 1; }
.gp-hint {
    font-size: 11.5px;
    color: var(--gp-muted);
    max-width: 64%;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* 模型那一格：genSelect 的 label 传空，所以去掉多出来的上边距 */
.gp-sel .gen-fld label { display: none; }
.gp-sel .gen-sel select { height: 42px; }

/* ---------- 上传图片：图格 + 加号格 ---------- */
.gp-shots {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 8px;
}
.gp-shot {
    position: relative;
    aspect-ratio: 1 / 1;
    border-radius: 10px;
    overflow: hidden;
    border: 1px solid var(--gp-line);
    background: var(--gp-soft);
}
.gp-shot img { width: 100%; height: 100%; object-fit: cover; display: block; }
.gp-shot .no {
    position: absolute; left: 5px; bottom: 5px;
    padding: 0 6px; border-radius: 999px;
    background: rgba(0, 0, 0, .54);
    color: #fff; font-size: 10.5px; line-height: 16px;
}
.gp-shot .rx {
    position: absolute; right: 4px; top: 4px;
    width: 20px; height: 20px;
    display: inline-flex; align-items: center; justify-content: center;
    border: 0; border-radius: 999px; padding: 0;
    background: rgba(0, 0, 0, .52); color: #fff;
    cursor: pointer; opacity: 0;
    transition: opacity .16s;
}
.gp-shot:hover .rx { opacity: 1; }
.gp-shot .rx svg { width: 11px; height: 11px; }

.gp-shot.add {
    display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 3px;
    border-style: dashed;
    background: transparent;
    color: var(--gp-muted);
    font-family: inherit; cursor: pointer;
    transition: border-color .16s, color .16s, background .16s;
}
.gp-shot.add:hover {
    border-color: var(--gp-accent);
    color: var(--gp-accent);
    background: var(--gp-accent-soft);
}
.gp-shot.add .pl { display: flex; }
.gp-shot.add .pl svg { width: 16px; height: 16px; }
.gp-shot.add .lb { font-size: 11px; }

.gp-shots-act { display: flex; align-items: center; gap: 9px; margin-top: 11px; flex-wrap: wrap; }
.gp-tip { font-size: 11.5px; color: var(--gp-muted); line-height: 1.5; }

/* ---------- 底部吸底条：胶囊 + 生成 + 小字 ---------- */
.gp-dock {
    position: sticky;
    bottom: 0;
    z-index: 5;
    display: flex;
    flex-direction: column;
    gap: 9px;
    padding: 11px 12px 12px;
    border: 1px solid var(--gp-line);
    border-radius: var(--gp-radius);
    background: var(--gp-card);
    box-shadow: var(--gp-shadow);
    backdrop-filter: blur(18px);
    -webkit-backdrop-filter: blur(18px);
}
.gp-dock .gen-run { margin-top: 0; }

.gp-caps { display: flex; flex-wrap: wrap; gap: 6px; }
.gp-cap {
    display: inline-flex; align-items: center; gap: 5px;
    height: 28px; padding: 0 10px;
    border: 1px solid var(--gp-line-2);
    border-radius: 999px;
    background: var(--gp-chip);
    color: var(--gp-text-2);
    font-size: 12px; font-family: inherit;
    cursor: pointer;
    transition: border-color .16s, color .16s, background .16s;
}
.gp-cap:hover:not(.off) { border-color: var(--gp-accent); color: var(--gp-text); }
.gp-cap.open {
    border-color: var(--gp-accent);
    color: var(--gp-accent);
    background: var(--gp-accent-soft);
}
.gp-cap.off { opacity: .55; cursor: default; }
.gp-cap .ic, .gp-cap .cr { display: flex; }
.gp-cap .ic svg { width: 13px; height: 13px; }
.gp-cap .cr svg { width: 10px; height: 10px; opacity: .7; }
.gp-cap .tx { white-space: nowrap; }

.gp-dock-note { font-size: 11.5px; color: var(--gp-muted); line-height: 1.5; text-align: center; }

/* ---------- 胶囊弹层 ---------- */
.gp-pop {
    position: fixed;
    z-index: 300;
    min-width: 136px;
    max-height: 246px;
    overflow-y: auto;
    padding: 5px;
    border: 1px solid var(--gp-line-2);
    border-radius: 12px;
    background: var(--float-bg);
    box-shadow: 0 16px 40px rgba(0, 0, 0, .32);
    backdrop-filter: blur(22px);
    -webkit-backdrop-filter: blur(22px);
}
.gp-pop-i {
    width: 100%;
    display: flex; align-items: center; gap: 8px;
    padding: 7px 10px;
    border: 0; border-radius: 8px;
    background: transparent;
    color: var(--gp-text-2);
    font-size: 12.5px; font-family: inherit;
    text-align: left; cursor: pointer;
    transition: background .14s, color .14s;
}
.gp-pop-i:hover { background: var(--gp-chip); color: var(--gp-text); }
.gp-pop-i.on { color: var(--gp-accent); font-weight: 600; }
.gp-pop-i .tx { flex: 1; }
.gp-pop-i .hn { font-size: 11px; color: var(--gp-muted); }
.gp-pop-i .ck { display: flex; }
.gp-pop-i .ck svg { width: 12px; height: 12px; }

/* ---------- 右：结果流 ---------- */
.gr-flow {
    min-width: 0;
    border: 1px solid var(--gp-line);
    border-radius: var(--gp-radius);
    background: var(--gp-card);
    box-shadow: var(--gp-shadow);
    overflow: hidden;
}
.gr-head {
    display: flex; align-items: center; gap: 9px;
    padding: 12px 15px;
    border-bottom: 1px solid var(--gp-line);
}
.gr-tt { font-size: 13.5px; font-weight: 600; color: var(--gp-text); }
.gr-count { font-size: 11.5px; color: var(--gp-muted); }
.gr-g { flex: 1; }

.gr-list { display: flex; flex-direction: column; gap: 12px; padding: 14px 15px 16px; }

.gr-card {
    border: 1px solid var(--gp-line);
    border-radius: 12px;
    background: var(--gp-soft);
    padding: 11px 12px 12px;
}
.gr-card.run { border-color: var(--gp-accent); box-shadow: 0 0 0 3px var(--gp-accent-soft); }

.gr-top { display: flex; align-items: center; gap: 8px; }
.gr-time { font-size: 11.5px; color: var(--gp-muted); }
.gr-st {
    padding: 1px 8px; border-radius: 999px;
    background: var(--gp-chip);
    color: var(--gp-muted);
    font-size: 11px;
}
.gr-st.success { background: var(--gp-ok-soft); color: var(--gp-ok); }
.gr-st.failed { background: var(--gp-danger-soft); color: var(--gp-danger); }
.gr-st.pending, .gr-st.processing { background: var(--gp-accent-soft); color: var(--gp-accent); }
.gr-del {
    width: 26px; height: 26px;
    display: inline-flex; align-items: center; justify-content: center;
    border: 0; border-radius: 7px;
    background: transparent; color: var(--gp-muted);
    cursor: pointer;
    transition: background .14s, color .14s;
}
.gr-del:hover { background: var(--gp-danger-soft); color: var(--gp-danger); }
.gr-del svg { width: 14px; height: 14px; }

.gr-bar { height: 3px; border-radius: 999px; background: var(--gp-chip); overflow: hidden; margin: 9px 0 1px; }
.gr-bar i { display: block; height: 100%; border-radius: 999px; background: var(--gp-accent); transition: width .4s ease; }

.gr-body {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    gap: 12px;
    align-items: start;
    margin-top: 10px;
}
.gr-shots { display: flex; gap: 6px; flex-wrap: wrap; }
.gr-shot {
    position: relative;
    width: 62px; height: 62px;
    border-radius: 9px; overflow: hidden;
    background: var(--gp-chip);
    cursor: zoom-in;
}
.gr-shot img, .gr-shot video { width: 100%; height: 100%; object-fit: cover; display: block; }
.gr-shot .pl {
    position: absolute; left: 50%; top: 50%;
    transform: translate(-50%, -50%);
    width: 22px; height: 22px; border-radius: 999px;
    display: flex; align-items: center; justify-content: center;
    background: rgba(0, 0, 0, .5); color: #fff;
}
.gr-shot .pl svg { width: 12px; height: 12px; }
.gr-wait {
    width: 62px; height: 62px; border-radius: 9px;
    display: flex; align-items: center; justify-content: center;
    background: var(--gp-chip);
    color: var(--gp-muted);
}

.gr-prompt {
    position: relative;
    min-width: 0;
    padding: 8px 34px 8px 10px;
    border: 1px solid var(--gp-line);
    border-radius: 9px;
    background: var(--gp-chip);
}
.gr-pr {
    font-size: 12px; line-height: 1.6;
    color: var(--gp-text-2);
    max-height: 62px;
    overflow: hidden;
    display: -webkit-box;
    -webkit-line-clamp: 4;
    -webkit-box-orient: vertical;
    word-break: break-word;
}
.gr-pr.dim { color: var(--gp-muted); }
.gr-copy {
    position: absolute; right: 5px; top: 5px;
    width: 24px; height: 24px;
    display: inline-flex; align-items: center; justify-content: center;
    border: 0; border-radius: 7px;
    background: transparent; color: var(--gp-muted);
    cursor: pointer;
    transition: background .14s, color .14s;
}
.gr-copy:hover { background: var(--gp-accent-soft); color: var(--gp-accent); }
.gr-copy svg { width: 13px; height: 13px; }

.gr-err { margin-top: 9px; font-size: 11.5px; line-height: 1.5; color: var(--gp-danger); }

.gr-empty { padding: 42px 20px; text-align: center; color: var(--gp-muted); font-size: 12.5px; }
.gr-empty .big { margin-bottom: 7px; font-size: 15px; font-weight: 600; color: var(--gp-text-2); }
.gr-tips {
    display: flex; flex-direction: column; gap: 7px;
    max-width: 420px; margin: 20px auto 0;
    text-align: left;
}
.gr-tips .tp { display: flex; gap: 8px; font-size: 12px; line-height: 1.55; }
.gr-tips .tp i { flex: none; color: var(--gp-accent); font-style: normal; }

/* ---------- 窄屏：参数列与结果流上下摞 ---------- */
@media (max-width: 1080px) {
    .gen-stage { grid-template-columns: minmax(0, 1fr); }
    .gp-dock { position: static; }
}
@media (max-width: 620px) {
    .gr-body { grid-template-columns: minmax(0, 1fr); }
    .gr-shots { width: 100%; }
}

/* ---- 模型摘要 / 创作技巧 ---- */
.gt-pair { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 1px; background: var(--gp-line); border: 1px solid var(--gp-line); border-radius: 10px; overflow: hidden; }
.gt-pair > div { background: var(--gp-soft); padding: 9px 12px; }
.gt-pair .k { font-size: 11.5px; color: var(--gp-muted); }
.gt-pair .v { font-size: 12.5px; font-weight: 600; color: var(--gp-text); margin-top: 2px; }
.gen-tips { display: flex; flex-direction: column; gap: 9px; }
.gen-tips .tp { display: flex; gap: 8px; font-size: 12.5px; color: var(--gp-text-2); line-height: 1.6; }
.gen-tips .tp i { color: var(--gp-accent); flex: none; font-style: normal; }

/* ---- 本次结果 ---- */
.gen-res { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 10px; }
.gen-res .ri {
    position: relative;
    border-radius: 10px; overflow: hidden;
    border: 1px solid var(--gp-line);
    background: var(--gp-soft);
    aspect-ratio: 1 / 1;
    cursor: zoom-in;
}
.gen-res .ri img, .gen-res .ri video { width: 100%; height: 100%; object-fit: cover; display: block; }
.gen-res .ri .badge {
    position: absolute; left: 7px; bottom: 7px;
    padding: 2px 7px; border-radius: 999px;
    background: rgba(0, 0, 0, .6); color: #fff; font-size: 10.5px;
}
.gen-empty { font-size: 12.5px; color: var(--gp-muted); }

/* @ 引用的小弹层：贴在提示词框左下角，列当前已上传的参考图 */
.gen-atpop {
    position: absolute;
    left: 12px;
    bottom: 34px;
    z-index: 6;
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    max-width: min(420px, 100%);
    padding: 8px;
    border: 1px solid var(--gp-line);
    border-radius: 10px;
    background: var(--gp-card);
    box-shadow: var(--gp-shadow);
}
.gen-atpop button {
    display: flex; flex-direction: column; align-items: center; gap: 3px;
    width: 56px; padding: 4px;
    border: 1px solid var(--gp-line);
    border-radius: 8px;
    background: var(--gp-soft);
    color: var(--gp-text-2);
    font-size: 10.5px;
    cursor: pointer;
}
.gen-atpop button:hover { border-color: var(--gp-accent); color: var(--gp-text); }
.gen-atpop img { width: 40px; height: 40px; object-fit: cover; border-radius: 5px; display: block; }

/* ==========================================================================
   §B 登录屏「暗色科幻」（2026-10-16 第六轮重做，用户点名 1:1 复刻参考站）

   版式：左侧品牌宣言 + 三张要能卡，右侧 Access Gateway 登录卡。
   这套皮肤**跟站点的浅/深主题是两条线**：
     · 深色主题 —— 纯科幻底（黑 + 三团霓虹 + 88px 网格），
       此时**不显示后台配的登录页背景图/视频**（.auth-bg 被藏起来）：
       那是浅色主题的观感资产，铺上来会把霓虹糊成脏紫，1:1 也就无从谈起。
     · 浅色主题 —— 照旧显示配的背景图 + 视频（v28 的行为），
       只是卡片换成了新版式，颜色全部走下面的 --lg-* 变量。
   ⚠️ 所以这一整块里**一个写死的白色都不许有**，全部走 --lg-*（同 §A 的理由）。
   ========================================================================== */

:root {
    --lg-fg: #ffffff;
    --lg-fg-2: rgba(255, 255, 255, .58);
    --lg-fg-3: rgba(255, 255, 255, .40);
    --lg-fg-4: rgba(255, 255, 255, .28);
    --lg-panel: rgba(255, 255, 255, .055);
    --lg-panel-2: rgba(0, 0, 0, .24);
    --lg-input: rgba(0, 0, 0, .28);
    --lg-line: rgba(255, 255, 255, .10);
    --lg-line-2: rgba(255, 255, 255, .12);
    --lg-card: var(--auth-card-bg);
    /* 「反色层上的文字」——目前只有分段标签的选中态用它（白药丸上的字）。
       ⚠️ 别拿 --lg-card 顶这个位：那是个**卡片底色**（半透明黑），
          压在白药丸上只是"灰白"，而且哪天卡片底改成实色就变成同色隐形。
          这里要的是参考站那个 `text-[#07070b]` —— 明确的反色前景色。 */
    --lg-fg-inv: #07070b;
    --lg-err: #fda4af;
    /* 主按钮的渐变：粉 → 橙，正好对上背景那两团霓虹 */
    --lg-btn-a: #ec4899;
    --lg-btn-b: #f97316;
    --lg-glow: rgba(236, 72, 153, .85);
    --lg-ring: rgba(255, 255, 255, .07);
    --lg-ring-2: rgba(236, 72, 153, .18);
    --lg-pill-fg: #f9a8d4;
    --lg-pill-bg: rgba(236, 72, 153, .12);
    --lg-pill-line: rgba(244, 114, 182, .24);
    /* 输入框聚焦：描边 + 外圈光。⚠️ 必须是变量：
       2026-10-15 之前这里是写死的粉（rgba(244,114,182,.6)）+ 一条浅色 override
       写死的卡其，浅色一改主色就会漏掉这一处。 */
    --lg-focus-line: rgba(244, 114, 182, .60);
    --lg-focus-ring: rgba(244, 114, 182, .16);
}

html[data-theme="light"] {
    --lg-fg: #1f2430;
    --lg-fg-2: #48505f;
    --lg-fg-3: #6b7484;
    --lg-fg-4: #8b94a4;
    --lg-panel: rgba(255, 255, 255, .60);
    --lg-panel-2: rgba(24, 32, 51, .05);
    --lg-input: rgba(255, 255, 255, .82);
    --lg-line: rgba(24, 32, 51, .12);
    --lg-line-2: rgba(24, 32, 51, .16);
    /* 浅色下分段标签的选中态是**深藏青药丸**，所以反色文字是白的 */
    --lg-fg-inv: #ffffff;
    --lg-err: #dc2626;
    /* 主按钮：**整站品牌蓝**（2026-10-15 第九轮；原来是照参考图抄的卡其 #8a7f5c，
       跟左侧功能栏的蓝对不上 —— 用户点名"像两个系统"。浅色下登录页是**同一个站**，
       按钮就不该是另一个色系） */
    --lg-btn-a: #4f6ef7;
    --lg-btn-b: #4059e0;
    --lg-glow: rgba(79, 110, 247, .30);
    --lg-ring: rgba(255, 255, 255, .55);
    --lg-ring-2: rgba(79, 110, 247, .22);
    --lg-pill-fg: #4f6ef7;
    --lg-pill-bg: rgba(79, 110, 247, .10);
    --lg-pill-line: rgba(79, 110, 247, .28);
    --lg-focus-line: rgba(79, 110, 247, .62);
    --lg-focus-ring: rgba(79, 110, 247, .16);
}

/* 深色下不显示后台配的登录页背景（理由见本节标题下的注释） */
html[data-theme="dark"] .auth-bg { display: none; }

.auth-screen { padding: 34px 26px; }

/* ---- 两列骨架 ---- */
.auth-grid {
    width: 100%;
    max-width: 1560px;
    display: grid;
    grid-template-columns: minmax(0, 1fr) 520px;
    gap: 72px;
    align-items: center;
    position: relative;
    z-index: 2;
}
@media (min-width: 1680px) {
    .auth-grid { grid-template-columns: minmax(0, 820px) 520px; gap: 132px; }
}
@media (max-width: 1100px) {
    .auth-grid { grid-template-columns: minmax(0, 1fr); gap: 0; max-width: 520px; }
    .auth-hero { display: none; }
}

/* ---- 顶上那两枚装饰标签 ---- */
.auth-deco { position: absolute; inset: 0; pointer-events: none; z-index: 1; }
.ad-tag { position: absolute; display: none; align-items: center; gap: 7px; font-size: 11.5px; color: var(--lg-fg-3); }
@media (min-width: 1280px) { .ad-tag { display: inline-flex; } }
.ad-tag.l { left: 41%; top: 30px; }
.ad-tag.r { right: 22px; top: 24px; }

/* ---- 左列：品牌宣言 ---- */
.auth-hero { min-width: 0; }
.ah-brand { display: flex; align-items: center; gap: 14px; }
.ah-logo {
    width: 52px; height: 52px; flex: none;
    border-radius: 16px;
    display: grid; place-items: center;
    color: #fff;
    font-family: Georgia, "Songti SC", "SimSun", serif;
    font-size: 23px; font-weight: 700;
}
.ah-name {
    font-family: Georgia, "Songti SC", "SimSun", serif;
    font-size: 23px; font-weight: 700; color: var(--lg-fg);
    letter-spacing: .01em;
}
.ah-sub { font-size: 12.5px; color: var(--lg-fg-3); margin-top: 2px; }

.ah-pill {
    display: inline-flex; align-items: center; gap: 7px;
    margin-top: 26px;
    padding: 5px 13px;
    border-radius: 999px;
    border: 1px solid var(--lg-pill-line);
    background: var(--lg-pill-bg);
    color: var(--lg-pill-fg);
    font-size: 12px;
}
.ah-pill i { width: 6px; height: 6px; border-radius: 50%; background: currentColor; display: block; }

.ah-title {
    margin: 18px 0 0;
    font-family: Georgia, "Songti SC", "SimSun", serif;
    font-size: clamp(38px, 5vw, 74px);
    font-weight: 700;
    line-height: 1.06;
    letter-spacing: .01em;
    color: var(--lg-fg);
}
.ah-title span { display: inline-block; }
.ah-lead {
    margin-top: 18px;
    max-width: 540px;
    font-size: 14.5px;
    line-height: 1.85;
    color: var(--lg-fg-2);
}

.ah-cards { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 14px; margin-top: 32px; }
.ah-card {
    position: relative;
    padding: 16px;
    border-radius: 22px;
    border: 1px solid var(--lg-line);
    background: var(--lg-panel);
    overflow: hidden;
    -webkit-backdrop-filter: blur(40px) saturate(150%);
    backdrop-filter: blur(40px) saturate(150%);
}
/* 每张卡自己一团味道 —— 粉 / 橙 / 青，跟背景那三团霓虹同源 */
.ah-card::before {
    content: '';
    position: absolute;
    width: 150px; height: 150px;
    right: -46px; top: -66px;
    border-radius: 50%;
    background: var(--ahc, rgba(236, 72, 153, .30));
    filter: blur(30px);
    pointer-events: none;
}
.ah-card .ahc-in {
    position: relative;
    border-radius: 14px;
    background: var(--lg-panel-2);
    padding: 10px 12px;
}
.ahc-en { font-size: 10.5px; letter-spacing: .18em; text-transform: uppercase; color: var(--lg-fg-3); }
.ahc-zh { margin-top: 5px; font-size: 13px; font-weight: 600; color: var(--lg-fg); line-height: 1.5; }
.ah-badge {
    display: inline-flex; align-items: center; gap: 5px;
    margin-top: 10px;
    padding: 2px 9px;
    border-radius: 999px;
    border: 1px solid rgba(110, 231, 183, .22);
    background: rgba(110, 231, 183, .10);
    color: #6ee7b7;
    font-size: 10.5px;
}
html[data-theme="light"] .ah-badge {
    border-color: rgba(18, 161, 80, .24);
    background: rgba(18, 161, 80, .10);
    color: #12a150;
}

/* ---- 右列：登录卡 ---- */
.auth-panel { position: relative; }
.ap-ring {
    position: absolute;
    left: 50%; top: 50%;
    border-radius: 50%;
    pointer-events: none;
    transform: translate(-50%, -50%) rotate(-11deg);
    width: 126%; height: 116%;
    border: 1px solid var(--lg-ring);
}
.ap-ring.r2 {
    width: 142%; height: 132%;
    transform: translate(-50%, -50%) rotate(8deg);
    border: 1px dashed var(--lg-ring-2);
}
.ap-dot {
    position: absolute;
    left: 50%; top: -6%;
    width: 8px; height: 8px; margin-left: -4px;
    border-radius: 50%;
    background: #fb923c;
    box-shadow: 0 0 16px 4px rgba(251, 146, 60, .8);
    pointer-events: none;
}

.auth-card {
    position: relative;
    padding: 22px 26px 26px;
    border-radius: 30px;
    border: 1px solid var(--lg-line-2);
    background: var(--lg-card);
    box-shadow: 0 36px 140px -46px var(--lg-glow), inset 0 1px 0 rgba(255, 255, 255, .10);
    -webkit-backdrop-filter: blur(40px) saturate(160%);
    backdrop-filter: blur(40px) saturate(160%);
    overflow: hidden;
    max-width: none;
    width: 100%;
}
/* 卡面上一道缓缓扫过的光 */
.ap-scan {
    position: absolute;
    left: 0; right: 0; top: 0; height: 130px;
    background: linear-gradient(180deg, rgba(255, 255, 255, .10), transparent);
    transform: translateY(-150%);
    animation: apScan 7s ease-in-out infinite;
    pointer-events: none;
}
@keyframes apScan {
    0%   { transform: translateY(-150%); }
    55%  { transform: translateY(430%); }
    100% { transform: translateY(430%); }
}
@media (prefers-reduced-motion: reduce) { .ap-scan { animation: none; opacity: 0; } }

.ap-head { display: flex; align-items: flex-start; gap: 12px; position: relative; }
.ap-en { font-size: 10.5px; letter-spacing: .24em; text-transform: uppercase; color: var(--lg-fg-3); }
.ap-zh {
    margin-top: 5px;
    font-family: Georgia, "Songti SC", "SimSun", serif;
    font-size: 27px; font-weight: 700; color: var(--lg-fg);
}
.ap-icon {
    width: 48px; height: 48px; flex: none;
    margin-left: auto;
    border-radius: 16px;
    border: 1px solid var(--lg-line);
    background: var(--lg-panel-2);
    color: var(--lg-fg);
    display: flex; align-items: center; justify-content: center;
}

.ap-tabs {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 8px;
    padding: 6px;
    margin: 20px 0;
    border-radius: 16px;
    border: 1px solid var(--lg-line);
    background: var(--lg-panel-2);
}
.ap-tabs button {
    height: 40px;
    border: 0;
    border-radius: 12px;
    background: transparent;
    color: var(--lg-fg-2);
    font-size: 13.5px; font-weight: 500;
    cursor: pointer;
    transition: background .16s, color .16s;
}
/* 选中态 = 反色药丸（深色主题下白底黑字，浅色主题下深藏青底白字）。
   ⚠️ 原来这里挂过一条 `@media (max-width: 1100px) { color: #07070b }`，
      那是把**浅色主题**的浅底文字硬刷成纯黑 —— 可浅色下药丸底是深藏青，
      黑字压深底直接糊掉。窄屏并不改变药丸颜色，那条 media query 纯属误伤，已删。 */
.ap-tabs button.active { background: var(--lg-fg); color: var(--lg-fg-inv); font-weight: 700; }

.ap-body { display: flex; flex-direction: column; gap: 13px; position: relative; }
.ap-fld label {
    display: flex; align-items: center; gap: 5px;
    font-size: 13px; font-weight: 500; color: var(--lg-fg-2);
    margin-bottom: 7px;
}
.ap-fld label .req { color: #f0736d; }
.ap-ipt { position: relative; }
.ap-ipt input {
    width: 100%;
    height: 50px;
    padding: 0 14px 0 44px;
    border-radius: 12px;
    border: 1px solid var(--lg-line);
    background: var(--lg-input);
    color: var(--lg-fg);
    font-size: 13.5px;
    outline: none;
    transition: border-color .16s, box-shadow .16s;
}
.ap-ipt input::placeholder { color: var(--lg-fg-4); }
.ap-ipt input:focus {
    border-color: var(--lg-focus-line);
    box-shadow: 0 0 0 3px var(--lg-focus-ring);
}
.ap-ipt .ic {
    position: absolute; left: 14px; top: 50%;
    transform: translateY(-50%);
    color: var(--lg-fg-3);
    display: flex;
    pointer-events: none;
}
.ap-ipt .eye {
    position: absolute; right: 7px; top: 50%;
    transform: translateY(-50%);
    width: 36px; height: 36px;
    border: 0; border-radius: 10px;
    background: transparent;
    color: var(--lg-fg-3);
    cursor: pointer;
    display: flex; align-items: center; justify-content: center;
}
.ap-ipt .eye:hover { color: var(--lg-fg); background: var(--lg-panel-2); }
.ap-ipt input.is-error { border-color: #f87171; }

.ap-row { display: flex; align-items: center; gap: 10px; }
.ap-forgot {
    margin-left: auto;
    font-size: 12px;
    color: var(--lg-fg-3);
    cursor: pointer;
    background: none; border: 0; padding: 0;
}
.ap-forgot:hover { color: var(--lg-fg); text-decoration: underline; }

.ap-submit {
    width: 100%;
    height: 50px;
    /* 上面那行「忘记密码？」只有 17px 高，间距给小了会贴到按钮边上 */
    margin-top: 9px;
    border: 0;
    border-radius: 12px;
    background: linear-gradient(96deg, var(--lg-btn-a), var(--lg-btn-b));
    color: #fff;
    font-size: 14.5px; font-weight: 600;
    cursor: pointer;
    box-shadow: 0 16px 42px -18px var(--lg-glow);
    transition: filter .16s, opacity .16s;
}
.ap-submit:hover:not(:disabled) { filter: brightness(1.08); }
.ap-submit:disabled { opacity: .6; cursor: not-allowed; }

.ap-note {
    margin-top: 14px;
    font-size: 11.5px;
    line-height: 1.7;
    color: var(--lg-fg-3);
    text-align: center;
}
.ap-note b { color: var(--lg-pill-fg); font-weight: 600; }
.ap-hint { font-size: 11.5px; line-height: 1.6; margin-top: 6px; }
.auth-card .err-text { color: var(--lg-err); font-size: 12px; margin-top: 6px; }
.auth-card .invite-note {
    display: flex; gap: 8px;
    padding: 10px 12px;
    border-radius: 12px;
    background: var(--lg-pill-bg);
    color: var(--lg-pill-fg);
    border: 1px solid var(--lg-pill-line);
    font-size: 12px;
    line-height: 1.6;
}

/* 深色下登录屏**不要自己的底色**：让 body::after 那层（黑底 + 三团霓虹 + 88px 网格）
   透上来，才跟参考站是同一块背景。浅色主题保持原样（反正有 .auth-bg 盖着）。 */
html[data-theme="dark"] .auth-screen { background: transparent; }

/* 大标题逐字淡入（authTitle() 给每个字挂了递增的 animation-delay） */
.ah-title span { animation: ahIn .72s cubic-bezier(.2, .7, .2, 1) both; }
@keyframes ahIn {
    from { opacity: 0; transform: translateY(.30em); }
    to   { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
    .ah-title span { animation: none; }
}

/* ⚠️ 浅色主题的左侧品牌区必须**自己托一层玻璃**：
   它底下是用户配的登录背景图（v28 那套），大字和说明直接压上去读不清
   （实测：大字还能认，下面那段 14.5px 的说明基本糊在照片里）。
   深色主题不需要 —— 那边是纯黑 + 霓虹，本身对比度就够，加了反而多一层框。 */
html[data-theme="light"] .auth-hero {
    padding: 28px 30px;
    border-radius: 30px;
    border: 1px solid rgba(255, 255, 255, .72);
    background: rgba(255, 255, 255, .60);
    box-shadow: 0 20px 56px rgba(24, 32, 51, .14);
    -webkit-backdrop-filter: blur(28px) saturate(150%);
    backdrop-filter: blur(28px) saturate(150%);
}
/* 那块白玻璃上，卡片要跟它分得开 —— 否则白卡压白玻璃，边界全糊掉 */
html[data-theme="light"] .ah-card {
    background: rgba(255, 255, 255, .55);
    border-color: rgba(24, 32, 51, .10);
}
html[data-theme="light"] .ahc-in { background: rgba(24, 32, 51, .045); }
/* 顶上那两枚装饰标签同理：白线状小字压在照片上等于隐形 */
html[data-theme="light"] .ad-tag {
    color: #48505f;
    padding: 4px 11px;
    border-radius: 999px;
    background: rgba(255, 255, 255, .68);
    border: 1px solid rgba(24, 32, 51, .10);
    -webkit-backdrop-filter: blur(14px);
    backdrop-filter: blur(14px);
}

/* ==========================================================================
   提示词反推页（2026-10-15 第三十九轮）
   --------------------------------------------------------------------------
   这一页的东西不多，但有两处是**踩过就忘不掉**的：

   ① 灰字必须托底：这页有整屏背景图，裸灰字压在照片上等于隐形。
      凡是没有卡片依附的说明文字，一律套 .glass-note（复用 v29 那个通用类）。
   ② 档位卡片是**可点的**，所以选中态要给足对比 —— 只换描边颜色不够，
      在浅色主题的毛玻璃上会看不出哪张被选中（描边细、颜色又接近）。
      这里选中态同时动描边 + 一圈光晕 + 左上角一枚小标。
   ========================================================================== */

/* ---------------- 素材选择区 ---------------- */

.rev-drop {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 10px;
    min-height: 168px;
    padding: 20px;
    border: 1.5px dashed var(--border-strong);
    border-radius: var(--radius-lg);
    background: var(--input-bg);
    cursor: pointer;
    text-align: center;
    transition: border-color .16s, background .16s, box-shadow .16s;
}
.rev-drop:hover,
.rev-drop.over {
    border-color: var(--accent);
    background: var(--accent-soft);
}
.rev-drop.over { box-shadow: 0 0 0 4px var(--accent-soft); }

/* 有素材之后不再走"虚线大空框"的观感 —— 那看着还像空的 */
.rev-drop.on {
    flex-direction: row;
    justify-content: flex-start;
    gap: 16px;
    min-height: 0;
    padding: 12px;
    border-style: solid;
    text-align: left;
}

.rev-preview {
    flex: none;
    width: 148px;
    height: 92px;
    border-radius: 12px;
    overflow: hidden;
    background: var(--media-bg);
    display: flex;
    align-items: center;
    justify-content: center;
}
.rev-preview img,
.rev-preview video {
    width: 100%;
    height: 100%;
    object-fit: contain;
    display: block;
}

.rev-file { flex: 1 1 auto; min-width: 0; }
.rev-file .t {
    font-size: 13.5px;
    font-weight: 600;
    color: var(--text);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.rev-file .s { margin-top: 5px; font-size: 12.5px; color: var(--text-2); }

.rev-dz-big { font-size: 15px; font-weight: 600; color: var(--text); }
.rev-dz-sub {
    max-width: 620px;
    font-size: 12.5px;
    color: var(--text-2);
    line-height: 1.85;
}

/* ---------------- 档位卡（在 .plan-card 上加的一点"可点"语气） ---------------- */

.rev-tier {
    cursor: pointer;
    transition: border-color .16s, box-shadow .16s, transform .16s;
}
.rev-tier:hover { transform: translateY(-1px); }
.rev-tier.on {
    border-color: var(--accent);
    box-shadow: 0 0 0 3px var(--accent-soft), var(--shadow-sm);
}
/* 选中标记（全站同一枚）：只靠描边在浅色毛玻璃上不够看，必须再给一枚 ✓。
   ⚠️ 2026-10-16 从**左上角**挪到**右上角** —— 左上角正压着卡片标题
      （用户截图点名：那枚蓝圆把「图片」两个字盖了一半）。 */
.rev-tier.on::after,
.plan-card.sel::after {
    content: "✓";
    position: absolute;
    right: 14px;
    top: 14px;
    width: 24px;
    height: 24px;
    line-height: 24px;
    text-align: center;
    border-radius: 999px;
    background: var(--accent);
    color: #fff;
    font-size: 13px;
    font-weight: 700;
    box-shadow: 0 3px 10px var(--accent-glow);
    animation: pickPop .18s ease-out;
}

/* 选中的那下「跳」出来：从 0.3 倍弹到原大小 */
@keyframes pickPop {
    from { transform: scale(.3); opacity: 0; }
    to   { transform: scale(1); opacity: 1; }
}

/* 系统开了「减弱动态效果」就别弹了，直接是最终样子 */
@media (prefers-reduced-motion: reduce) {
    .rev-tier.on::after,
    .plan-card.sel::after { animation: none; }
    .plan-grid .plan-card,
    .plan-grid .plan-card:hover,
    .plan-grid .plan-card.sel { transform: none; }
    .plan-grid .plan-card.sel { transition: none; }
}

/* ---------------- 结果区 ---------------- */

/* 提示词正文块：等宽 + 可选中 + 保留换行。
   上游给的是带时间轴的分镜稿，换行是**内容的一部分**，塌成一行就没法用了。 */
.rev-code {
    padding: 16px 18px;
    border-radius: 14px;
    background: var(--input-bg);
    border: 1px solid var(--glass-hair);
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
    font-size: 13px;
    line-height: 1.95;
    color: var(--text);
    white-space: pre-wrap;
    word-break: break-word;
    max-height: 420px;
    overflow: auto;
    user-select: text;
    -webkit-user-select: text;
}

.rev-slots {
    margin-top: 16px;
    padding: 14px 16px;
    border-radius: 14px;
    background: var(--panel);
    border: 1px solid var(--glass-hair);
}
.rev-slot {
    display: flex;
    align-items: baseline;
    gap: 10px;
    padding: 5px 0;
    font-size: 13px;
}
.rev-slot .k {
    flex: none;
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 12.5px;
    color: var(--accent);
    background: var(--accent-soft);
    border-radius: 7px;
    padding: 2px 8px;
}
.rev-slot .v { color: var(--text-2); line-height: 1.75; }

/* 四个出口：一排按钮，窄屏自动折行 */
.rev-out {
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
    margin-top: 18px;
}

/* ---------------- 历史条目 ---------------- */

.rev-item {
    display: flex;
    align-items: center;
    gap: 14px;
    padding: 11px 12px;
    border-radius: 14px;
    border: 1px solid transparent;
    transition: background .16s, border-color .16s;
}
.rev-item + .rev-item { margin-top: 6px; }
.rev-item:hover { background: var(--hover); }
.rev-item.on { border-color: var(--accent); background: var(--accent-soft); }

.rev-item .th {
    flex: none;
    width: 64px;
    height: 64px;
    border-radius: 11px;
    overflow: hidden;
    background: var(--media-bg);
    display: flex;
    align-items: center;
    justify-content: center;
}
.rev-item .th img,
.rev-item .th video { width: 100%; height: 100%; object-fit: cover; display: block; }
.rev-item .th .ph { font-size: 12px; color: var(--muted); }

.rev-item .mn { flex: 1 1 auto; min-width: 0; }
.rev-item .l1 {
    display: flex;
    align-items: center;
    gap: 9px;
    flex-wrap: wrap;
}
.rev-item .l1 .d { font-size: 12px; color: var(--muted); }
.rev-item .l2 {
    margin-top: 6px;
    font-size: 12.5px;
    color: var(--text-2);
    line-height: 1.7;
    overflow: hidden;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
}
.rev-item .ac { flex: none; display: flex; gap: 8px; }

@media (max-width: 720px) {
    .rev-drop.on { flex-direction: column; align-items: stretch; }
    .rev-preview { width: 100%; }
    .rev-item { flex-wrap: wrap; }
    .rev-item .ac { width: 100%; justify-content: flex-end; }
}
