/* ==========================================================================
   无限画布页面样式
   ========================================================================== */

html, body { height: 100%; overflow: hidden; }

.app {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  height: 100vh;
  /* 手机上 100vh 是「浏览器工具栏收起时」的高度，底部内容会被地址栏/工具栏盖住。
     100dvh 跟随实际可视区域，这里给支持它的浏览器覆盖掉。 */
  height: 100dvh;
}

/* -------------------------------- 顶部导航 -------------------------------- */
.topbar {
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 12px 20px;
  /* 避开刘海 / 状态栏（需 viewport-fit=cover，见各页面的 viewport meta） */
  padding-top: calc(12px + env(safe-area-inset-top, 0px));
  padding-left: calc(20px + env(safe-area-inset-left, 0px));
  padding-right: calc(20px + env(safe-area-inset-right, 0px));
  border-radius: 0 0 2rem 2rem;
  position: relative;
  z-index: 30;
  box-shadow: 0 8px 24px -18px rgba(92, 64, 51, 0.45);
  flex-shrink: 0;
}

.brand { display: flex; align-items: center; gap: 12px; min-width: 0; text-decoration: none; }

.canvas-meta {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-left: auto;
  min-width: 0;
}

.canvas-title-input {
  font-family: Georgia, "Songti SC", serif;
  font-size: 15px;
  color: #5c4033;
  background: transparent;
  border: 1px solid transparent;
  border-radius: 999px;
  padding: 8px 16px;
  min-width: 160px;
  max-width: 320px;
  transition: all 300ms var(--no-ease);
}

.canvas-title-input:hover { background: #f2ece3; }
.canvas-title-input:focus {
  outline: none;
  background: #fffdf9;
  border-color: #d4a373;
  box-shadow: 0 0 0 3px rgba(139, 157, 119, 0.2);
}

.save-state {
  font-size: 12px;
  color: #6b5d51;
  background: #f2ece3;
  border-radius: 999px;
  padding: 6px 14px;
  white-space: nowrap;
  transition: color 500ms var(--no-ease), background 500ms var(--no-ease);
}

.save-state.is-saving { color: #6f8160; background: rgba(139, 157, 119, 0.18); }
.save-state.is-error { color: #8c4a34; background: rgba(212, 163, 115, 0.22); }

.topbar-actions { display: flex; align-items: center; gap: 8px; }
.topbar-sep { width: 1px; height: 24px; background: #e8ded2; }

/* --------------------------------- 画布 ---------------------------------- */
.board {
  position: relative;
  flex: 1;
  overflow: hidden;
  cursor: default;
  outline: none;
  background: #faf6f1;
  /* 触摸手势由 JS 接管（单指平移、双指捏合缩放）。
     不写这一句的话，浏览器会把"立即拖动"判定成滚动手势并发出 pointercancel，
     拖动会被吞掉 —— 表现为手指划过去毫无反应。
     注意 touch-action 不继承，所以资产抽屉等子元素仍可正常滚动。 */
  touch-action: none;
}

.board.is-panning { cursor: grabbing; }
.board.is-marqueeing { cursor: crosshair; }
.board.is-linking { cursor: crosshair; }
.board.is-armed { cursor: grab; }

.board-texture {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background-image: radial-gradient(circle, rgba(140, 118, 98, 0.34) 1.2px, transparent 1.2px);
  background-size: 26px 26px;
  background-position: 0 0;
}

.blob-one { width: 380px; height: 340px; background: rgba(139, 157, 119, 0.28); top: -140px; left: -120px; }
.blob-two { width: 300px; height: 300px; background: rgba(212, 163, 115, 0.24); bottom: -130px; right: -90px; animation-delay: -8s; }

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

.link-layer {
  position: absolute;
  top: 0;
  left: 0;
  overflow: visible;
  pointer-events: none;
}

/* 加宽的透明描边只用来接收点击，避免连线细到点不中 */
.link-layer .link-hit {
  fill: none;
  stroke: transparent;
  stroke-width: 16;
  stroke-linecap: round;
  pointer-events: stroke;
  cursor: pointer;
}

.link-layer .link-path {
  fill: none;
  stroke: #b09a86;
  stroke-width: 2.5;
  pointer-events: none;
  transition: stroke 500ms var(--no-ease), stroke-width 500ms var(--no-ease);
}

.link-layer .link-path.is-active { stroke: #8b9d77; }
.link-layer .link-path.is-selected { stroke: #5c4033; stroke-width: 3.5; }

/* 连线控制层：删除按钮用 HTML 元素，便于 hover 命中与样式过渡 */
.link-controls {
  position: absolute;
  top: 0;
  left: 0;
  pointer-events: none;
  z-index: 2;
}

.link-delete-btn {
  position: absolute;
  width: 24px;
  height: 24px;
  padding: 0;
  border-radius: 999px;
  display: flex;
  align-items: center;
  justify-content: center;
  transform: translate(-50%, -50%);
  font-family: inherit;
  font-size: 15px;
  line-height: 1;
  color: #6b5d51;
  background: rgba(255, 253, 249, 0.96);
  border: 1px solid #e8ded2;
  box-shadow: 0 8px 18px -12px rgba(92, 64, 51, 0.55);
  opacity: 0;
  pointer-events: none;
  cursor: pointer;
  transition: opacity 160ms var(--no-ease), color 160ms var(--no-ease),
    background 160ms var(--no-ease), border-color 160ms var(--no-ease), transform 160ms var(--no-ease);
}

.link-delete-btn.is-visible { opacity: 1; pointer-events: auto; }
.link-delete-btn.is-hover { color: #a8452c; border-color: #e3b7a6; background: #fdf3ef; }
.link-delete-btn:hover { transform: translate(-50%, -50%) scale(1.12); }

.node-layer { position: absolute; top: 0; left: 0; }

/* --------------------------------- 节点 ---------------------------------- */
.node {
  position: absolute;
  display: flex;
  flex-direction: column;
  background: #fffdf9;
  border: 1px solid #e8ded2;
  border-radius: 1.6rem;
  box-shadow: 0 8px 24px -20px rgba(92, 64, 51, 0.5);
  transition: box-shadow 500ms var(--no-ease), border-color 500ms var(--no-ease), transform 500ms var(--no-ease);
  user-select: none;
}

.node:hover { border-color: #d4a373; }

.node.is-selected {
  border-color: #8b9d77;
  box-shadow: 0 0 0 3px rgba(139, 157, 119, 0.28), 0 8px 24px -20px rgba(92, 64, 51, 0.5);
}

.node.is-dragging { transition: none; }

.node-head {
  display: flex;
  align-items: center;
  gap: 8px;
  flex: 0 0 auto;
  padding: 8px 12px;
  border-bottom: 1px solid #efe7dc;
  border-radius: 1.6rem 1.6rem 0 0;
  background: rgba(139, 157, 119, 0.12);
}

.node-head svg { width: 14px; height: 14px; color: #6f8160; }

.node-title {
  font-family: Georgia, "Songti SC", serif;
  font-size: 12px;
  color: #5c4033;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  flex: 1;
  cursor: text;
}

.node-body {
  position: relative;
  flex: 1 1 auto;
  min-height: 0; /* 允许在固定高度的节点内收缩，overflow 才能生效 */
  overflow: hidden;
  border-radius: 0 0 1.6rem 1.6rem;
}

.node-image {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* 需要接收指针事件才能"单击放大/标注"。
     节点拖拽靠 closest('.node') 命中，不受影响。 */
  pointer-events: auto;
  cursor: zoom-in;
  background: #f2ece3;
}

/* 空图片节点：两个并列按钮（选图 / 白纸涂鸦） */
.node-image-empty {
  display: flex;
  flex-direction: column;
  gap: 8px;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100%;
  padding: 10px;
  border: 1px dashed #c9b9a3;
  border-radius: 6px;
  background: #f2ece3;
}

.node-image-empty-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 12px;
  border: 1px solid #d8c9b4;
  border-radius: 6px;
  background: #fffdf9;
  color: #5f5346;
  font-size: 12px;
  font-family: inherit;
  cursor: pointer;
  transition: background 0.15s, border-color 0.15s;
}

.node-image-empty-btn:hover {
  background: #efe7db;
  border-color: #a3937f;
}

.node-image-empty-btn:active {
  background: #e8dfd0;
}

.node-image-empty-btn svg {
  width: 15px;
  height: 15px;
}

/* 空白涂鸦模式：白纸（没有底图时给个白底，否则看不清画了什么） */
.viewer-modal.is-blank .viewer-wrap {
  background: #ffffff;
  border-radius: 4px;
  box-shadow: 0 10px 30px rgba(80, 64, 48, 0.18);
}

/* 白纸模式必须把 <img> 藏起来。
 * ⛔ 只 removeAttribute('src') 不够：没有 src 的 img 仍会渲染
 * 「破图图标 + alt 文字（预览图）」，糊在白纸左上角。
 * 用 visibility 而非 display —— img 是普通流式元素，display:none 会让
 * .viewer-wrap 失去唯一的内容来源而塌成 0 高度（canvas 是 absolute 定位）。 */
.viewer-modal.is-blank .viewer-wrap img {
  visibility: hidden;
}

.node-image-missing {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100%;
  font-size: 12px;
  color: #a3937f;
  background: #f2ece3;
}

/* 可点击的空态 / 失效态：必须看得出能点，且点了要有反馈。
   之前只是一段提示文字，用户点了没反应，以为功能坏了。 */
.node-image-missing.is-action {
  flex-direction: column;
  gap: 6px;
  border: 1px dashed #c9b9a3;
  border-radius: 6px;
  cursor: pointer;
  font-family: inherit;
  transition: background 0.15s, border-color 0.15s;
}

.node-image-missing.is-action:hover {
  background: #efe7db;
  border-color: #a3937f;
}

.node-image-missing.is-action:active {
  background: #e8dfd0;
}

.node-image-missing.is-action svg {
  width: 22px;
  height: 22px;
}

/* 图片节点标题栏的涂鸦按钮 */
.node-head-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  margin-left: 4px;
  padding: 0;
  border: 1px solid transparent;
  border-radius: 5px;
  background: transparent;
  color: #8a7a66;
  cursor: pointer;
  flex: none;
}

.node-head-btn:hover {
  background: #efe7db;
  border-color: #d8c9b4;
  color: #5f5346;
}

.node-head-btn svg {
  width: 14px;
  height: 14px;
}

.node-text {
  width: 100%;
  height: 100%;
  padding: 14px 16px 16px;
  border: none;
  outline: none;
  resize: none;
  background: #fdf9f2;
  color: #3a2f27;
  font-family: Georgia, "Songti SC", serif;
  font-size: 14px;
  line-height: 1.7;
}
.node-text::placeholder { color: #b5a79a; }

.node-resize {
  position: absolute;
  right: -6px;
  bottom: -6px;
  width: 18px;
  height: 18px;
  border-radius: 60% 20% 60% 20%;
  background: #8b9d77;
  border: 2px solid #faf6f1;
  cursor: nwse-resize;
  opacity: 0;
  transition: opacity 400ms var(--no-ease);
}
.node:hover .node-resize,
.node.is-selected .node-resize { opacity: 1; }

/* ------------------------------ 图像生成节点 ------------------------------ */
.node-body.is-generator { overflow-y: auto; }

.gnode { display: flex; flex-direction: column; gap: 8px; padding: 12px; }

.gnode-prompt {
  width: 100%;
  min-height: 132px;
  max-height: 300px;
  padding: 10px 12px;
  border: 1px solid #e8ded2;
  border-radius: 1rem;
  background: #fffdf9;
  color: #3a2f27;
  font-family: inherit;
  font-size: 12.5px;
  line-height: 1.65;
  resize: vertical;
  outline: none;
  transition: border-color 300ms var(--no-ease), box-shadow 300ms var(--no-ease);
}
.gnode-prompt::placeholder { color: #b5a79a; }
.gnode-prompt:focus { border-color: #d4a373; box-shadow: 0 0 0 3px rgba(139, 157, 119, 0.18); }

/* 接了文本卡片时，输入框变为只读镜像（内容由上游决定） */
.gnode-prompt.is-driven {
  background: #f2ece3;
  color: #6b5d51;
  border-style: dashed;
  cursor: default;
}
.gnode-prompt.is-driven:focus { border-color: #e8ded2; box-shadow: none; }

/* 生成参数：两列栅格，每项一个 10px 小标题 + 下拉 */
.gnode-params {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px;
}

.gnode-field { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.gnode-field > span {
  padding-left: 4px;
  font-size: 10px;
  letter-spacing: 0.04em;
  color: #a3937f;
}

.gnode-select {
  width: 100%;
  min-width: 0;
  padding: 7px 10px;
  border: 1px solid #e8ded2;
  border-radius: 999px;
  background: #fffdf9;
  color: #5c4033;
  font-family: inherit;
  font-size: 11.5px;
  cursor: pointer;
  outline: none;
  transition: border-color 300ms var(--no-ease);
}
.gnode-select:focus { border-color: #d4a373; }

.gnode-ref {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 7px 11px;
  border-radius: 999px;
  background: #f2ece3;
  color: #8c7c6d;
  font-size: 11px;
}
.gnode-ref svg { width: 13px; height: 13px; flex-shrink: 0; }
.gnode-ref span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.gnode-ref.is-img2img { background: rgba(139, 157, 119, 0.2); color: #5c4033; }
.gnode-ref.is-error { background: rgba(192, 90, 62, 0.14); color: #8c3a2a; }

/* 视频：首帧缩略图 + 断开按钮 */
.gnode-ref img {
  width: 22px;
  height: 22px;
  object-fit: cover;
  border-radius: 4px;
  flex-shrink: 0;
}
.gnode-ref-clear {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 18px;
  height: 18px;
  margin-left: auto;
  padding: 0;
  border: none;
  border-radius: 50%;
  background: transparent;
  color: inherit;
  cursor: pointer;
}
.gnode-ref-clear:hover { background: rgba(0, 0, 0, 0.08); }
.gnode-ref-clear svg { width: 12px; height: 12px; }

/* ============ 视频帧槽位（首帧 / 尾帧） ============
   ⚠️ 刻意**不复用** .gnode-ref：那个是「图像生成节点」的参考图条，
   它的单行药丸造型（border-radius:999px）与图生图共用，改它会连带变形。
   这里纵向堆叠，能装下两个缩略图 + 交换 + 断开 + 开关 + 提示。 */
.gnode-frames {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 8px 10px;
  border: 1px solid #e8ded2;
  border-radius: 10px;
  background: #f7f2ea;
}
.gnode-frame-slots { display: flex; align-items: stretch; gap: 6px; }
.gnode-frame-slot {
  display: flex;
  align-items: center;
  gap: 5px;
  flex: 1 1 0;
  min-width: 0;
  padding: 4px 6px;
  border: 1px dashed #ddd0be;
  border-radius: 8px;
  background: #fffdf9;
  font-size: 10.5px;
  color: #8c7c6d;
}
.gnode-frame-slot.is-empty { justify-content: center; opacity: 0.72; }
.gnode-frame-slot.is-first { border-color: #c9b79c; }
.gnode-frame-slot.is-last { border-color: #a9b598; }
.gnode-frame-tag { flex-shrink: 0; letter-spacing: 0.03em; }
.gnode-frame-thumb {
  width: 22px;
  height: 22px;
  object-fit: cover;
  border-radius: 4px;
  flex-shrink: 0;
}
.gnode-frame-btn,
.gnode-frame-swap {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
  margin-left: auto;
  padding: 0;
  border: none;
  border-radius: 50%;
  background: transparent;
  color: #8c7c6d;
  cursor: pointer;
  flex-shrink: 0;
}
.gnode-frame-btn:hover,
.gnode-frame-swap:hover { background: rgba(0, 0, 0, 0.08); }
.gnode-frame-btn svg,
.gnode-frame-swap svg { width: 12px; height: 12px; }
.gnode-frame-swap { margin-left: 0; align-self: center; }
.gnode-frame-toggle {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  align-self: flex-start;
  font-size: 11px;
  color: #8c7c6d;
  cursor: pointer;
}
.gnode-frame-toggle input { margin: 0; accent-color: #6f8160; }
.gnode-frame-tip { margin: 0; font-size: 10.5px; line-height: 1.45; color: #8c7c6d; }
.gnode-frame-tip.is-warn { color: #8a6a2f; }
.gnode-frames.is-error { border-color: rgba(192, 90, 62, 0.4); }
.gnode-frames.is-error .gnode-frame-tip { color: #8c3a2a; }

/* 图生视频时的「自适应（由输入图决定）」
   ⚠️ 刻意做成静态说明块，而不是 disabled 的 select：
      禁用态看不出「为什么灰的」，而原因只写在 title 里、鼠标不悬上就看不到。 */
.gnode-auto {
  display: flex;
  align-items: baseline;
  gap: 4px;
  min-width: 0;
  padding: 9px 16px;
  /* 虚线 = 「这不是可选项」，与 .is-driven 的提示语义一致 */
  border: 1px dashed #ddd0be;
  border-radius: 999px;
  background: #f2ece3;
  font-size: 11.5px;
  color: #8c7c6d;
  cursor: default;
}
.gnode-auto-main { flex-shrink: 0; color: #6b5d51; }
.gnode-auto-sub {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-style: normal;
}

/* 模型字段下方的常驻提示（未配置模型等） */
.gnode-hint {
  /* ⚠️ .gnode-params 是两列栅格（1fr 1fr），不加这行只会占半行 */
  grid-column: 1 / -1;
  margin: 2px 0 0;
  padding: 5px 8px;
  border-radius: 8px;
  background: #f7f2ea;
  font-size: 10.5px;
  line-height: 1.45;
  color: #8c7c6d;
}
.gnode-hint.is-warn { background: rgba(192, 148, 62, 0.12); color: #8a6a2f; }

/* 视频播放器 */
.gnode-video {
  display: block;
  width: 100%;
  border-radius: 10px;
  background: #1c1714;
  /* ⛔ 不要设 object-fit: cover —— 会裁掉画面 */
}
.gnode-tip {
  margin: 6px 0 0;
  font-size: 11px;
  line-height: 1.5;
  color: #8c7c6d;
}

.gnode-actions { display: flex; align-items: center; gap: 10px; }

.gnode-run {
  padding: 9px 20px;
  border: none;
  border-radius: 999px;
  background: #5c4033;
  color: #faf6f1;
  font-family: inherit;
  font-size: 12.5px;
  font-weight: 500;
  cursor: pointer;
  transition: all 500ms var(--no-ease);
}
.gnode-run:hover { background: #6f8160; }
.gnode-run:active { transform: translateY(1px); }
.gnode-run[disabled] { opacity: 0.55; cursor: progress; }

.gnode-status { flex: 1; font-size: 11px; color: #8c7c6d; line-height: 1.4; }
.gnode-status.is-done { color: #6f8160; }
.gnode-status.is-error { color: #8c4a34; }

.gnode-result:empty { display: none; }

/* 图片角上的「保存到本地」小按钮，hover 图片才显形 */
.gnode-result-bar { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }

/* 连接点：44px 透明热区 + 14px 实心圆点，常驻显示；hover 放大并变色，光标为十字加号 */
.node-port {
  position: absolute;
  top: 50%;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  transform: translateY(-50%);
  cursor: crosshair;
  z-index: 3;
}

.node-port::after {
  content: "";
  position: absolute;
  left: 15px;
  top: 15px;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  border: 2px solid #fffdf9;
  background: #5c4033;
  box-shadow: 0 0 0 1px #b09a86;
  transition: transform 160ms var(--no-ease), background 160ms var(--no-ease),
    box-shadow 160ms var(--no-ease);
}

.node-port:hover::after {
  transform: scale(1.25);
  background: #6f8160;
  box-shadow: 0 0 0 1px #6f8160;
}

.node-port.in { left: -22px; }
.node-port.out { right: -22px; }

.node-actions {
  position: absolute;
  top: -14px;
  right: -8px;
  display: none;
  gap: 6px;
}
.node:hover .node-actions, .node.is-selected .node-actions { display: flex; }

.node-action {
  width: 28px;
  height: 28px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 999px;
  border: 1px solid #e8ded2;
  background: #fffdf9;
  color: #5c4033;
  cursor: pointer;
  transition: all 400ms var(--no-ease);
}
.node-action svg { width: 14px; height: 14px; }
.node-action:hover { background: #f2ece3; color: #6f8160; }
.node-action.danger:hover { background: rgba(212, 163, 115, 0.25); color: #8c4a34; }

.marquee {
  position: absolute;
  border: 1px solid #8b9d77;
  background: rgba(139, 157, 119, 0.16);
  border-radius: 1rem;
  pointer-events: none;
  z-index: 5;
}

/* ------------------------------ 拖拽文件提示层 ----------------------------- */
.drop-overlay {
  position: absolute;
  inset: 14px;
  z-index: 40;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 6px;
  border: 2px dashed #8b9d77;
  border-radius: 2rem;
  background: rgba(250, 246, 241, 0.9);
  color: #5c4033;
  pointer-events: none;
}
.drop-overlay svg { width: 34px; height: 34px; color: #8b9d77; }
.drop-overlay strong { font-size: 15px; font-weight: 500; }
.drop-overlay span { font-size: 12px; color: #8c7c6d; }

/* ------------------------------- 画布工具条 ------------------------------- */
.board-tools,
.zoom-tools {
  position: absolute;
  display: flex;
  gap: 8px;
  padding: 8px;
  background: rgba(255, 253, 249, 0.92);
  border: 1px solid #e8ded2;
  border-radius: 999px;
  box-shadow: 0 8px 24px -20px rgba(92, 64, 51, 0.5);
  z-index: 12;
  backdrop-filter: blur(6px);
}

.board-tools { left: calc(20px + env(safe-area-inset-left, 0px)); top: 20px; }
/* 底部控件避开系统手势条 / 浏览器底栏 */
.zoom-tools {
  left: calc(20px + env(safe-area-inset-left, 0px));
  bottom: calc(20px + env(safe-area-inset-bottom, 0px));
  align-items: center;
}
.zoom-label { font-size: 12px; color: #5c4033; min-width: 44px; text-align: center; }

.board-hint {
  position: absolute;
  left: 50%;
  bottom: 22px;
  transform: translateX(-50%);
  margin: 0;
  font-size: 12px;
  color: #8c7c6d;
  background: rgba(255, 253, 249, 0.86);
  border: 1px solid #efe7dc;
  border-radius: 999px;
  padding: 8px 20px;
  z-index: 11;
  pointer-events: none;
}

/* -------------------------------- 小地图 --------------------------------- */
.minimap {
  position: absolute;
  right: calc(20px + env(safe-area-inset-right, 0px));
  bottom: calc(20px + env(safe-area-inset-bottom, 0px));
  width: 208px;
  height: 148px;
  background-color: rgba(255, 253, 249, 0.92);
  background-image: radial-gradient(circle, rgba(140, 118, 98, 0.22) 1px, transparent 1px);
  background-size: 12px 12px;
  border: 1px solid #e8ded2;
  border-radius: 1.4rem;
  overflow: hidden;
  z-index: 12;
  cursor: pointer;
}

.minimap-empty {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 11px;
  letter-spacing: 0.08em;
  color: #a3937f;
  pointer-events: none;
}

.minimap-nodes { position: absolute; inset: 0; }
.minimap-node {
  position: absolute;
  background: #d4a373;
  border-radius: 4px;
  opacity: 0.75;
}
.minimap-node.is-text { background: #8b9d77; }

.minimap-viewport {
  position: absolute;
  border: 1.5px solid #6f8160;
  background: rgba(139, 157, 119, 0.14);
  border-radius: 6px;
  pointer-events: none;
}

/* ------------------------------ 资产库抽屉 ------------------------------- */
.asset-drawer {
  position: absolute;
  top: 16px;
  right: 16px;
  bottom: 16px;
  width: 320px;
  display: flex;
  flex-direction: column;
  background: rgba(255, 253, 249, 0.97);
  border: 1px solid #e8ded2;
  border-radius: 2rem;
  box-shadow: 0 8px 24px -18px rgba(92, 64, 51, 0.45);
  z-index: 20;
  transform: translateX(calc(100% + 24px));
  opacity: 0;
  transition: transform 600ms var(--no-ease), opacity 600ms var(--no-ease);
  pointer-events: none;
}

.asset-drawer.is-open { transform: translateX(0); opacity: 1; pointer-events: auto; }

.drawer-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 14px 16px;
  border-bottom: 1px solid #efe7dc;
}
.drawer-head strong { display: inline-flex; align-items: center; gap: 8px; font-size: 15px; color: #5c4033; }
.drawer-head svg { width: 16px; height: 16px; color: #6f8160; }

.drawer-fields { padding: 12px 16px 8px; display: flex; flex-direction: column; gap: 8px; }
.drawer-row { display: flex; gap: 8px; }
.drawer-row .no-input { flex: 1; }

.drawer-drop {
  margin: 4px 16px 12px;
  padding: 14px;
  border: 1px dashed #d4a373;
  border-radius: 1.2rem;
  text-align: center;
  font-size: 12px;
  color: #8c7c6d;
  transition: all 400ms var(--no-ease);
}
.drawer-drop.is-over { background: rgba(139, 157, 119, 0.16); border-color: #6f8160; color: #5c4033; }

.drawer-drop-tip {
  display: inline-block;
  margin-top: 6px;
  font-size: 11px;
  color: #a89b8c;
}

.drawer-drop-tip b {
  color: #6f8160;
  font-weight: 600;
}

.drawer-grid {
  flex: 1;
  overflow-y: auto;
  padding: 0 16px 16px;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 10px;
  align-content: start;
}

.drawer-item {
  position: relative;
  aspect-ratio: 1;
  border-radius: 1.1rem;
  overflow: hidden;
  border: 1px solid #efe7dc;
  background: #f2ece3;
  cursor: grab;
  transition: all 400ms var(--no-ease);
}
.drawer-item:hover { border-color: #8b9d77; transform: translateY(-2px); }
.drawer-item img { width: 100%; height: 100%; object-fit: cover; display: block; pointer-events: none; }
.drawer-item .drawer-item-del {
  position: absolute;
  top: 4px;
  right: 4px;
  width: 22px;
  height: 22px;
  border-radius: 999px;
  border: none;
  background: rgba(92, 64, 51, 0.78);
  color: #faf6f1;
  font-size: 12px;
  line-height: 1;
  cursor: pointer;
  opacity: 0;
  transition: opacity 300ms var(--no-ease);
}
.drawer-item:hover .drawer-item-del { opacity: 1; }

.drawer-empty {
  grid-column: 1 / -1;
  text-align: center;
  font-size: 12px;
  color: #a3937f;
  padding: 24px 0;
}

.drawer-foot {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 12px;
  border-top: 1px solid #efe7dc;
  font-size: 12px;
  color: #6f8160;
  text-decoration: none;
  transition: background 400ms var(--no-ease);
}
.drawer-foot:hover { background: #f2ece3; }
.drawer-foot svg { width: 14px; height: 14px; }

/* ---------------------------- 画布列表 / 编辑弹窗 --------------------------- */
.editor-card { max-width: 760px; }

.canvas-card {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 12px;
  border: 1px solid #efe7dc;
  border-radius: 1.4rem;
  margin-bottom: 10px;
  cursor: pointer;
  transition: all 500ms var(--no-ease);
}
.canvas-card:hover { background: #f2ece3; border-color: #d4a373; }
.canvas-card.is-current { border-color: #8b9d77; background: rgba(139, 157, 119, 0.12); }

.canvas-card-cover {
  width: 54px;
  height: 54px;
  border-radius: 1rem;
  background: #f2ece3 center/cover no-repeat;
  flex-shrink: 0;
  border: 1px solid #efe7dc;
}
.canvas-card-info { flex: 1; min-width: 0; }
.canvas-card-info h4 { margin: 0 0 4px; font-size: 14px; color: #5c4033; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.canvas-card-info span { font-size: 11px; color: #8c7c6d; }
.canvas-card-actions { display: flex; gap: 6px; }

/* -------------------------------- 裁剪编辑 -------------------------------- */
.editor-ratios { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 14px; }

.editor-stage {
  flex: 1;
  min-height: 220px;
  max-height: 52vh;
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
  background: #f2ece3;
  border-radius: 1.6rem;
  border: 1px solid #efe7dc;
  padding: 12px;
}

.editor-canvas-wrap { position: relative; line-height: 0; max-width: 100%; max-height: 100%; }
.editor-canvas-wrap img { max-width: 100%; max-height: 48vh; display: block; user-select: none; -webkit-user-drag: none; }

.crop-box {
  position: absolute;
  border: 1.5px solid #6f8160;
  box-shadow: 0 0 0 9999px rgba(58, 47, 39, 0.42);
  cursor: move;
}

.crop-handle {
  position: absolute;
  width: 12px;
  height: 12px;
  background: #faf6f1;
  border: 1.5px solid #6f8160;
  border-radius: 60% 20% 60% 20%;
}
.crop-handle[data-handle="nw"] { left: -6px; top: -6px; cursor: nwse-resize; }
.crop-handle[data-handle="n"] { left: 50%; top: -6px; margin-left: -6px; cursor: ns-resize; }
.crop-handle[data-handle="ne"] { right: -6px; top: -6px; cursor: nesw-resize; }
.crop-handle[data-handle="e"] { right: -6px; top: 50%; margin-top: -6px; cursor: ew-resize; }
.crop-handle[data-handle="se"] { right: -6px; bottom: -6px; cursor: nwse-resize; }
.crop-handle[data-handle="s"] { left: 50%; bottom: -6px; margin-left: -6px; cursor: ns-resize; }
.crop-handle[data-handle="sw"] { left: -6px; bottom: -6px; cursor: nesw-resize; }
.crop-handle[data-handle="w"] { left: -6px; top: 50%; margin-top: -6px; cursor: ew-resize; }

.editor-foot { align-items: center; justify-content: space-between; }
.crop-size-label { font-size: 12px; color: #8c7c6d; }
.foot-actions { display: flex; gap: 12px; }

/* ------------------------------- 节点右键菜单 ------------------------------ */
.node-context-menu {
  position: fixed;
  z-index: 200;
  min-width: 168px;
  padding: 8px;
  background: #fffdf9;
  border: 1px solid #e8ded2;
  border-radius: 1.2rem;
  box-shadow: 0 8px 24px -18px rgba(92, 64, 51, 0.45);
}

.node-context-menu button {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 9px 14px;
  border: none;
  border-radius: 999px;
  background: transparent;
  color: #5c4033;
  font-family: inherit;
  font-size: 13px;
  text-align: left;
  cursor: pointer;
  transition: background 400ms var(--no-ease);
}
.node-context-menu button:hover { background: #f2ece3; }
.node-context-menu button svg { width: 15px; height: 15px; color: #6f8160; }
.node-context-menu button.danger:hover { background: rgba(212, 163, 115, 0.25); color: #8c4a34; }

.node-context-menu .menu-label {
  padding: 6px 14px 8px;
  font-size: 11px;
  letter-spacing: 0.08em;
  color: #a3937f;
}

/* 端口菜单：把「拖线松手在空白处」要新建的节点类型排成图标网格 */
.node-context-menu.is-port-menu { min-width: 200px; padding: 8px; }
.node-context-menu .menu-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(56px, 1fr));
  gap: 6px;
}
.node-context-menu .menu-grid .menu-btn {
  height: 58px;
  flex-direction: column;
  justify-content: center;
  gap: 5px;
  padding: 6px;
  border-radius: 1rem;
  font-size: 11px;
  line-height: 1.15;
  text-align: center;
}
.node-context-menu .menu-grid .menu-btn svg { width: 18px; height: 18px; }

/* ------------------------------ 图片放大 + 标注 ------------------------------ */
.viewer-modal {
  z-index: 200;
  padding: 20px;
  align-items: stretch;
  background: rgba(58, 47, 39, 0.62);
}

.viewer-shell {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 12px;
  background: #faf6f1;
  border: 1px solid #e8ded2;
  border-radius: 2rem;
  padding: 14px;
  box-shadow: 0 8px 24px -18px rgba(92, 64, 51, 0.5);
  transform: translateY(10px);
  transition: transform 500ms var(--no-ease);
}
.viewer-modal.is-in .viewer-shell { transform: translateY(0); }

.viewer-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  flex-shrink: 0;
}

.viewer-tools { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.viewer-sep { width: 1px; height: 22px; background: #e8ded2; margin: 0 4px; }

.viewer-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-height: 38px;
  padding: 8px 14px;
  border: 1px solid #e8ded2;
  border-radius: 999px;
  background: #fffdf9;
  color: #5c4033;
  font-family: inherit;
  font-size: 12.5px;
  cursor: pointer;
  transition: all 400ms var(--no-ease);
}
.viewer-btn svg { width: 15px; height: 15px; flex-shrink: 0; }
.viewer-btn:hover { background: #f2ece3; color: #6f8160; }
.viewer-btn:active { transform: translateY(1px); }
.viewer-btn.is-active { background: #8b9d77; border-color: #8b9d77; color: #faf6f1; }
.viewer-btn[disabled] { opacity: 0.42; cursor: not-allowed; }
.viewer-btn[disabled]:hover { background: #fffdf9; color: #5c4033; }
.viewer-close { width: 40px; padding: 8px; justify-content: center; }

.viewer-field {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-height: 38px;
  padding: 6px 12px;
  border: 1px solid #e8ded2;
  border-radius: 999px;
  background: #fffdf9;
  color: #a3937f;
}
.viewer-field svg { width: 13px; height: 13px; }
.viewer-field input[type="color"] {
  width: 26px;
  height: 26px;
  padding: 0;
  border: none;
  border-radius: 999px;
  background: none;
  cursor: pointer;
}
.viewer-field input[type="range"] { width: 96px; accent-color: #8b9d77; cursor: pointer; }

.viewer-stage {
  flex: 1;
  min-height: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: auto;
  border-radius: 1.6rem;
  background: #efe9e0;
  background-image: radial-gradient(circle, rgba(140, 118, 98, 0.22) 1px, transparent 1px);
  background-size: 16px 16px;
  padding: 16px;
}

.viewer-wrap { position: relative; line-height: 0; flex-shrink: 0; }
.viewer-wrap img { display: block; border-radius: 0.6rem; user-select: none; -webkit-user-drag: none; }
.viewer-wrap canvas {
  position: absolute;
  left: 0;
  top: 0;
  border-radius: 0.6rem;
  cursor: crosshair;
  touch-action: none;
}

.viewer-hint {
  margin: 0;
  text-align: center;
  font-size: 12px;
  color: #8c7c6d;
  flex-shrink: 0;
}

/* ------------------------------- 响应式适配 ------------------------------- */
@media (max-width: 900px) {
  .brand-text { display: none; }
  .canvas-title-input { min-width: 110px; max-width: 40vw; }
  .tool-btn span { display: none; }
  .tool-btn { padding: 8px 10px; }
  .asset-drawer { width: min(88vw, 320px); }
  .board-tools { flex-wrap: wrap; max-width: calc(100vw - 40px); }
  .minimap { width: 150px; height: 108px; }
  .board-hint { display: none; }
}

/* ------------------------- 手机端顶栏：只留主功能图标 -------------------------
   窄屏塞不下「带字按钮 + 图标按钮 + 标题 + 账号」，会互相遮挡。
   这里顶栏只保留主功能图标，其余（含积分、账号）收进「全部功能」菜单。 */
.narrow-only { display: none; }

@media (max-width: 640px) {
  .topbar {
    gap: 8px;
    padding: 8px 10px;
    padding-top: calc(8px + env(safe-area-inset-top, 0px));
    border-radius: 0 0 1.4rem 1.4rem;
  }
  .brand { display: none; }                 /* 标题本身就是上下文，品牌区让位 */
  .canvas-meta { margin-left: 0; flex: 1; min-width: 0; }
  .canvas-title-input { min-width: 0; max-width: none; width: 100%; font-size: 14px; }
  .save-state { display: none; }
  .topbar-actions { gap: 6px; }
  .topbar-actions .tool-btn { padding: 7px; }
  /* 非主功能一律收进菜单（分隔线、积分、账号同理） */
  .topbar-actions .tool-btn:not(.is-primary):not(.narrow-only),
  .topbar-actions .topbar-sep,
  .topbar-actions .credit-chip,
  .topbar-actions .user-chip { display: none; }
  .narrow-only { display: inline-flex; }
}

/* ------------------------- 手机端「全部功能」菜单 ------------------------- */
.mobile-menu {
  position: fixed;
  inset: 0;
  z-index: 60;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  background: rgba(92, 64, 51, 0.3);
  backdrop-filter: blur(2px);
}

/* ⚠️ 必须显式处理：上面的 display:flex 会盖掉浏览器默认的 [hidden]{display:none}，
   漏掉这一条的话遮罩会一直盖在应用上（本项目 auth.css 里也留过同样的教训）。 */
.mobile-menu[hidden] { display: none; }

.mobile-menu-card {
  width: 100%;
  max-width: 560px;
  max-height: 78dvh;
  overflow-y: auto;
  background: #fffdf9;
  border-radius: 1.6rem 1.6rem 0 0;
  padding: 14px 16px;
  /* 底部留出系统手势条的空间 */
  padding-bottom: calc(18px + env(safe-area-inset-bottom, 0px));
  box-shadow: 0 -12px 32px -20px rgba(92, 64, 51, 0.55);
}

.mobile-menu-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 10px;
}

.mobile-menu-head strong { font-size: 15px; color: #5c4033; }

.mobile-menu-list { display: grid; gap: 8px; }

.mobile-menu-item {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  padding: 12px 14px;
  border: 1px solid #eee7dc;
  border-radius: 1rem;
  background: #fffdf9;
  text-align: left;
  cursor: pointer;
}

.mobile-menu-item:active { background: #f6efe6; }
.mobile-menu-item[hidden] { display: none; }

.mobile-menu-item .mm-ic {
  display: inline-flex;
  flex-shrink: 0;
  color: #6f8160;
}

.mobile-menu-item > span:last-child { flex: 1; min-width: 0; }

.mobile-menu-item b { display: block; font-size: 14px; font-weight: 600; color: #5c4033; }

.mobile-menu-item small {
  display: block;
  margin-top: 2px;
  font-size: 11.5px;
  line-height: 1.5;
  color: #a89e8c;
}

/* --------------------- 手机端图片查看器：操作区改为悬浮固定 ---------------------
   查看器一共有 11 个控件，窄屏下会折成好几行把图片挤没，关闭按钮还落在最右侧够不到。
   这里改成：图片占满全屏，工具条固定在底部悬浮，关闭按钮固定在右上角悬浮。 */
@media (max-width: 640px) {
  .viewer-modal { padding: 0; align-items: stretch; }

  /* ⚠️ transform / backdrop-filter 都会成为 position:fixed 的包含块。
     这里把自身的内边距清零，保证悬浮元素对齐的是整个视口。 */
  .viewer-modal .viewer-shell,
  .viewer-modal.is-in .viewer-shell {
    transform: none;
    border: 0;
    border-radius: 0;
    padding: 0;
    gap: 0;
  }

  /* 图片区占满全屏，上下留出悬浮操作区的位置（layoutViewer 会读真实 padding 来适配） */
  .viewer-stage { border-radius: 0; padding: 68px 10px 176px; }
  .viewer-hint { display: none; }

  /* 工具与操作：固定底部，图标化 */
  .viewer-bar {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 4;
    margin: 0;
    padding: 8px 10px;
    padding-bottom: calc(10px + env(safe-area-inset-bottom, 0px));
    /* ⚠️ 不能用 backdrop-filter / filter —— 它们会让本元素成为 fixed 子元素的
       包含块，里面的「关闭」按钮就会被锚定到工具条内部（实测被压到底部），
       而不是视口右上角。这里用不透明度换定位正确。 */
    background: rgba(255, 253, 249, 0.98);
    border-top: 1px solid #e8ded2;
    gap: 8px;
    justify-content: center;
  }

  .viewer-tools { width: 100%; justify-content: center; gap: 8px; }
  .viewer-sep { display: none; }
  .viewer-btn { min-width: 44px; min-height: 44px; padding: 0 12px; justify-content: center; }
  .viewer-btn span { display: none; }
  .viewer-btn svg { width: 18px; height: 18px; }
  /* 保存 / 存资产库保留文字，纯图标容易认错 */
  .viewer-btn--wide { padding: 0 14px; }
  .viewer-btn--wide span { display: inline; }
  .viewer-field { min-height: 44px; padding: 6px 10px; }
  .viewer-field input[type="range"] { width: 64px; }

  /* 关闭：固定右上角悬浮，任何时候都能点到 */
  .viewer-close {
    position: fixed;
    top: calc(12px + env(safe-area-inset-top, 0px));
    right: calc(12px + env(safe-area-inset-right, 0px));
    width: 46px;
    height: 46px;
    min-height: 46px;
    padding: 0;
    z-index: 5;
    background: rgba(255, 253, 249, 0.97);
    border-color: #e0d6c8;
    box-shadow: 0 8px 22px -12px rgba(58, 47, 39, 0.75);
  }
}

/* ============ 资产库里的视频素材 ============
   ⛔ 视频不能塞 <img>（解码不了只会破图）。
   <video preload="metadata"> 会让浏览器显示第一帧，正好当缩略图。 */
.asset-video,
.drawer-video {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  background: #1c1714;
}
.drawer-item { position: relative; }
.asset-kind-badge,
.drawer-badge {
  position: absolute;
  left: 6px;
  bottom: 6px;
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 2px 7px;
  border-radius: 999px;
  background: rgba(28, 23, 20, 0.72);
  color: #faf6f1;
  font-size: 10px;
  pointer-events: none;
  z-index: 1;
}
.asset-kind-badge svg,
.drawer-badge svg { width: 11px; height: 11px; }

