/* ============================================================================
 *  TRIFRAME · 界面样式
 *  布局：左（预览舞台） / 右（功能面板）非对称栅格；舞台带氛围层与颗粒
 * ==========================================================================*/

* { box-sizing: border-box; }
[v-cloak] { opacity: 0; }

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

body {
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-body);
  font-size: var(--fs-base);
  line-height: 1.7;
  -webkit-font-smoothing: antialiased;
  overflow: hidden;
}

/* ---------- 氛围层：琥珀安全灯 + 冷色补光 + 颗粒 ---------- */
.atmos {
  position: fixed; inset: 0; pointer-events: none; z-index: 0;
  background:
    radial-gradient(52rem 34rem at 8% -12%, var(--accent-soft), transparent 62%),
    radial-gradient(42rem 30rem at 100% 4%, rgba(56, 189, 248, 0.10), transparent 58%),
    radial-gradient(38rem 26rem at 60% 108%, rgba(245, 165, 36, 0.07), transparent 60%);
}
.atmos::after {
  content: ""; position: absolute; inset: 0; opacity: var(--grain-opacity);
  mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}

/* ---------- 滚动条 ---------- */
::-webkit-scrollbar { width: 8px; height: 8px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--line-strong); border-radius: 8px; }
::-webkit-scrollbar-thumb:hover { background: var(--text-mute); }

/* ============================ 顶栏 ============================ */
.app { position: relative; z-index: 1; height: 100%; display: flex; flex-direction: column; }

/* 高度写成定值 + overflow:hidden：导出时才能把顶栏「压扁」成 0 并平滑过渡
   （isplay:none 是没有过渡的，画面会硬跳一下） */
.topbar {
  height: var(--header-h); flex: 0 0 var(--header-h);
  display: flex; align-items: center; gap: var(--sp-4);
  padding: 0 var(--sp-5);
  border-bottom: 1px solid var(--line);
  background: color-mix(in srgb, var(--surface) 82%, transparent);
  backdrop-filter: blur(8px);
  overflow: hidden;
  transition: height var(--dur-slow) var(--ease-out),
              flex-basis var(--dur-slow) var(--ease-out),
              padding var(--dur-slow) var(--ease-out),
              opacity 240ms ease;
}

.brand { display: flex; align-items: baseline; gap: 10px; min-width: 0; }
.brand-mark {
  font-family: var(--font-display); font-weight: 700; font-size: 20px;
  letter-spacing: -0.02em; color: var(--text);
}
.brand-mark em { font-style: normal; color: var(--accent); }
.brand-sub {
  font-family: var(--font-mono); font-size: var(--fs-xs); color: var(--text-mute);
  letter-spacing: 0.12em; text-transform: uppercase;
}
.brand-sub::before { content: ""; }
.topbar-spacer { flex: 1; }

/* 状态徽标 */
.chip {
  display: inline-flex; align-items: center; gap: 6px;
  height: 26px; padding: 0 10px; border-radius: 999px;
  border: 1px solid var(--line); background: var(--surface);
  font-family: var(--font-mono); font-size: var(--fs-xs); color: var(--text-dim);
  white-space: nowrap;
}
.chip .dot {
  width: 6px; height: 6px; border-radius: 50%; background: var(--text-mute);
  transition: background-color var(--dur-base) var(--ease-out), box-shadow var(--dur-base) var(--ease-out);
}
.chip.is-ok .dot { background: var(--ok); box-shadow: 0 0 8px var(--ok); }
.chip.is-warn .dot { background: var(--warn); box-shadow: 0 0 8px var(--warn); }
.chip.is-bad .dot { background: var(--danger); box-shadow: 0 0 8px var(--danger); }

/* ============================ 主体 ============================ */
/* 用 flex 而不是 grid：导出时右侧面板要「滑出去」并把舞台让成满屏，
   grid 的列宽不好做过渡，flex + 负 margin 可以平滑收起 */
.shell { flex: 1; display: flex; min-height: 0; overflow: hidden; }

/* ---------- 舞台 ---------- */
.stage {
  position: relative; flex: 1 1 auto; min-width: 0; overflow: hidden;
  display: flex; flex-direction: column;
  border-right: 1px solid var(--line);
}
/* 网格底改成铺在整个舞台上，跟随滚动也不会跑 */
.stage::before {
  content: ""; position: absolute; inset: 0; pointer-events: none; z-index: 0;
  background-image:
    linear-gradient(var(--stage-grid) 1px, transparent 1px),
    linear-gradient(90deg, var(--stage-grid) 1px, transparent 1px);
  background-size: 32px 32px;
  mask-image: radial-gradient(120% 100% at 50% 40%, #000 30%, transparent 78%);
}
.stage-head {
  position: relative; z-index: 2;
  height: 34px; flex: 0 0 34px; overflow: hidden;
  display: flex; align-items: center; gap: var(--sp-3);
  padding: 0 var(--sp-5); border-bottom: 1px solid var(--line);
  font-family: var(--font-mono); font-size: var(--fs-xs);
  color: var(--text-mute); letter-spacing: 0.08em; white-space: nowrap;
  transition: height var(--dur-slow) var(--ease-out),
              flex-basis var(--dur-slow) var(--ease-out),
              opacity 240ms ease;
}
.stage-body {
  position: relative; z-index: 1; flex: 1; min-height: 0;
  overflow: auto; overscroll-behavior: contain;
  padding: var(--sp-6);
  display: flex;
  cursor: default;
  scrollbar-width: thin;
  transition: padding var(--dur-slow) var(--ease-out);
}
.stage-body::-webkit-scrollbar { width: 10px; height: 10px; }
.stage-body::-webkit-scrollbar-thumb { background: var(--line-strong); border-radius: 999px; }
/* margin:auto：内容小于视口时居中，大于视口时可滚动到任意边缘 */
.stage-body > .stage-fit { margin: auto; }
/* 内容超出时进入平移模式：iframe 让出鼠标事件，才能在任何位置拖动画布 */
.stage-body.is-scrollable { cursor: grab; }
.stage-body.is-scrollable .dev-view { pointer-events: none; }
.stage-body.is-dragging { cursor: grabbing; user-select: none; }
.stage-body.is-dragging .dev-view { pointer-events: none; }

/* 外层按缩放后尺寸占位，保证 flex 居中按可视尺寸计算；内层做 transform 缩放。
   外层只过渡宽高（它的 transform 留给「分块拍摄」时的瞬时平移用，不能加过渡） */
.stage-fit { position: relative; transition: width var(--dur-slow) var(--ease-out), height var(--dur-slow) var(--ease-out); }
.stage-scale {
  position: absolute; left: 0; top: 0; transform-origin: top left;
  transition: transform var(--dur-slow) var(--ease-out);
}

/* ---------- 导出画布（预览所见即所得） ---------- */
.canvas {
  position: relative;
  box-shadow: 0 0 0 1px var(--line), 0 18px 48px rgba(0, 0, 0, 0.35);
  transition: background-color 320ms var(--ease-out),
              width var(--dur-slow) var(--ease-out),
              height var(--dur-slow) var(--ease-out);
}
/* 导出瞬间必须「切底色立刻生效」，不能带渐变 —— 否则透明背景合成会抓到中间色 */
.is-exporting .canvas { transition: none !important; }
/* 静默档：画布必须「立刻」就位，不能带着缩放过渡去量位置
   （高清 / 超清档要保留这段过渡，让收起面板 + 放大画布看起来是连贯的） */
.is-exporting:not(.is-capturing) .stage-scale,
.is-exporting:not(.is-capturing) .stage-fit,
.is-exporting:not(.is-capturing) .dev { transition: none !important; }
.canvas[data-bg="transparent"] {
  background-image:
    linear-gradient(45deg, rgba(140,140,140,0.14) 25%, transparent 25%, transparent 75%, rgba(140,140,140,0.14) 75%),
    linear-gradient(45deg, rgba(140,140,140,0.14) 25%, transparent 25%, transparent 75%, rgba(140,140,140,0.14) 75%);
  background-size: 24px 24px;
  background-position: 0 0, 12px 12px;
}
/* 导出透明背景时临时关掉棋盘格：它是「表示透明」的底纹，
   不能被拍进导出图，否则透明背景会变成一片灰色方格（见 app.js exportScreen） */
.canvas.is-plain-bg { background-image: none !important; }
/* 透明背景下不画设备投影。
   投影是「半透明黑」，铺在透明底上不会变成柔和的影子，只会变成一圈脏兮兮的暗色描边
   —— 存成 PNG 贴到浅色底上，看起来就像被人描了黑线。深色 / 亮色背景才有投影的意义。 */
.canvas[data-bg="transparent"] .dev,
.canvas[data-bg="transparent"] .dev[data-frame="monitor"]::after { box-shadow: none !important; }
.canvas[data-bg="transparent"] .dev-shadow { display: none; }

/* 背景纹理（与导出端 glow / grid / dots 对应） */
.canvas[data-tex]:not([data-tex="none"])::before {
  content: ""; position: absolute; inset: 0; pointer-events: none;
}
.canvas[data-tex="grid"]::before {
  background-image:
    linear-gradient(rgba(128,128,128,0.10) 1px, transparent 1px),
    linear-gradient(90deg, rgba(128,128,128,0.10) 1px, transparent 1px);
  background-size: 40px 40px;
}
.canvas[data-tex="dots"]::before {
  background-image: radial-gradient(rgba(128,128,128,0.20) 1.4px, transparent 1.5px);
  background-size: 26px 26px;
}
.canvas[data-tex="glow"]::before {
  background:
    radial-gradient(60% 55% at 12% 0%, rgba(245,165,36,0.16), transparent 62%),
    radial-gradient(55% 50% at 98% 96%, rgba(56,189,248,0.14), transparent 60%);
}
/* 尺寸全部由 canvasStyle 传入的变量驱动，随导出缩放同步变化 */
.canvas-head { position: absolute; }
.canvas-title {
  height: var(--lh-title, 44px); line-height: var(--lh-title, 44px);
  font-family: var(--font-display); font-weight: 700; font-size: var(--fs-title, 34px);
  letter-spacing: -0.02em; color: var(--ink); white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis;
}
.canvas-rule {
  width: var(--rule-w, 96px); height: var(--rule-h, 3px);
  margin-top: var(--rule-mt, 11px); background: var(--accent);
}
.canvas-sub {
  height: var(--lh-sub, 26px); line-height: var(--lh-sub, 26px);
  font-size: var(--fs-sub, 15px); color: var(--ink-dim);
}
.canvas-body { position: absolute; }
.canvas-foot {
  position: absolute; display: flex; align-items: flex-end; justify-content: space-between;
  font-family: var(--font-mono);
  font-size: var(--fs-foot, 13px); line-height: var(--lh-foot, 20px);
  color: var(--ink-dim);
}
.canvas-foot .canvas-brand {
  font-family: var(--font-display); font-weight: 700; letter-spacing: 0.04em; color: var(--accent);
}

/* ---------- 设备框（真实样机外观） ---------- */
.dev {
  position: absolute;
  border-radius: var(--dev-radius, 10px);
  background: transparent;
  /* 位置 / 尺寸都参与过渡：拖滑块、换预设、改留白时三台设备是「滑」到新位置的 */
  transition: left var(--dur-slow) var(--ease-out),
              top var(--dur-slow) var(--ease-out),
              width var(--dur-slow) var(--ease-out),
              height var(--dur-slow) var(--ease-out),
              border-radius var(--dur-base) var(--ease-out),
              box-shadow var(--dur-base) var(--ease-out),
              opacity 240ms ease;
}
/* 平板 / 手机：整块就是机身，正常填充 */
.dev[data-frame="tablet"],
.dev[data-frame="phone"] { background: #20252d; }
.dev[data-frame="browser"] { background: var(--surface-2); }
.dev[data-frame="device"] { background: var(--surface-3); border: 1px solid var(--line); }

/* 显示器：机身投影 + 支架 + 底座。
   整块 .dev 保持透明，只有「机身」那一截（高度减去支架）才是实色，
   支架 / 底座两侧露出画布背景 —— 否则屏幕下方会拖着一整块黑矩形。 */
.dev[data-frame="monitor"] { box-shadow: none; background: transparent; }
.dev[data-frame="monitor"] > .dev-inner {
  bottom: var(--stand-h, 0px);
  background: #20252d;
  border-radius: var(--dev-radius, 10px);
}
.dev.is-noshadow, .dev.is-noshadow[data-frame="monitor"]::after { box-shadow: none !important; }
.dev.is-noshadow .dev-shadow { display: none; }
.dev-shadow {
  position: absolute; left: 0; top: 0; width: 100%;
  height: calc(100% - var(--stand-h, 0px));
  border-radius: var(--dev-radius, 10px);
  box-shadow: 0 26px 70px rgba(15, 23, 42, 0.26), 0 8px 22px rgba(15, 23, 42, 0.14);
  pointer-events: none;
}
.dev.is-noshadow:not([data-frame="monitor"]) { box-shadow: none !important; }
/* 支架 / 底座都画在外框「内部」的预留空间里：
   .dev 的高度已由 layout 计入 pad.stand（见 app.js framePad），
   所以起点是 100% - stand-h，而不是 100%（后者会让支架脱落到外框下方，
   与外框之间露出一段空白，也和导出端 capture.js 的画法不一致）。 */
.dev[data-frame="monitor"]::before {
  content: ""; position: absolute; left: 50%; transform: translateX(-50%);
  top: calc(100% - var(--stand-h, 0px));
  width: 15%; height: calc(var(--stand-h, 0px) - var(--base-h, 14px));
  background: linear-gradient(90deg, #171b21, #2b323b 52%, #15191f);
}
.dev[data-frame="monitor"]::after {
  content: ""; position: absolute; left: 50%; transform: translateX(-50%);
  top: calc(100% - var(--base-h, 14px));
  width: 36%; height: var(--base-h, 14px);
  background: linear-gradient(180deg, #2b323b, #14181e);
  border-radius: 999px;
  box-shadow: 0 10px 26px rgba(15, 23, 42, 0.22);
}
.dev:not([data-frame="monitor"]) { box-shadow: 0 26px 70px rgba(15, 23, 42, 0.26), 0 8px 22px rgba(15, 23, 42, 0.14); }
:root[data-theme="light"] .dev:not([data-frame="monitor"]) {
  box-shadow: 0 26px 70px rgba(15, 23, 42, 0.26), 0 8px 22px rgba(15, 23, 42, 0.14);
}

.dev-inner {
  position: absolute; inset: 0; display: flex; flex-direction: column;
  overflow: hidden; border-radius: inherit;
  transition: bottom var(--dur-slow) var(--ease-out), background-color 320ms var(--ease-out);
}
.dev-bar {
  flex: 0 0 var(--bar-h, 0px); height: var(--bar-h, 0px);
  display: flex; align-items: center; gap: 7px; padding: 0 14px;
  border-bottom: 1px solid var(--line);
  background: color-mix(in srgb, var(--surface-3) 70%, transparent);
}
.dev-bar .dots { display: flex; gap: 6px; }
.dev-bar .dots i { width: 9px; height: 9px; border-radius: 50%; display: block; }
.dev-bar .dots i:nth-child(1) { background: #ff5f57; }
.dev-bar .dots i:nth-child(2) { background: #febc2e; }
.dev-bar .dots i:nth-child(3) { background: #28c840; }
.dev-bar .omnibox {
  flex: 1; min-width: 0; height: 62%; border-radius: 999px;
  background: var(--surface); border: 1px solid var(--line);
  display: flex; align-items: center; padding: 0 10px;
  font-family: var(--font-mono); font-size: 10px; color: var(--text-dim);
  font-variant-ligatures: none; letter-spacing: 0.02em;
  overflow: hidden; white-space: nowrap; text-overflow: ellipsis;
}
.dev-screen {
  position: relative; flex: 0 0 auto; overflow: hidden;
  width: var(--sw, 100%); height: var(--sh, 100%);
  margin: var(--mg-t, 0) var(--mg-r, 0) var(--mg-b, 0) var(--mg-l, 0);
  background: #fff;
  border-radius: var(--screen-r, 4px);
  transition: width var(--dur-slow) var(--ease-out),
              height var(--dur-slow) var(--ease-out),
              margin var(--dur-slow) var(--ease-out),
              border-radius var(--dur-base) var(--ease-out);
}
.dev-view {
  position: absolute; left: 0; top: 0; border: 0; display: block; background: #fff;
  transform-origin: top left; will-change: transform;
  transition: transform var(--dur-slow) var(--ease-out);
  animation: view-in 420ms var(--ease-out) both;
}
/* 新打开的网页淡入，避免白屏硬闪 */
@keyframes view-in { from { opacity: 0; } to { opacity: 1; } }
/* 导出时关掉 will-change：让浏览器按最终放大比例重新栅格化一次，文字才不会发虚 */
.is-exporting .dev-view { will-change: auto; }
/* 手机：灵动岛 */
.dev[data-frame="phone"] .dev-screen::after {
  content: ""; position: absolute; left: 50%; transform: translateX(-50%);
  top: 5.5%; width: 32%; height: 2.8%; min-height: 6px;
  background: #20252d; border-radius: 999px; z-index: 5;
}

.dev-tag {
  position: absolute; top: -26px; left: 0;
  height: 20px; padding: 0 9px; border-radius: 999px;
  background: var(--accent); color: var(--accent-ink);
  font-family: var(--font-display); font-weight: 600; font-size: 11px;
  display: flex; align-items: center; letter-spacing: 0.02em; white-space: nowrap;
}
.dev-size {
  position: absolute; padding: 2px 7px; border-radius: 999px;
  background: color-mix(in srgb, var(--surface) 82%, transparent);
  border: 1px solid var(--line);
  font-family: var(--font-mono); font-size: 10px; color: var(--text-dim);
}
.dev-size[data-pos="br"] { right: 8px; bottom: 8px; }
.dev-size[data-pos="bl"] { left: 8px; bottom: 8px; }
.dev-size[data-pos="tr"] { right: 8px; top: 8px; }
.dev-size[data-pos="tl"] { left: 8px; top: 8px; }

/* 空态 */
.stage-empty {
  position: relative; margin: auto;   /* 与 .stage-fit 一致：在预览区内水平垂直居中 */
  display: flex; flex-direction: column; align-items: center; gap: 14px;
  text-align: center; color: var(--text-mute);
}
.stage-empty .big {
  font-family: var(--font-display); font-weight: 700; font-size: 40px;
  letter-spacing: -0.03em; color: var(--text); line-height: 1.05;
}
.stage-empty .big em { font-style: normal; color: var(--accent); }
.stage-empty .sub { font-family: var(--font-mono); font-size: var(--fs-sm); max-width: 420px; }

/* 舞台底部工具条 */
.stage-foot {
  position: absolute; left: var(--sp-5); right: var(--sp-5); bottom: var(--sp-4);
  display: flex; align-items: center; gap: var(--sp-3);
  pointer-events: none;
  z-index: 3;                 /* 必须高于 .stage-body(z-index:1)，否则会被盖住点不到 */
  transition: opacity 240ms ease, transform var(--dur-base) var(--ease-out);
}
.stage-foot > * { pointer-events: auto; }
.zoom-pill {
  display: inline-flex; align-items: center; gap: 8px; height: 30px; padding: 0 10px;
  border-radius: 999px; border: 1px solid var(--line);
  background: color-mix(in srgb, var(--surface) 88%, transparent);
  backdrop-filter: blur(6px);
  font-family: var(--font-mono); font-size: var(--fs-xs); color: var(--text-dim);
}

/* ============================ 右侧面板 ============================ */
.panel {
  flex: 0 0 var(--panel-w); width: var(--panel-w);
  min-width: 0; display: flex; flex-direction: column;
  background: var(--bg-2); overflow: hidden;
  /* 导出时靠「平移 + 负外边距」把面板滑出去、把舞台让成满屏 */
  transition: transform var(--dur-slow) var(--ease-out),
              margin-right var(--dur-slow) var(--ease-out),
              margin-bottom var(--dur-slow) var(--ease-out),
              flex-basis var(--dur-slow) var(--ease-out),
              height var(--dur-slow) var(--ease-out),
              opacity 260ms ease;
}
.step-rail {
  display: grid; grid-template-columns: repeat(3, 1fr);
  border-bottom: 1px solid var(--line);
}
.step-item {
  position: relative; padding: 12px 10px 10px; cursor: pointer;
  border-right: 1px solid var(--line);
  transition: background var(--dur-fast) ease;
  user-select: none;
}
.step-item:last-child { border-right: 0; }
.step-item:hover { background: var(--surface); }
.step-item .num {
  font-family: var(--font-mono); font-size: 10px; color: var(--text-mute);
  letter-spacing: 0.14em; display: block;
}
.step-item .ttl {
  font-family: var(--font-display); font-weight: 600; font-size: var(--fs-md);
  color: var(--text-dim); letter-spacing: -0.01em; margin-top: 2px;
}
.step-item.is-active { background: var(--surface); }
.step-item.is-active .ttl { color: var(--text); }
.step-item.is-active .num { color: var(--accent); }
.step-item.is-done .num { color: var(--ok); }
.step-item::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: -1px; height: 2px;
  background: var(--accent); transform: scaleX(0); transform-origin: left;
  transition: transform var(--dur-base) var(--ease-out);
}
.step-item.is-active::after { transform: scaleX(1); }

.panel-scroll {
  position: relative;                 /* 切步骤时旧内容要脱离文档流，才能淡出得干净 */
  flex: 1 1 0; min-height: 0; height: 100%;
  overflow-y: auto; overflow-x: hidden;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
  padding: var(--sp-4) var(--sp-5) var(--sp-6);
}
/* 三步之间的切换：旧的向上淡出（脱离流），新的从下方淡入 */
.sec-enter-active { transition: opacity 280ms ease, transform var(--dur-base) var(--ease-out); }
/* 正在离场的旧步骤脱离文档流，纵向起点与内容区对齐（绝对定位相对的是 padding box） */
.sec-leave-active {
  position: absolute; left: var(--sp-5); right: var(--sp-5); top: var(--sp-4); z-index: 1;
  pointer-events: none;
  transition: opacity 200ms ease, transform 240ms var(--ease-out);
}
.sec-enter-from { opacity: 0; transform: translateY(10px); }
.sec-leave-to { opacity: 0; transform: translateY(-8px); }

/* 空态 ↔ 预览 */
.fade-enter-active, .fade-leave-active {
  transition: opacity 240ms ease, transform var(--dur-base) var(--ease-out);
}
.fade-enter-from, .fade-leave-to { opacity: 0; transform: translateY(10px) scale(0.985); }
/* 面板滚动条常显，避免「看不到能滚动」 */
.panel-scroll::-webkit-scrollbar { width: 10px; }
.panel-scroll::-webkit-scrollbar-track { background: color-mix(in srgb, var(--surface) 60%, transparent); }
.panel-scroll::-webkit-scrollbar-thumb { background: var(--line-strong); border-radius: 999px; }
.panel-scroll { scrollbar-width: thin; scrollbar-color: var(--line-strong) transparent; }

.sec { margin-bottom: var(--sp-6); }
.sec-head {
  display: flex; align-items: center; gap: 8px; margin-bottom: var(--sp-3);
}
.sec-head h3 {
  margin: 0; font-family: var(--font-display); font-weight: 600; font-size: var(--fs-md);
  letter-spacing: -0.01em; color: var(--text);
}
.sec-head .rule { flex: 1; height: 1px; background: var(--line); }
.sec-head .kicker {
  font-family: var(--font-mono); font-size: 10px; letter-spacing: 0.16em;
  color: var(--text-mute); text-transform: uppercase;
}

.field { margin-bottom: var(--sp-4); }
.field-label {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  font-size: var(--fs-sm); color: var(--text-dim); margin-bottom: 6px;
}
.field-label .mono { font-family: var(--font-mono); font-size: 10px; color: var(--text-mute); }
.row { display: flex; align-items: center; gap: var(--sp-2); }
.row > .grow { flex: 1; min-width: 0; }
.grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-2); }
.grid-2 > * { min-width: 0; }   /* 允许内部控件随列宽收缩，窄屏不撑破面板 */
.hint { font-size: var(--fs-xs); color: var(--text-mute); line-height: 1.6; margin-top: 6px; }
/* 提示条：自动切换预览方式、打开失败等消息（文案面向普通用户） */
.note {
  margin-top: 8px; padding: 8px 10px; border-radius: var(--r-sm);
  border: 1px solid var(--line); background: var(--surface);
  font-size: var(--fs-xs); line-height: 1.6; color: var(--text-dim);
}
.note.is-bad { border-color: rgba(248, 113, 113, 0.45); color: var(--danger); }

/* 预设尺寸胶囊 */
.preset-wrap { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
.preset {
  font-family: var(--font-mono); font-size: 10px; color: var(--text-dim);
  padding: 4px 8px; border-radius: var(--r-sm);
  border: 1px solid var(--line); background: var(--surface);
  cursor: pointer; transition: all var(--dur-fast) ease;
}
.preset:hover { color: var(--text); border-color: var(--line-strong); transform: translateY(-1px); }
.preset.is-on { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); }

/* 设备卡 */
.devcard {
  border: 1px solid var(--line); border-radius: var(--r-md);
  background: var(--surface); padding: var(--sp-3);
  margin-bottom: var(--sp-3);
}
.devcard-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px; }
.devcard-name {
  font-family: var(--font-display); font-weight: 600; font-size: var(--fs-md);
  letter-spacing: -0.01em;
}
.devcard-dim { font-family: var(--font-mono); font-size: 10px; color: var(--text-mute); }

/* 导出区：面板底部常驻，不参与滚动 */
.export-bar {
  flex: 0 0 auto;
  padding: var(--sp-4) var(--sp-5);
  border-top: 1px solid var(--line);
  background: color-mix(in srgb, var(--bg-2) 94%, transparent);
  backdrop-filter: blur(10px);
  display: flex; flex-direction: column; gap: var(--sp-2);
  box-shadow: 0 -12px 28px rgba(0, 0, 0, 0.18);
  transition: box-shadow var(--dur-base) var(--ease-out);
}
/* 导出中：导出区整体「点亮」，其余部分变暗，视线自然落在这条进度上 */
.export-bar.is-busy { box-shadow: 0 -12px 28px rgba(0, 0, 0, 0.18), 0 0 0 1px var(--accent) inset; }

/* ---------- 导出进度条（就在导出按钮上方） ---------- */
.export-progress { display: flex; flex-direction: column; gap: 6px; margin-bottom: 2px; }
.export-progress .ep-head {
  display: flex; align-items: baseline; justify-content: space-between; gap: 8px;
}
.export-progress .ep-stage {
  font-size: var(--fs-xs); color: var(--text-dim);
  display: inline-flex; align-items: center; gap: 6px;
}
.export-progress .ep-dot {
  width: 6px; height: 6px; border-radius: 50%; background: var(--accent);
  animation: ep-pulse 1.1s ease-in-out infinite;
}
@keyframes ep-pulse {
  0%, 100% { opacity: 1; transform: scale(1); }
  50% { opacity: 0.35; transform: scale(0.6); }
}
.export-progress .ep-pct {
  font-family: var(--font-mono); font-size: var(--fs-xs); color: var(--accent);
  font-variant-numeric: tabular-nums;
}
.export-progress .ep-track {
  position: relative; height: 5px; border-radius: 999px; overflow: hidden;
  background: var(--surface-3);
}
.export-progress .ep-track > i {
  display: block; height: 100%; border-radius: 999px;
  background: linear-gradient(90deg, var(--accent), var(--accent-2));
  transition: width 260ms var(--ease-out);
}
/* 走到 100% 之前不要完全填满，免得看起来像卡住了 */
.export-progress .ep-track > i::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.35), transparent);
  animation: ep-shine 1.4s linear infinite;
}
@keyframes ep-shine { from { transform: translateX(-100%); } to { transform: translateX(100%); } }

/* ---------- 拍摄期间浮在最底部的进度条 ----------
   拍摄时面板整个滑走了，面板里那条进度条跟着看不见；
   这条固定在窗口底部「专用带」里，全程可见，而且不在拍摄区域内。 */
/* 高度必须 ≤ 48 - bottom，否则会伸进拍摄区域被拍进去（app.js 的 CAP_BAR 也依赖它）。
   当前：4(下边距) + 5+12+3+3+6 + 2(上下边框) ≈ 35px，留足余量。 */
.float-progress {
  position: fixed; left: 50%; transform: translateX(-50%);
  bottom: 4px; z-index: 60;
  width: min(420px, 60vw);
  display: flex; flex-direction: column; gap: 3px;
  padding: 5px 12px 6px;
  border-radius: 999px;
  border: 1px solid var(--line);
  background: color-mix(in srgb, var(--surface) 92%, transparent);
  backdrop-filter: blur(8px);
  box-shadow: var(--shadow-2);
  animation: fp-in 260ms var(--ease-out) both;
}
@keyframes fp-in { from { opacity: 0; transform: translate(-50%, 10px); } to { opacity: 1; transform: translate(-50%, 0); } }
.float-progress .fp-head {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  font-size: 10px; line-height: 12px; color: var(--text-dim);
}
.float-progress .fp-pct {
  font-family: var(--font-mono); color: var(--accent); font-variant-numeric: tabular-nums;
}
.float-progress .fp-track {
  position: relative; height: 3px; border-radius: 999px; overflow: hidden; background: var(--surface-3);
}
.float-progress .fp-track > i {
  display: block; height: 100%; border-radius: 999px;
  background: linear-gradient(90deg, var(--accent), var(--accent-2));
  transition: width 260ms var(--ease-out);
}
@media (prefers-reduced-motion: reduce) { .float-progress { animation: none; } }

/* 导出中：整个界面锁住（除了进度条本身），任何按钮都点不动、任何内容都改不了。
   inert 由 JS 挂在 #app 上，这里只做视觉上的「锁住」提示。 */
.is-exporting .panel-scroll,
.is-exporting .step-rail,
.is-exporting .topbar { opacity: 0.55; filter: saturate(0.6); pointer-events: none; }
.is-exporting .panel-scroll,
.is-exporting .step-rail,
.is-exporting .topbar,
.is-exporting .export-bar > .row { transition: opacity 240ms ease, filter 240ms ease; }
.is-exporting .export-bar > .row { opacity: 0.5; }
@media (prefers-reduced-motion: reduce) {
  .export-progress .ep-dot, .export-progress .ep-track > i::after { animation: none; }
}

/* 开关行 */
.sw-row {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  padding: 7px 0; border-bottom: 1px dashed var(--line);
}
.sw-row:last-child { border-bottom: 0; }
.sw-row .lbl { font-size: var(--fs-base); color: var(--text-dim); }
.sw-row .lbl small { display: block; font-size: 10px; color: var(--text-mute); font-family: var(--font-mono); }

/* Element Plus 局部校准 */
.el-input__wrapper, .el-select__wrapper, .el-textarea__inner {
  background: var(--surface) !important;
  box-shadow: 0 0 0 1px var(--line) inset !important;
  border-radius: var(--r-md) !important;
}
.el-input__wrapper.is-focus, .el-select__wrapper.is-focused {
  box-shadow: 0 0 0 1px var(--accent) inset !important;
}
.el-input__inner {
  font-family: var(--font-mono); font-size: var(--fs-sm);
  /* 禁用连字并微调字距：确保 https:// 的双斜杠清晰可辨，不被连成一个字形 */
  font-variant-ligatures: none;
  font-feature-settings: "liga" 0, "calt" 0;
  letter-spacing: 0.02em;
}
.el-button { font-family: var(--font-display); font-weight: 600; letter-spacing: -0.01em; border-radius: var(--r-md) !important; }
.el-slider__runway { background: var(--surface-3) !important; }
.el-divider { border-color: var(--line); margin: var(--sp-4) 0; }
.el-divider__text { background: var(--bg-2); color: var(--text-mute); font-family: var(--font-mono); font-size: 10px; letter-spacing: 0.14em; }
.el-radio-button__inner { font-family: var(--font-display); font-weight: 600; }
.el-switch__core { border-color: var(--line-strong) !important; }

/* ============ 导出中的两种状态 ============
   .is-exporting —— 只要在导出就一定挂上：藏浮层、冻住网页、锁住界面。
   .is-capturing —— 只有「高清 / 超清」（需要分块拍摄）才挂上：收起界面让画布铺满窗口。
   默认的「静默」档不挂 .is-capturing，所以界面一个像素都不会动，
   用户只在导出按钮上方看到一条进度条。 */

/* ---- 导出中：藏掉工具自己的浮层 ----
   它们是 position:fixed，正好压在画布上，会被一起拍进导出图。
   Element Plus 的消息挂在 document.body 上，所以状态类也要挂到 body。 */
.is-exporting .el-message,
.is-exporting .el-message-box__wrapper,
.is-exporting .el-notification,
.is-exporting .el-overlay,
.is-exporting .el-loading-mask { display: none !important; }

/* ---- 导出中：预览区不接指针事件 ----
   导出按钮在右侧面板，拍的时候鼠标往往正好压在画布 / 某个 iframe 上，
   网页里的 :hover（文字变色、图标放大、弹出菜单）会被一起拍进导出图。
   关掉命中测试后，里面已经生效的 hover 状态也会被浏览器清掉。 */
.is-exporting .stage-body,
.is-exporting .dev,
.is-exporting .dev-view { pointer-events: none !important; }

/* 透明背景导出用的两块底色。
   必须写成 !important 的类，不能靠写内联样式：
   .canvas 的 :style 由 Vue 托管，导出期间任何一次重渲染都会把它整个重写一遍，
   内联覆盖会被抹掉 → 底色回到 transparent → 画布背后的舞台（深色 + 网格 + 氛围光）
   透出来，而且两帧底色一样「没变化」，合成时被当成不透明内容烤进导出图。 */
.is-exporting .canvas.is-backdrop-dark {
  background-color: #000 !important;
  background-image: none !important;
}
.is-exporting .canvas.is-backdrop-light {
  background-color: #fff !important;
  background-image: none !important;
}

/* ---- 高清 / 超清：整屏沉浸模式 ----
   分块拍摄必须让画布大到铺满（甚至超出）窗口，所以这里把界面收起来。
   全部用「可过渡」的属性收起，一律不用 display:none —— 那会让画面硬跳一下。 */
.is-capturing .topbar { height: 0; flex-basis: 0; padding-top: 0; padding-bottom: 0; opacity: 0; border-bottom-width: 0; }
.is-capturing .panel {
  transform: translateX(100%);
  margin-right: calc(-1 * var(--panel-w));
  opacity: 0; pointer-events: none;
}
.is-capturing .stage-head { height: 0; flex-basis: 0; opacity: 0; border-bottom-width: 0; }
.is-capturing .stage-foot { opacity: 0; transform: translateY(14px); }
.is-capturing .stage { border-right: 0; }
/* 窗口底部预留一条 48px 的「进度条专用带」（要与 app.js 里的 CAP_BAR 一致）。
   拍摄区域 = .stage-body 的可视框，永远不含这条带子，
   所以浮在带子里的进度条既看得见、又绝不可能被拍进导出图。 */
.is-capturing .stage { padding-bottom: 48px; }
.is-capturing .stage-body { padding: 0; overflow: hidden; }
/* 画布被放大到窗口之外时禁止出现滚动条，滚动条会吃掉画面宽度 */
.is-capturing .stage-body::-webkit-scrollbar { width: 0; height: 0; }

/* ============================ 入场序列 ============================ */
.reveal { opacity: 0; transform: translateY(14px); }
.reveal.is-in {
  opacity: 1; transform: none;
  transition: opacity var(--dur-base) var(--ease-out), transform var(--dur-base) var(--ease-out);
}
.reveal:nth-child(2) { transition-delay: 70ms; }
.reveal:nth-child(3) { transition-delay: 140ms; }
.reveal:nth-child(4) { transition-delay: 210ms; }

/* ---------- 主题切换：给几百毫秒的「全局换色」窗口，之后立刻撤掉 ----------
   不常驻是因为它会盖掉各元素自己的位移 / 缩放过渡。 */
html.theme-anim,
html.theme-anim *,
html.theme-anim *::before,
html.theme-anim *::after {
  transition: background-color 320ms var(--ease-out),
              border-color 320ms var(--ease-out),
              color 320ms var(--ease-out),
              fill 320ms var(--ease-out),
              box-shadow 320ms var(--ease-out) !important;
}

@media (prefers-reduced-motion: reduce) {
  .reveal { opacity: 1 !important; transform: none !important; transition: none !important; }
  * { animation-duration: 0.001ms !important; transition-duration: 0.001ms !important; }
}

/* ============================ 响应式 ============================ */
@media (max-width: 1280px) {
  :root { --panel-w: 360px; }
}
@media (max-width: 1024px) {
  /* 上下堆叠布局：压缩舞台高度，给下方配置面板留出足够可视空间 */
  .shell { flex-direction: column; }
  .stage { flex: 1 1 auto; border-right: 0; border-bottom: 1px solid var(--line); }
  .panel { flex: 1 1 0; width: auto; }
  /* 竖排时面板改成「向下滑出 + 高度收拢」 */
  .is-capturing .panel {
    transform: translateY(100%);
    margin-right: 0; flex: 0 0 0; height: 0;
  }
}
@media (max-width: 640px) {
  .topbar { padding: 0 var(--sp-3); gap: var(--sp-2); }
  .brand-sub { display: none; }
  /* 手机上舞台进一步压缩，避免下方列表被挤到只剩一条缝 */
  .stage { flex: 1 1 auto; }
  .stage-empty .big { font-size: 30px; }
  .panel-scroll { padding: var(--sp-3) var(--sp-4) var(--sp-6); }
  .export-bar { padding: var(--sp-3) var(--sp-4); }
}

/* ---------- 主题切换按钮 ---------- */
.theme-btn {
  width: 34px; height: 34px; border-radius: 999px; cursor: pointer;
  border: 1px solid var(--line); background: var(--surface);
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--text-dim); transition: all var(--dur-fast) ease;
}
.theme-btn:hover { color: var(--text); border-color: var(--accent); }
.theme-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
