/* ui.css —— 引擎侧通用界面样式。框架阶段视觉从简，重点是把各系统的状态
   （已去过/有新事件/主线/加油站/理智档位）用得上的 class 都接好，
   具体美术风格由你以后自己换皮。 */

:root {
  --bg: #0a0a0a;
  --panel: #141414;
  --line: #3a3a3a;
  --text: #d8d8d8;
  --accent: #ff9900;
  --accent-2: #00ccff;
  --danger: #ff4433;
  --accent-rgb: 255, 153, 0;
  --accent2-rgb: 0, 204, 255;
  --danger-rgb: 255, 68, 51;

  /* macOS 质感：菜单栏/窗口的毛玻璃与描边取值集中放这里，方便统一调 */
  --menubar-bg: rgba(28, 28, 30, 0.72);
  --menubar-border: rgba(255, 255, 255, 0.08);
  --window-bg: rgba(32, 32, 34, 0.88);
  --window-titlebar: rgba(255, 255, 255, 0.05);
  --window-border: rgba(255, 255, 255, 0.13);
  --window-shadow: 0 20px 60px rgba(0, 0, 0, 0.55), 0 2px 8px rgba(0, 0, 0, 0.35);
  --traffic-red: #ff5f57;
  --traffic-yellow: #febc2e;
  --traffic-green: #28c840;
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  height: 100%;
  background: var(--bg);
  color: var(--text);
  font-family: -apple-system, BlinkMacSystemFont, "SF Pro SC", "SF Pro Text",
    "PingFang SC", "Microsoft YaHei", sans-serif;
  -webkit-font-smoothing: antialiased;
}

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

/* --- 状态栏：仿 macOS 顶部菜单栏，营造"在自己电脑上看结果"的观感 --- */
#status-bar {
  display: flex;
  align-items: center;
  height: 60px;
  padding: 0 14px;
  background: var(--menubar-bg);
  backdrop-filter: blur(20px) saturate(1.8);
  -webkit-backdrop-filter: blur(20px) saturate(1.8);
  border-bottom: 1px solid var(--menubar-border);
  flex-shrink: 0;
  font-size: 13px;
  position: relative;
  z-index: 50;
}

.menu-left {
  display: flex;
  align-items: center;
  gap: 8px;
}

.menu-brand {
  color: var(--accent);
  font-size: 9px;
}

.menu-right {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: 14px;
}

.menu-item { opacity: 0.85; }

#status-bar .status-buttons {
  display: flex;
  gap: 6px;
}

.menu-pill {
  position: relative;
  height: 28px;
  padding: 0 13px;
  border-radius: 6px;
  background: transparent;
  border: 1px solid transparent;
  color: var(--text);
  font-size: 15px;
  font-weight: 600;
  display: inline-flex;
  align-items: center;
  gap: 5px;
}
.menu-pill:hover {
  background: rgba(255, 255, 255, 0.12);
  border-color: transparent;
  color: #fff;
}

#day-display { font-weight: 700; color: var(--accent-2); }
#clock-display { font-variant-numeric: tabular-nums; letter-spacing: 0.3px; }
#sanity-display::before { content: "\25c9"; margin-right: 4px; font-size: 9px; opacity: 0.8; }
#sanity-display { color: var(--accent); }

/* --- 地图 --- */
#map-viewport {
  position: relative;
  flex: 1;
  overflow: hidden;
  touch-action: none;
  cursor: grab;
  background: #000;
}

.map-content {
  position: absolute;
  top: 0; left: 0;
  width: 100%;
  height: 100%;
  will-change: transform;
}

/* 视口比例跟底图（3:2）不一致时，.map-image 的 object-fit:contain 会在某一轴上留黑边；
   这个盒子由 map.js 的 layoutImageBox() 精确摆成"图片实际显示的那个矩形"，
   <img> 和所有 .hotspot 都挂在它下面，百分比坐标才会真的对应图片本身。
   left/top/width/height 由 JS 用行内样式设置，这里的 0 只是加载完成前的占位。 */
.map-image-box {
  position: absolute;
  top: 0; left: 0;
}

.map-image {
  width: 100%;
  height: 100%;
  display: block;
  object-fit: contain;
  pointer-events: none;
  user-select: none;
}

/* 墨迹揭图层：盖在卫星底图上的一张 canvas，由 engine/map.js 画（白色墨渍 + source-in
   手绘地图），只有解锁过的地点周围才洇出手绘地图。跟 .map-image-box 同大小，
   所以缩放/平移时跟底图一起动；宽高属性（画布像素）由 JS 按手绘图原始尺寸设置。 */
.map-ink {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  user-select: none;
}

.hotspot {
  position: absolute;
  transform: translate(-50%, -50%);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  cursor: pointer;
}

.hotspot-dot {
  position: relative; /* 给 .has-event 的脉冲光环（::after）当定位参照 */
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: var(--accent-2);
  border: 2px solid #fff;
  box-shadow: 0 0 6px rgba(0,0,0,0.6);
}

.hotspot-basecamp .hotspot-dot {
  background: var(--accent);
  width: 22px;
  height: 22px;
}

.hotspot-label {
  font-size: 12px;
  padding: 1px 6px;
  background: rgba(0,0,0,0.7);
  border-radius: 3px;
  white-space: nowrap;
}

.hotspot.locked {
  display: none;
}

.hotspot.visited .hotspot-dot {
  filter: grayscale(0.6) brightness(0.7);
}

.hotspot.mainline .hotspot-dot {
  border-color: var(--accent);
}

.hotspot.current .hotspot-label {
  outline: 1px solid var(--accent-2);
}

/* 有新事件的点一直在脉冲。这一圈必须用伪元素的 transform+opacity 做，不能用 box-shadow：
   box-shadow 是 paint 属性、没法交给合成器，11 个热点就是 11 路常驻的 60fps 重绘，
   而且重绘落在 .map-content 那个装着大图和墨迹 canvas 的合成层里，整张地图跟着卡。
   几何对齐：dot 是 border-box 18px（含 2px 边框），padding box 14px，
   inset:-2px 把光环撑回 18px（半径 9），scale(2.11) 的外沿约 19px，
   正好等于原来 box-shadow 的 9px + 10px spread。
   z-index:-1 让它落在圆点背景之下（.hotspot 有 transform，已是层叠上下文，不会漏到地图上）。 */
.hotspot.has-event .hotspot-dot::after {
  content: "";
  position: absolute;
  inset: -2px;
  z-index: -1;
  border-radius: 50%;
  background: rgba(255,153,0,0.7);
  animation: pulse 1.4s infinite;
}

@keyframes pulse {
  0%   { transform: scale(1);    opacity: 0.7; }
  70%  { transform: scale(2.11); opacity: 0; }
  100% { transform: scale(2.11); opacity: 0; }
}

#map-controls {
  position: absolute;
  right: 16px;
  bottom: 16px;
  display: flex;
  flex-direction: column;
  gap: 6px;
  z-index: 30;
}

#map-controls button {
  width: 34px;
  height: 34px;
  font-size: 18px;
}

/* --- 通用按钮：仿 macOS 圆角按钮，默认即"强调色"填充（呼应 accent 可当系统强调色） --- */
button, .btn {
  background: rgba(var(--accent-rgb), 0.22);
  color: #fff;
  border: 1px solid rgba(var(--accent-rgb), 0.55);
  padding: 6px 14px;
  cursor: pointer;
  font-size: 13px;
  font-family: inherit;
  border-radius: 6px;
  transition: background 0.15s, border-color 0.15s;
}

button:hover, .btn:hover {
  background: rgba(var(--accent-rgb), 0.32);
  border-color: var(--accent);
  color: #fff;
}
button:active, .btn:active { background: rgba(var(--accent-rgb), 0.42); }

button:disabled, .btn:disabled {
  background: rgba(255, 255, 255, 0.04);
  border-color: rgba(255, 255, 255, 0.08);
  color: #666;
  cursor: default;
}
button:disabled:hover, .btn:disabled:hover { background: rgba(255, 255, 255, 0.04); border-color: rgba(255, 255, 255, 0.08); color: #666; }

.btn-gray {
  background: rgba(255, 255, 255, 0.06);
  border-color: rgba(255, 255, 255, 0.14);
  color: #aaa;
}
.btn-gray:hover { background: rgba(255, 255, 255, 0.12); color: #fff; }

.btn-danger {
  background: rgba(var(--danger-rgb), 0.16);
  border-color: var(--danger);
  color: #ff9a8c;
}
.btn-danger:hover { background: rgba(var(--danger-rgb), 0.26); color: #fff; }

.btn-option {
  display: block;
  width: 100%;
  text-align: left;
  margin-bottom: 6px;
  background: rgba(255, 255, 255, 0.05);
  border-color: rgba(255, 255, 255, 0.12);
  color: var(--text);
}
.btn-option:hover { background: rgba(255, 255, 255, 0.1); border-color: var(--accent-2); color: #fff; }

/* --- 弹窗：仿 macOS 悬浮窗口（毛玻璃 + 红黄绿交通灯），配合状态栏营造"在自己电脑桌面上
   查看结果"的感觉。窗口本身由 main.js 的 renderWindow() 统一拼出 .mac-titlebar/.mac-body。 --- */
.modal-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0,0,0,0.4);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 100;
}

.modal-box {
  background: var(--window-bg);
  backdrop-filter: blur(24px) saturate(1.6);
  -webkit-backdrop-filter: blur(24px) saturate(1.6);
  border: 1px solid var(--window-border);
  border-radius: 12px;
  padding: 0;
  max-width: 560px;
  width: 90%;
  max-height: 80vh;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  box-shadow: var(--window-shadow);
}

/* 窗口标题栏：左侧交通灯（红=关闭，黄/绿仅装饰），标题居中 */
.mac-titlebar {
  display: flex;
  align-items: center;
  gap: 8px;
  height: 38px;
  padding: 0 12px;
  flex-shrink: 0;
  background: var(--window-titlebar);
  border-bottom: 1px solid var(--window-border);
  position: relative;
  user-select: none;
}

.mac-traffic { display: flex; gap: 8px; z-index: 1; }

.mac-dot {
  width: 12px;
  height: 12px;
  margin: 0;
  border: none;
  padding: 0;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 9px;
  line-height: 1;
  color: transparent;
  cursor: default;
}
#mac-close { cursor: pointer; }
.mac-dot-red { background: var(--traffic-red); }
.mac-dot-yellow { background: var(--traffic-yellow); }
.mac-dot-green { background: var(--traffic-green); }
.mac-titlebar:hover .mac-dot { color: rgba(0,0,0,0.55); }
.mac-dot-red::before { content: "×"; }
.mac-dot-yellow::before { content: "−"; }
.mac-dot-green::before { content: "+"; }

.mac-title {
  position: absolute;
  left: 0;
  right: 0;
  text-align: center;
  font-size: 13px;
  font-weight: 600;
  color: rgba(255,255,255,0.85);
  pointer-events: none;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  padding: 0 90px;
}

.mac-body {
  flex: 1 1 auto;
  min-height: 0;
  padding: 20px;
  overflow-y: auto;
}

.hidden { display: none !important; }

.event-text { line-height: 1.7; margin-bottom: 14px; white-space: pre-wrap; }
.hint { color: #888; font-size: 0.9em; }

/* 仿 macOS 分段控件（System Settings 里常见的胶囊 tab） */
.tabs {
  display: flex;
  gap: 2px;
  padding: 3px;
  margin-bottom: 14px;
  background: rgba(255,255,255,0.06);
  border-radius: 8px;
  width: fit-content;
}
.tab-btn {
  position: relative;
  background: transparent;
  border: none;
  padding: 5px 12px;
  font-size: 12.5px;
  border-radius: 6px;
  color: rgba(255,255,255,0.6);
}
.tab-btn:hover { color: #fff; background: transparent; }
.tab-btn.active {
  background: rgba(var(--accent-rgb), 0.9);
  color: #1a1a1a;
  font-weight: 600;
}

/* 未读小红点：记事本按钮本身 + 记事本内各分类 tab 共用同一套样式 */
#btn-notebook { position: relative; }
.notif-dot {
  position: absolute;
  top: -1px;
  right: -1px;
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: var(--danger);
  box-shadow: 0 0 4px rgba(255,68,51,0.7);
}

.archive-entry {
  margin-bottom: 10px;
  padding: 10px 12px;
  background: rgba(255,255,255,0.04);
  border-radius: 8px;
}
.archive-entry-title { font-weight: 700; margin-bottom: 4px; color: var(--accent-2); }

.clue-list { padding-left: 18px; }

.signal-digest { margin-top: 12px; padding-top: 8px; border-top: 1px dashed var(--line); }
.signal-line { color: var(--accent-2); font-size: 0.9em; }

/* 仿 macOS 细条进度条 */
.progress-bar { background: rgba(255,255,255,0.08); height: 8px; border-radius: 4px; overflow: hidden; margin: 12px 0; }
.progress-fill { height: 100%; background: rgba(var(--accent-rgb), 0.95); }

/* 调查回顾：当天按调查顺序排列的事件记录列表，进度条留在滚动区域外面固定显示在最上方 */
.tracker-log { max-height: 55vh; overflow-y: auto; padding-right: 4px; }

/* 记事本：仿 Notes.app 的侧栏(分类) + 内容区两栏布局 */
.mac-split { display: flex; gap: 16px; align-items: flex-start; }
.mac-sidebar {
  flex: 0 0 128px;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.mac-pane {
  flex: 1;
  min-width: 0;
  max-height: 50vh;
  overflow-y: auto;
  padding-right: 4px;
}
.sidebar-item {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: space-between;
  text-align: left;
  background: transparent;
  border: none;
  padding: 6px 10px;
  border-radius: 6px;
  font-size: 13px;
  color: rgba(255,255,255,0.75);
}
.sidebar-item:hover { background: rgba(255,255,255,0.08); color: #fff; }
.sidebar-item.active {
  background: rgba(var(--accent-rgb), 0.85);
  color: #1a1a1a;
  font-weight: 600;
}
.sidebar-item .notif-dot { position: static; margin-left: 6px; flex-shrink: 0; }

.ending-title { font-size: 1.4em; color: var(--accent); margin-bottom: 10px; }
/* 后日谈那一页的小标题：结局正文已经给过一次"落幕"，这里要更轻，别抢正文 */
.ending-title-epilogue { font-size: 1.05em; color: #9a9a9a; letter-spacing: 0.08em; }

/* 关键词高亮：未收集显示蓝色链接（提示"点它"），点击收集词条后变灰（仿"访问过的链接"）。
   class 名沿用 kw-locked/kw-unlocked（对应 archive.isUnlocked 状态，JS 不用改），
   只是这里把视觉反过来映射：未解锁(kw-locked)=蓝色可点，已解锁(kw-unlocked)=灰色已读。 */
.kw { cursor: pointer; text-decoration: underline; }
.kw-locked { color: var(--accent-2); }
.kw-unlocked { color: #888; }

/* --- Toast：仿 macOS 通知中心右上角横幅 --- */
#toast {
  position: fixed;
  top: 74px;
  right: 16px;
  background: rgba(40,40,42,0.85);
  backdrop-filter: blur(20px) saturate(1.8);
  -webkit-backdrop-filter: blur(20px) saturate(1.8);
  border: 1px solid rgba(255,255,255,0.12);
  color: var(--text);
  padding: 12px 16px;
  border-radius: 12px;
  z-index: 200;
  max-width: 320px;
  width: max-content;
  font-size: 13px;
  line-height: 1.5;
  box-shadow: var(--window-shadow);
  transition: opacity 0.3s;
  animation: toastIn 0.25s ease-out;
}

@keyframes toastIn {
  from { opacity: 0; transform: translateY(-8px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* --- QQ/BB 事件后闲聊：跟事件绑定的对话，浮在弹窗之上，做成 iMessage 聊天串的样子。
   见 engine/dialogue.js + main.js playDialogue()。整块是一条连续的聊天记录：QQ 在左边
   发灰气泡、BB 在右边发蓝气泡，每条正文出现之前先冒一个"正在输入"的三点气泡。
   column-reverse + 新消息 prepend 到最前面：视觉上新消息从底部冒出来、旧消息被顶上去，
   顶部用渐变 mask 淡出而不是被硬切断。气泡尖角（tail）是纯 CSS：一个跟气泡同色的小方块，
   用 radial-gradient 当遮罩挖掉一个圆，剩下的弯钩就是 iMessage 那个尾巴，不需要图片。 --- */
.dialogue-overlay {
  position: fixed;
  inset: 0;
  z-index: 150; /* 盖过 modal-overlay(100)，讨论期间事件弹窗仍看得见、按钮锁着 */
  pointer-events: none;
}

.dlg-thread {
  /* iMessage 浅色模式取色：对方的灰气泡 / 自己发的蓝气泡 */
  --imsg-gray: #e9e9eb;
  --imsg-blue: #0b84ff;

  /* 落在屏幕右下角那片空地上，但不贴边：--dlg-gutter 是"事件弹窗右边缘到屏幕右边"的空档
     （弹窗最宽 560px、居中，所以是半屏减 280px），聊天串在这块空档里居中摆放。
     这样窗口放大时它跟着往里挪、窗口变窄时自动收窄，始终不压住中间的事件正文。 */
  --dlg-gutter: calc(50vw - 280px);
  --dlg-width: clamp(200px, calc(var(--dlg-gutter) - 48px), 400px);

  position: fixed;
  right: calc((var(--dlg-gutter) - var(--dlg-width)) / 2);
  bottom: 56px;
  width: var(--dlg-width);
  /* 固定高度（不是 max-height）：配合 column-reverse，消息始终贴着底部往上堆，
     上面那截是空的。高度由内容撑的话，下面那条按百分比算的淡出蒙版会一直啃在
     最上面一条消息上（名字被糊掉就是这么来的）。 */
  height: 48vh;
  display: flex;
  flex-direction: column-reverse;
  gap: 8px;
  overflow: hidden;
  pointer-events: none;
  /* 顶部淡出：只在消息多到顶满整列时才吃到，固定 40px 一条窄带，不按百分比算 */
  -webkit-mask-image: linear-gradient(to top, #000 calc(100% - 40px), transparent 100%);
          mask-image: linear-gradient(to top, #000 calc(100% - 40px), transparent 100%);
}

/* 一行 = 头像 + （名字 + 气泡）。QQ 的头像在气泡左边，BB 的用 row-reverse 挪到右边。 */
.dlg-row {
  display: flex;
  align-items: flex-end;
  gap: 10px; /* 留出气泡尖角（往外伸 8px）不顶到头像的余量 */
  animation: dlgRowIn 0.26s cubic-bezier(0.22, 1, 0.36, 1);
}
.dlg-row-qq { justify-content: flex-start; }
.dlg-row-bb { flex-direction: row-reverse; justify-content: flex-start; }

.dlg-body {
  display: flex;
  flex-direction: column;
  min-width: 0;
  max-width: calc(100% - 38px); /* 给头像（28px）+ gap 留位 */
}
.dlg-row-bb .dlg-body { align-items: flex-end; }

/* 头像：暂时纯色占位，字母只是临时标记。换真头像时把 background 换成
   url(../assets/avatars/qq.png) center/cover，再把 main.js 里填的字母去掉即可。 */
.dlg-avatar {
  flex: 0 0 auto;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 12px;
  font-weight: 700;
  color: #fff;
  user-select: none;
  background: var(--dlg-avatar-bg);
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
}
.dlg-row-qq .dlg-avatar { --dlg-avatar-bg: #ff9f0a; }
.dlg-row-bb .dlg-avatar { --dlg-avatar-bg: #bf5af0; }

/* 同一个人连着说的第二句起：不重复挂名字，间距收紧（对应 main.js 里的 dlg-row-cont） */
.dlg-row-cont { margin-top: -6px; }
.dlg-row-cont .dlg-name { display: none; }

.dlg-name {
  font-size: 11px;
  letter-spacing: 0.03em;
  color: rgba(255, 255, 255, 0.62);
  margin: 0 12px 3px;
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.8); /* 底下可能是浅色的地图，得压得住 */
}

.dlg-bubble {
  position: relative;
  max-width: 100%;
  padding: 8px 14px 9px;
  border-radius: 18px;
  font-size: 13px;
  line-height: 1.45;
  white-space: pre-wrap;
  overflow-wrap: break-word;
  pointer-events: auto;
  /* drop-shadow 会顺着尖角的形状走（box-shadow 不行，它只认矩形盒子） */
  filter: drop-shadow(0 1px 3px rgba(0, 0, 0, 0.3));
}

.dlg-row-qq .dlg-bubble {
  background: var(--imsg-gray);
  color: #000;
  border-bottom-left-radius: 5px;
}
.dlg-row-bb .dlg-bubble {
  background: var(--imsg-blue);
  color: #fff;
  border-bottom-right-radius: 5px;
}

/* 气泡尖角：18×18 的小方块 + 圆形遮罩，挖出 iMessage 那个往外弯的钩子 */
.dlg-bubble::after {
  content: "";
  position: absolute;
  bottom: 0;
  width: 18px;
  height: 18px;
}
.dlg-row-qq .dlg-bubble::after {
  left: -8px;
  background: var(--imsg-gray);
  border-bottom-left-radius: 3px;
  -webkit-mask: radial-gradient(circle 18px at 0 0, transparent 99%, #000 100%);
          mask: radial-gradient(circle 18px at 0 0, transparent 99%, #000 100%);
}
.dlg-row-bb .dlg-bubble::after {
  right: -8px;
  background: var(--imsg-blue);
  border-bottom-right-radius: 3px;
  -webkit-mask: radial-gradient(circle 18px at 100% 0, transparent 99%, #000 100%);
          mask: radial-gradient(circle 18px at 100% 0, transparent 99%, #000 100%);
}
/* 一串连发的消息里，只有最后一条挂尖角（iMessage 就是这么干的）：
   dlg-row-said 由 main.js 补给"后面还跟着同一个人的下一句"的那几行。 */
.dlg-row-said .dlg-bubble::after { display: none; }
/* 头像同理，只挂在一串连发的最后一条上；前面几条留住位置、不占地方地隐掉 */
.dlg-row-said .dlg-avatar { visibility: hidden; }
.dlg-row-said.dlg-row-qq .dlg-bubble { border-bottom-left-radius: 18px; }
.dlg-row-said.dlg-row-bb .dlg-bubble { border-bottom-right-radius: 18px; }

/* "正在输入"：三个点在气泡里依次起伏 */
.dlg-typing {
  display: flex;
  align-items: center;
  gap: 5px;
  padding: 12px 14px;
}
.dlg-typing i {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: rgba(0, 0, 0, 0.32);
  animation: dlgTypingDot 1.25s infinite ease-in-out;
}
.dlg-row-bb .dlg-typing i { background: rgba(255, 255, 255, 0.75); }
.dlg-typing i:nth-child(2) { animation-delay: 0.18s; }
.dlg-typing i:nth-child(3) { animation-delay: 0.36s; }

/* 气泡里的关键词链接：默认那套（亮蓝 / 灰）在浅色气泡上看不清，这里按气泡底色重配。
   语义不变：kw-locked = 还没收进记事本（可点），kw-unlocked = 已收集（"访问过的链接"）。 */
.dlg-row-qq .dlg-bubble .kw-locked { color: #0b6fd8; }
.dlg-row-qq .dlg-bubble .kw-unlocked { color: #8e8e93; }
.dlg-row-bb .dlg-bubble .kw-locked { color: #fff; }
.dlg-row-bb .dlg-bubble .kw-unlocked { color: rgba(255, 255, 255, 0.62); }

@keyframes dlgRowIn {
  from { opacity: 0; transform: translateY(10px) scale(0.94); }
  to   { opacity: 1; transform: translateY(0) scale(1); }
}

/* 三点变成正文的那一下：从尖角那侧撑开 */
@keyframes dlgBubbleIn {
  from { opacity: 0.35; transform: scale(0.92); }
  to   { opacity: 1; transform: scale(1); }
}
.dlg-row-qq .dlg-bubble-in { animation: dlgBubbleIn 0.2s ease-out; transform-origin: left bottom; }
.dlg-row-bb .dlg-bubble-in { animation: dlgBubbleIn 0.2s ease-out; transform-origin: right bottom; }

@keyframes dlgTypingDot {
  0%, 60%, 100% { opacity: 0.35; transform: translateY(0); }
  30%           { opacity: 1;    transform: translateY(-3px); }
}

/* --- 开始剪辑：仿简易剪辑软件的复盘 UI（素材库 + 时间轴，拖拽拼顺序） --- */
.modal-box.modal-wide { max-width: 860px; }

.editor-brief { margin-bottom: 4px; }
.editor-brief .event-text { margin-bottom: 4px; }

.editor-panel-label {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.45);
  margin: 16px 0 8px;
}
.editor-panel-label:first-of-type { margin-top: 8px; }

/* 素材库：已收集的素材以"视频素材"图标摆放，可拖拽 */
.clip-library {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  min-height: 96px;
  padding: 10px;
  background: rgba(255, 255, 255, 0.03);
  border: 1px solid var(--window-border);
  border-radius: 8px;
}

.clip-icon {
  width: 92px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  padding: 8px 6px;
  background: rgba(255, 255, 255, 0.05);
  border: 1px solid rgba(255, 255, 255, 0.1);
  border-radius: 8px;
  cursor: grab;
  text-align: center;
  user-select: none;
  touch-action: none; /* 手指按住就直接进入拖拽判定，不让浏览器把这个手势当成页面滚动 */
}
.clip-icon:hover { border-color: var(--accent-2); background: rgba(255, 255, 255, 0.08); }
.clip-icon.dragging { opacity: 0.35; }

.clip-icon-thumb { font-size: 22px; line-height: 1; }
.clip-icon-label { font-size: 11px; line-height: 1.35; color: var(--text); }

.clip-tag {
  font-size: 10px;
  padding: 1px 6px;
  border-radius: 999px;
  font-weight: 700;
}
.clip-tag.tag-low  { background: rgba(255, 255, 255, 0.1); color: #aaa; }
.clip-tag.tag-mid  { background: rgba(var(--accent2-rgb), 0.2); color: var(--accent-2); }
.clip-tag.tag-high { background: rgba(var(--accent-rgb), 0.24); color: var(--accent); }

/* 时间轴：拖放区，落下的素材变成长方形，长度按重要度分档 */
.timeline-track {
  display: flex;
  align-items: stretch;
  gap: 6px;
  min-height: 64px;
  padding: 8px;
  background: rgba(0, 0, 0, 0.25);
  border: 1px dashed var(--line);
  border-radius: 8px;
  overflow-x: auto;
}
.timeline-track.drag-over { border-color: var(--accent-2); background: rgba(var(--accent2-rgb), 0.08); }

.timeline-empty {
  margin: auto;
  color: #777;
  font-size: 12.5px;
}

.clip-block {
  position: relative;
  display: flex;
  align-items: center;
  flex-shrink: 0;
  padding: 0 22px 0 10px;
  border-radius: 6px;
  background: rgba(var(--accent2-rgb), 0.22);
  border: 1px solid rgba(var(--accent2-rgb), 0.55);
  color: #fff;
  font-size: 11.5px;
  cursor: grab;
  white-space: nowrap;
  overflow: hidden;
  touch-action: none;
}
.clip-block.dragging { opacity: 0.35; }

/* 触屏拖拽时跟着手指走的浮动副本，鼠标端的原生拖拽不会用到这个 */
.clip-drag-ghost {
  position: fixed;
  z-index: 10000;
  pointer-events: none;
  opacity: 0.85;
  transform: translate(-50%, -50%);
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.4);
}
.clip-block-label { overflow: hidden; text-overflow: ellipsis; }
.clip-block.imp-low  { width: 64px; }
.clip-block.imp-mid  { width: 110px; }
.clip-block.imp-high { width: 160px; }

.clip-block-remove {
  position: absolute;
  right: 2px;
  top: 50%;
  transform: translateY(-50%);
  width: 16px;
  height: 16px;
  padding: 0;
  border: none;
  border-radius: 50%;
  background: rgba(0, 0, 0, 0.35);
  color: #fff;
  font-size: 11px;
  line-height: 1;
  display: flex;
  align-items: center;
  justify-content: center;
}
.clip-block-remove:hover { background: var(--danger); }

.editor-feedback {
  margin-top: 12px;
  padding: 8px 10px;
  border-radius: 8px;
  font-size: 13px;
  line-height: 1.6;
}
.editor-feedback.feedback-ok   { background: rgba(40, 200, 90, 0.14); border: 1px solid rgba(40, 200, 90, 0.4); color: #a8f0c0; }
.editor-feedback.feedback-bad  { background: rgba(var(--danger-rgb), 0.14); border: 1px solid rgba(var(--danger-rgb), 0.4); color: #ff9a8c; }

.editor-actions {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  margin-top: 14px;
}

/* 素材简介小窗：叠在剪辑台窗口之上，点素材图标弹出，同一套 mac 窗口质感但更窄。 */
.clip-info-overlay { z-index: 150; }
.modal-box.modal-box-small { max-width: 360px; }
.clip-info-desc { line-height: 1.7; margin: 10px 0 0; }

/* --- 理智值 UI 反馈（5.5 节：氛围化呈现，不直接摆数字） ---
   闪烁做在 body::after 这一层纯色遮罩上，只动 opacity。
   不要改回"给 #app 上 filter 动画"的写法：祖先带着一个正在跑的 filter，
   整棵子树每帧都要重新栅格化（关键帧里 96% 的时间是 none 也没用，动画在跑就一直算），
   而 #app 里装着整张地图、两张 1536x1024 的图和墨迹 canvas；
   filter 还会建立新的 containing block，把 .map-content 的 will-change:transform 废掉，
   平移缩放从 GPU 合成退化成重栅格化，顺带让状态栏/弹窗的 backdrop-filter 每帧重新采样。
   opacity 是合成器属性，整条动画不碰主线程重绘。
   z-index 60：盖过状态栏(50)，但压在弹窗(100)之下——跟原先 filter 挂在 #app 上时的覆盖范围一致。 */
body::after {
  content: "";
  display: none;
  position: fixed;
  inset: 0;
  z-index: 60;
  pointer-events: none;
  opacity: 0;
}

body.sanity-shaken::after {
  display: block;
  background: #000;
  animation: shakenFlicker 6s infinite;
}
body.sanity-breaking::after {
  display: block;
  background: #1a0603; /* 压暗的同时带一点暖红，替掉原来的 hue-rotate */
  animation: breakingFlicker 3s infinite;
}

@keyframes shakenFlicker {
  0%, 96%, 100% { opacity: 0; }
  97% { opacity: 0.07; }
}
@keyframes breakingFlicker {
  0%, 90%, 100% { opacity: 0; }
  92% { opacity: 0.26; }
  94% { opacity: 0; }
  96% { opacity: 0.17; }
}

/* --- 移动端竖屏提示：默认不显示；只在"触屏 + 竖屏"时盖住全部内容，
   不影响桌面端把浏览器窗口拖窄的情况（pointer: coarse 排除鼠标设备）。
   纯 CSS 媒体查询驱动，不依赖 JS，页面还没加载完也能生效。 --- */
#orientation-overlay {
  display: none;
}

@media screen and (orientation: portrait) and (pointer: coarse) {
  #orientation-overlay {
    display: flex;
    position: fixed;
    inset: 0;
    z-index: 9999;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 10px;
    padding: 24px;
    background: var(--bg);
    text-align: center;
  }
}

.orientation-icon {
  font-size: 48px;
  animation: orientationRotate 1.6s ease-in-out infinite;
}

.orientation-text {
  font-size: 16px;
  color: var(--text);
}

.orientation-hint {
  font-size: 12px;
  color: #888;
}

@keyframes orientationRotate {
  0%, 100% { transform: rotate(0deg); }
  50% { transform: rotate(90deg); }
}

/* ===================================================================
   地震演出（engine/quake.js）
   对应 design-doc.md 1.6 节的两次现实地震。要的是"没头没尾地晃了一下、
   然后一切安静下来"，不是爆炸式的高潮：振幅从头衰减到尾，尾帧回到原位。

   只动 transform / opacity 两个合成器属性——理由跟上面理智闪烁那一节一样，
   祖先上挂 filter 会把 .map-content 的 will-change:transform 废掉。
   动画是一次性的，quake.js 跑完就把 .quaking 摘掉，不留常驻开销。

   振幅由 --quake-amp 传进来（JS 按震级设），关键帧里用倍数写出衰减包络，
   这样同一份关键帧既能演 M6.4 的"晃一下"也能演 M7.1 的"站不住"。
   统一带 scale(1.015)：#app 被平移时边缘会露出 body 底色，靠这点放大顶出屏幕外。
   =================================================================== */

/* 世界（状态栏 + 地图）：横向为主、纵向略带，尾巴上补一点旋转，避免看起来像纯平移 */
@keyframes quakeShakeWorld {
  0%   { transform: scale(1.015) translate3d(0, 0, 0) rotate(0deg); }
  4%   { transform: scale(1.015) translate3d(calc(var(--quake-amp) * -1), calc(var(--quake-amp) * 0.45), 0) rotate(-0.28deg); }
  9%   { transform: scale(1.015) translate3d(calc(var(--quake-amp) * 0.95), calc(var(--quake-amp) * -0.35), 0) rotate(0.22deg); }
  15%  { transform: scale(1.015) translate3d(calc(var(--quake-amp) * -0.8), calc(var(--quake-amp) * -0.5), 0) rotate(-0.18deg); }
  22%  { transform: scale(1.015) translate3d(calc(var(--quake-amp) * 0.85), calc(var(--quake-amp) * 0.3), 0) rotate(0.24deg); }
  30%  { transform: scale(1.015) translate3d(calc(var(--quake-amp) * -0.62), calc(var(--quake-amp) * 0.4), 0) rotate(-0.15deg); }
  38%  { transform: scale(1.015) translate3d(calc(var(--quake-amp) * 0.7), calc(var(--quake-amp) * -0.25), 0) rotate(0.16deg); }
  47%  { transform: scale(1.015) translate3d(calc(var(--quake-amp) * -0.45), calc(var(--quake-amp) * -0.3), 0) rotate(-0.1deg); }
  56%  { transform: scale(1.015) translate3d(calc(var(--quake-amp) * 0.5), calc(var(--quake-amp) * 0.22), 0) rotate(0.11deg); }
  65%  { transform: scale(1.015) translate3d(calc(var(--quake-amp) * -0.3), calc(var(--quake-amp) * 0.16), 0) rotate(-0.07deg); }
  74%  { transform: scale(1.015) translate3d(calc(var(--quake-amp) * 0.26), calc(var(--quake-amp) * -0.14), 0) rotate(0.05deg); }
  83%  { transform: scale(1.012) translate3d(calc(var(--quake-amp) * -0.15), 0, 0) rotate(-0.03deg); }
  92%  { transform: scale(1.008) translate3d(calc(var(--quake-amp) * 0.08), 0, 0) rotate(0deg); }
  100% { transform: scale(1) translate3d(0, 0, 0) rotate(0deg); }
}

/* 弹窗（那台"玩家自己的电脑"）：振幅约为世界的六成，相位跟世界错开，两层不同步才像真的 */
@keyframes quakeShakeWindow {
  0%   { transform: translate3d(0, 0, 0) rotate(0deg); }
  6%   { transform: translate3d(calc(var(--quake-amp) * 0.6), calc(var(--quake-amp) * -0.3), 0) rotate(0.2deg); }
  14%  { transform: translate3d(calc(var(--quake-amp) * -0.55), calc(var(--quake-amp) * 0.32), 0) rotate(-0.22deg); }
  24%  { transform: translate3d(calc(var(--quake-amp) * 0.48), calc(var(--quake-amp) * 0.2), 0) rotate(0.14deg); }
  35%  { transform: translate3d(calc(var(--quake-amp) * -0.4), calc(var(--quake-amp) * -0.22), 0) rotate(-0.12deg); }
  48%  { transform: translate3d(calc(var(--quake-amp) * 0.3), calc(var(--quake-amp) * 0.14), 0) rotate(0.08deg); }
  62%  { transform: translate3d(calc(var(--quake-amp) * -0.2), calc(var(--quake-amp) * -0.1), 0) rotate(-0.05deg); }
  78%  { transform: translate3d(calc(var(--quake-amp) * 0.12), 0, 0) rotate(0deg); }
  100% { transform: translate3d(0, 0, 0) rotate(0deg); }
}

body.quaking #app {
  animation: quakeShakeWorld var(--quake-shake-ms, 1300ms) cubic-bezier(0.36, 0.07, 0.19, 0.97) both;
  will-change: transform;
}

body.quaking #modal-box {
  animation: quakeShakeWindow var(--quake-shake-ms, 1300ms) cubic-bezier(0.36, 0.07, 0.19, 0.97) both;
  will-change: transform;
}

/* 落灰层：白湖镇的灰是盐碱粉尘，所以偏冷的米白而不是土黄。
   z-index 250 盖过 toast(200)，压在竖屏提示(9999)之下；pointer-events:none 不挡点击。
   淡入快（0.18s，跟着第一下晃动一起来）、淡出慢（1.4s，在死寂里慢慢沉）。 */
#quake-overlay {
  position: fixed;
  inset: 0;
  z-index: 250;
  pointer-events: none;
  opacity: 0;
  transition: opacity 1.4s ease-out;
}

/* 演出期间（quake.js 加的 class，含抖完那一拍死寂）让落灰层接管点击，
   挡住"抖到一半玩家又点了一下地图"这种两条流程叠在一起的情况 */
body.quake-blocking #quake-overlay { pointer-events: auto; }

#quake-overlay.quake-dust-on {
  opacity: var(--quake-dust, 1);
  transition: opacity 0.18s ease-out;
}

/* 从上往下压的一层灰幕 + 四角压暗，制造"顶上在掉东西"的方向感 */
.quake-veil {
  position: absolute;
  inset: 0;
  background:
    linear-gradient(to bottom, rgba(216, 209, 193, 0.17), rgba(216, 209, 193, 0.05) 36%, transparent 68%),
    radial-gradient(120% 90% at 50% 40%, transparent 55%, rgba(0, 0, 0, 0.35) 100%);
}

/* 粉尘颗粒：纯 CSS 渐变点阵，不用图。
   关键是别让人看出"这是一张平铺的网格"：用四层互质的间距（37/53/89/139）叠起来，
   每层再错开 background-position，两组颗粒（本体和 ::after）以不同速度下落形成视差——
   几层错位的规则网格叠在一起，眼睛就读成随机的了。颗粒边缘用较宽的渐变过渡收住，
   免得在 1x 屏上变成一颗颗硬邦邦的死像素。 */
.quake-dust {
  position: absolute;
  inset: -30% 0 -10% 0;
  background-image:
    radial-gradient(circle, rgba(236, 231, 216, 0.42) 0.6px, transparent 2.2px),
    radial-gradient(circle, rgba(236, 231, 216, 0.26) 1.1px, transparent 3.4px);
  background-size: 37px 37px, 89px 89px;
  background-position: 0 0, 13px 31px;
  animation: quakeDustFall 3.4s linear infinite;
  will-change: transform;
}

/* 第二组颗粒：更稀、更慢，跟上面那组错开位置，叠出视差 */
.quake-dust::after {
  content: "";
  position: absolute;
  inset: 0;
  background-image:
    radial-gradient(circle, rgba(236, 231, 216, 0.34) 0.8px, transparent 2.6px),
    radial-gradient(circle, rgba(230, 226, 212, 0.2) 1.4px, transparent 4.2px);
  background-size: 53px 53px, 139px 139px;
  background-position: 21px 7px, 67px 43px;
  animation: quakeDustFall 5.6s linear infinite;
}

@keyframes quakeDustFall {
  from { transform: translate3d(0, -12%, 0); }
  to   { transform: translate3d(0, 24%, 0); }
}

/* ===================================================================
   逐字 / 逐句浮现（engine/reveal.js）
   缓动统一 cubic-bezier(0.22,1,0.36,1)：先快后极慢地停住，收尾"落定"感最强。
   延迟 = 基准 + 序号 × 步长，序号写在每个 span 的 --i 上，由 reveal.js 生成。
   =================================================================== */

.rv-char {
  display: inline-block;
  animation: rvCharUp 0.7s cubic-bezier(0.22, 1, 0.36, 1) both;
  animation-delay: calc(var(--rv-base, 0.15s) + var(--i, 0) * var(--rv-step, 0.05s));
}

@keyframes rvCharUp {
  from { opacity: 0; transform: translateY(0.5em); }
  to   { opacity: 1; transform: translateY(0); }
}

/* 行内只淡入不位移：inline 元素上的 transform 无效，改 inline-block 又会让长句不能折行 */
.rv-clause {
  animation: rvFadeIn 0.7s cubic-bezier(0.22, 1, 0.36, 1) both;
  animation-delay: calc(var(--rv-base, 0.15s) + var(--i, 0) * var(--rv-step, 0.18s));
}

@keyframes rvFadeIn {
  from { opacity: 0; }
  to   { opacity: 1; }
}

/* 需要"整段往上浮"时套在块级元素上，位移只做一次，不摊到每个行内 span */
.rv-block {
  animation: rvBlockUp 0.7s cubic-bezier(0.22, 1, 0.36, 1) both;
  animation-delay: calc(var(--rv-base, 0.15s) + var(--i, 0) * var(--rv-step, 0.18s));
}

@keyframes rvBlockUp {
  from { opacity: 0; transform: translateY(16px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* 系统开了"减少动态效果"：位移和抖动全关，只保留淡入，信息一样能读到 */
@media (prefers-reduced-motion: reduce) {
  .quake-dust { animation: none; }
  .rv-char { animation-name: rvFadeIn; }
  .rv-block { animation-name: rvFadeIn; }
  .rv-char, .rv-clause, .rv-block { animation-duration: 0.4s; }
}
