/* ==========================================================================
   DAWN.C CREATIVE PORTFOLIO - EDITORIAL ILLUSTRATION DESIGN SYSTEM
   Keywords: stylized digital painting / simplified geometric shapes /
             broad flat color masses / restrained color palette /
             subtle painterly texture / minimal details / simplified lighting
   ========================================================================== */

:root {
  --font-serif: 'Noto Serif SC', 'Cinzel', Georgia, serif;
  --font-sans: 'Plus Jakarta Sans', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  --font-mono: "SF Mono", "Courier New", Courier, monospace;

  /* Restrained Editorial Palette */
  --color-bg-base: #ede7dc;
  --color-text-main: #231e1b;
  --color-text-muted: #6e645d;
  --color-text-faint: #9c9187;
  
  --color-kraft-paper: #d7c3a7;
  --color-kraft-dark: #b89f81;
  --color-kraft-light: #f4ece1;
  --color-kraft-spine: #8f7556;
  
  --color-warm-wood: #322117;
  --color-wood-mid: #50392b;
  --color-desk-wood: #26170f;
  
  --color-lamp-warm: rgba(254, 219, 114, 0.22);
  --color-lamp-intense: rgba(255, 238, 170, 0.45);
  
  --color-terracotta: #b74838;
  --color-sage: #5e8250;
  --color-sage-soft: #8ca381;
  --color-lake-blue: #4f7b94;
  --color-butter-yellow: #fad36d;
  --color-coffee-dark: #3e271a;
  --color-crema: #d89e5a;

  /* Shadows & Curves */
  --shadow-editorial: 0 12px 36px rgba(28, 20, 16, 0.12);
  --shadow-soft: 0 4px 18px rgba(35, 30, 27, 0.08);
  --shadow-kraft: 0 16px 40px rgba(20, 14, 11, 0.35);
  --radius-sm: 4px;
  --radius-md: 8px;
  --radius-lg: 16px;
  --radius-full: 9999px;

  /* Header Height */
  --header-height: 68px;
}

/* Base Reset */
*, *::before, *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

html, body {
  width: 100%;
  height: 100%;
  overflow: hidden;
  background-color: var(--color-bg-base);
  color: var(--color-text-main);
  font-family: var(--font-sans);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  user-select: none;
}

/* Subtle Painterly Paper Grain Texture across entire screen */
body::after {
  content: "";
  position: fixed;
  inset: 0;
  pointer-events: none;
  background-image: radial-gradient(rgba(0,0,0,0.02) 1px, transparent 0);
  background-size: 8px 8px;
  opacity: 0.7;
  z-index: 999;
}

/* ================= HEADER & NAV BAR ================= */
.site-header {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: var(--header-height);
  z-index: 100;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 2.5rem;
  background: linear-gradient(180deg, rgba(24, 18, 14, 0.65) 0%, rgba(24, 18, 14, 0.2) 70%, transparent 100%);
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
  /* 原来这里是 transition: all —— 连 backdrop-filter 都会去做过渡动画（很贵且没有意义）。
     顶栏本来也没有别的状态要过渡，只留背景即可。 */
  transition: background 0.3s ease;
}

/* ★ 场景切换期间临时摘掉顶栏的背景模糊。
   顶栏是 position:fixed + backdrop-filter，横跨整个视口宽度 ——
   它背后的画面一变，这层模糊就得整块重算。而切场景时背后正是两幅整屏插画在交叉溶解，
   于是每帧重算一次全宽模糊。隔离实测：这是切换掉帧的最大来源
   （最长帧 83ms → 50ms，是所有单项里收益最大的一个）。
   静止时它照常显示（视觉完全不变），只在切换的那 0.95 秒里摘掉。 */
html.is-switching .site-header {
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}

.brand-signature {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  text-decoration: none;
  color: #fff;
  font-family: var(--font-serif);
  font-weight: 600;
  font-size: 1.15rem;
  letter-spacing: 0.05em;
  text-shadow: 0 1px 4px rgba(0,0,0,0.4);
}

.brand-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background-color: var(--color-butter-yellow);
  box-shadow: 0 0 8px var(--color-butter-yellow);
}

.header-nav {
  display: flex;
  align-items: center;
  gap: 1.75rem;
  background: rgba(26, 20, 16, 0.45);
  padding: 0.35rem 0.6rem;
  border-radius: var(--radius-full);
  border: 1px solid rgba(255, 255, 255, 0.15);
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
}

.nav-item {
  background: transparent;
  border: none;
  cursor: pointer;
  padding: 0.45rem 1rem;
  border-radius: var(--radius-full);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  color: rgba(255, 255, 255, 0.85);
  font-family: var(--font-sans);
  transition: all 0.25s ease;
  position: relative;
}

.nav-item .nav-zh {
  font-size: 0.85rem;
  font-weight: 600;
  letter-spacing: 0.1em;
  line-height: 1.1;
}

.nav-item .nav-en {
  font-size: 0.65rem;
  font-weight: 400;
  letter-spacing: 0.08em;
  opacity: 0.75;
  text-transform: uppercase;
  margin-top: 2px;
}

.nav-item:hover {
  color: #fff;
  background: rgba(255, 255, 255, 0.1);
}

.nav-item.active {
  color: #1f1813;
  background: #fbf7ee;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.18);
}

.nav-item.active .nav-zh {
  color: #1f1813;
}

.nav-item.active .nav-en {
  color: var(--color-terracotta);
  font-weight: 600;
  opacity: 1;
}

.header-actions {
  display: flex;
  align-items: center;
  gap: 1rem;
}

.action-btn {
  background: rgba(26, 20, 16, 0.45);
  border: 1px solid rgba(255, 255, 255, 0.2);
  color: #fff;
  border-radius: var(--radius-full);
  padding: 0.45rem 0.95rem;
  display: flex;
  align-items: center;
  gap: 0.5rem;
  cursor: pointer;
  font-size: 0.75rem;
  font-weight: 500;
  letter-spacing: 0.05em;
  transition: all 0.25s ease;
}

.action-btn:hover {
  background: rgba(255, 255, 255, 0.2);
  border-color: rgba(255, 255, 255, 0.4);
}

.audio-waves {
  display: flex;
  align-items: flex-end;
  gap: 2px;
  height: 12px;
}

.wave-bar {
  width: 2px;
  height: 4px;
  background-color: var(--color-butter-yellow);
  border-radius: 1px;
  transition: height 0.2s ease;
}

.action-btn.playing .wave-bar:nth-child(1) { animation: soundwave 1s ease-in-out infinite 0.1s; }
.action-btn.playing .wave-bar:nth-child(2) { animation: soundwave 1s ease-in-out infinite 0.3s; }
.action-btn.playing .wave-bar:nth-child(3) { animation: soundwave 1s ease-in-out infinite 0.2s; }
.action-btn.playing .wave-bar:nth-child(4) { animation: soundwave 1s ease-in-out infinite 0.4s; }

@keyframes soundwave {
  0%, 100% { height: 3px; }
  50% { height: 12px; }
}

/* ================= VIEWPORT & SCENES ================= */
.viewport-stage {
  position: relative;
  width: 100vw;
  height: 100vh;
  overflow: hidden;
  /* FIX: 离场场景的 .scene{transform:scale(1.02)} 会把本容器的「可视溢出区」
     撑大（实测 1920x1080 下 scrollWidth 1939 / scrollHeight 1091，360x640 下
     364 / 646），于是这个 overflow:hidden 的盒子变成了一个「可程序化滚动」的
     滚动容器：任何元素被 .focus() / scrollIntoView() 带进来时，整页会平移最多
     约 19px。overflow:clip 的裁剪效果与 hidden 完全一致，但不建立滚动容器，
     从根上消除这种位移；老浏览器不认识 clip 时自动退回上一行的 hidden。 */
  overflow: clip;
}

.scene {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  /* 默认：彻底隐藏，完全不参与合成（软件渲染下这是最省的做法）。 */
  opacity: 0;
  pointer-events: none;
  visibility: hidden;
  /* 离场：快速淡出。原来 visibility 是立即生效的 —— 旧场景「啪」地消失、
     新场景再从全透明慢慢浮现，中间直接露出 body 底色，所以切换看着很生硬。

     ★ 这里刻意**不再做 transform: scale()**。
       缩放整屏图层会让浏览器在动画的每一帧重新栅格化整屏内容 ——
       有 GPU 加速时这步几乎免费，没有时就是纯 CPU 重采样。
       隔离实测（热路径、软件渲染、1440×900）：
         带 scale   最长帧 67ms · >50ms 的卡顿帧 6 个
         去掉 scale 最长帧 34ms · >50ms 的卡顿帧 0 个
       「交叉溶解」的观感由 opacity 完整保留，缩放只是锦上添花，不值这个代价。 */
  transition: opacity 0.42s cubic-bezier(0.4, 0, 0.7, 0.2);
}

/* ★★ 有 GPU 加速时：非当前场景停在 0.004（而不是 0）并保持可见。
   浏览器会**跳过完全透明图层的栅格化** —— 原来的 opacity:0 + visibility:hidden
   意味着 4 个非当前场景从来没被画过，第一次切过去时才现场把整屏内容
   （插画 + 全部叠加元素）栅格化一遍，那一帧实测要 100~220ms，
   就是「第一次点某个页面会卡一下」的来源。
   停在 0.4% 透明度上，图层一直保有栅格化结果，切换时只做图层混合。
   实测（冷启动首次切换、开 GPU）：最长帧 220ms → 15ms。
   0.4% 叠在画面上肉眼完全看不出来。

   ★ 为什么加 `html.has-gpu` 这个开关而不是无条件用：
     软件渲染（没有 GPU 加速）时，5 个全屏图层每帧都要参与合成，
     实测热路径最长帧反而从 50ms 涨到 133ms。所以按能力分流：
       有 GPU → 保持栅格化（切换丝滑）
       没 GPU → 彻底隐藏（不合成看不见的图层）
     判断放在 app.js 里（读 WebGL 的 renderer 字符串，SwiftShader / llvmpipe
     这类软件光栅器就当作没有 GPU）。

   可点性与无障碍不受影响：pointer-events 关掉，另外 JS 会给非当前场景
   加 inert + aria-hidden，屏幕阅读器和 Tab 依然只碰得到当前场景。 */
html.has-gpu .scene:not(.active) {
  opacity: 0.004;
  visibility: visible;
}

/* ★ 必须**静态**挂在 .scene 上，不能等切换时才加。
   加了之后浏览器会在页面加载阶段就把 5 个场景各自提升成合成层、栅格化好，
   过渡期间只做图层混合，不再重新栅格化整屏内容。
   隔离实测（冷启动首次切换、开 GPU）：最长帧 253ms → 67ms。
   ★ 注意别改成「切换时临时加」—— 那一刻才建层 + 整屏重新栅格化，实测反而涨到 227ms。 */
.scene {
  will-change: opacity;
}

.scene.active {
  opacity: 1;
  pointer-events: auto;
  visibility: visible;
  /* 入场：更慢 + 强减速曲线，与离场形成「快出慢进」的节奏差，
     两段重叠约 0.42s 构成真正的交叉溶解，而不是先黑一下再出现 */
  transition: opacity 0.58s cubic-bezier(0.16, 1, 0.3, 1);
}

/* 场景内容错峰入场。这里用独立的 translate 属性而不是 transform ——
   .contact-editorial-panel / .home-hero-text 等已经在用 transform 做居中，
   若动画改 transform 会把它们的定位一起冲掉。 */
/* 注意 .contact-editorial-panel 不在这条规则里：它的显隐由 .is-open 控制，
   而这条动画带 `both`，会把 opacity 锁死在 1 —— 那样「默认收起」就永远不成立。
   它的展开动效由 .contact-editorial-panel / .is-open 自己的 transition 负责。 */
.scene.active .home-hero-text {
  animation: scene-content-in 0.72s cubic-bezier(0.16, 1, 0.3, 1) 0.13s both;
}

@keyframes scene-content-in {
  0%   { opacity: 0; translate: 0 22px; }
  100% { opacity: 1; translate: 0 0; }
}

/* 历史遗留提醒：如果以后又要给某个「内部含 position:fixed 后代」的容器做入场动画，
   只能用 opacity 做（`translate` / `transform` 哪怕值是 0 0 也会创建包含块，
   把 fixed 后代钉死在容器里）。原咖啡文案层就是踩了这个坑，才额外定义过一个
   纯 opacity 的 scene-fade-in —— 该层已整块移除，keyframes 一并删掉。 */

.scene-canvas-wrap {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: hidden;
}

.scene-bg-img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
  display: block;
  /* ★ 这里原来挂着 filter: contrast(102%) brightness(99%)。
     数值小到肉眼看不出来，代价却是**每张整屏插画都要多走一遍滤镜管线**，
     而且会把它单独提升成一个合成层。切场景时两幅插画同时在动，这个开销直接变成掉帧。
     实测去掉它：最长帧 83ms → 67ms。视觉上没有任何区别，所以直接删掉。 */
}

.scene-particle-overlay {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
}

/* ================= INTERACTIVE HOTSPOTS ================= */
.interactive-element {
  position: absolute;
  cursor: pointer;
  z-index: 20;
  transition: transform 0.25s cubic-bezier(0.34, 1.56, 0.64, 1);
  /* 按钮重置：首页的两个热区（猫、小屋）现在是原生 <button>，
     不清掉浏览器默认外观就会渲染成两块白板盖在插画上。 */
  margin: 0;
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  appearance: none;
  -webkit-appearance: none;
  -webkit-tap-highlight-color: transparent;
}

.interactive-element:focus-visible { outline: none; }

.interactive-element:hover {
  transform: scale(1.05);
}

.hotspot-tag {
  position: absolute;
  bottom: -32px;
  left: 50%;
  transform: translateX(-50%) translateY(4px);
  background: rgba(30, 24, 20, 0.85);
  backdrop-filter: blur(6px);
  color: #fff;
  padding: 4px 10px;
  border-radius: var(--radius-full);
  font-size: 0.72rem;
  font-weight: 500;
  white-space: nowrap;
  pointer-events: none;
  opacity: 0;
  transition: all 0.25s ease;
  display: flex;
  align-items: center;
  gap: 4px;
  border: 1px solid rgba(255, 255, 255, 0.15);
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
}

.hotspot-tag.accent {
  background: rgba(183, 72, 56, 0.9);
  border-color: rgba(255, 220, 200, 0.3);
}

.interactive-element:hover .hotspot-tag {
  opacity: 1;
  transform: translateX(-50%) translateY(0);
}

/* ================= SCENE 1: HOME DETAILS =================
   两个可点物件（小黑猫、红屋顶小屋）都放在 .home-art-layer 里，
   用「插画像素百分比」定位 —— 原先是「视口百分比 + 固定 px」，
   换个视口就漂走（实测 1440×900 下猫的热区比猫本体低了将近 100 原图像素）。
   坐标全部来自对 assets/home_bg.jpg 做像素级实测（近黑像素扫描 + 坐标网格读数）。 */

/* ---------- 插画坐标层 ---------- */
.home-art-layer {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  width: max(100%, calc(100vh * 1376 / 768));
  height: max(100%, calc(100vw * 768 / 1376));
  pointer-events: none;
}
/* 层本身不吃事件（它整层盖在插画上），只有两个按钮吃 */
.home-art-layer .interactive-element { pointer-events: auto; }

/* ---------- 小黑猫 ----------
   实测包围盒：原图 x629..728 / y512..640（含向右卷起的尾巴）。
   四边各留 ~6px 余量，好点。 */
.cat-hotspot {
  left: 45.203%;               /* 622/1376 */
  top: 65.755%;                /* 505/768  */
  width: 8.285%;               /* 114/1376 */
  height: 18.620%;             /* 143/768  */
}

.cat-speech-bubble {
  position: absolute;
  bottom: calc(100% + 6px);
  left: 50%;
  transform: translateX(-50%) scale(0.8);
  background: #ffffff;
  color: #1f1813;
  padding: 4px 10px;
  border-radius: 12px;
  font-size: 0.75rem;
  font-weight: 600;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
  opacity: 0;
  pointer-events: none;
  transition: all 0.25s cubic-bezier(0.34, 1.56, 0.64, 1);
  white-space: nowrap;
}

.cat-speech-bubble::after {
  content: "";
  position: absolute;
  bottom: -5px;
  left: 50%;
  transform: translateX(-50%);
  border-left: 5px solid transparent;
  border-right: 5px solid transparent;
  border-top: 5px solid #ffffff;
}

.cat-speech-bubble.show {
  opacity: 1;
  transform: translateX(-50%) scale(1);
}

/* ---------- 红屋顶小屋 ----------
   实测包围盒：原图 x1085..1255 / y502..650（含烟囱）。 */
.cottage-hotspot {
  left: 78.852%;               /* 1085/1376 */
  top: 65.365%;                /* 502/768   */
  width: 12.355%;              /* 170/1376  */
  height: 19.271%;             /* 148/768   */
}

/* 烟囱冒的烟。烟囱口实测在原图 (1197, 507) —— 相对小屋热区是「距右 34%、顶部上方」。
   尺寸全部用百分比表达，这样烟会跟插画一起缩放（原来写死 px，视口一变就不成比例）。 */
.chimney-smoke {
  position: absolute;
  top: -17%;
  right: 28%;
  width: 12%;
  height: 28%;
  pointer-events: none;
}

.smoke-puff {
  position: absolute;
  bottom: 0;
  left: 50%;
  border-radius: 50%;
  background: rgba(245, 248, 250, 0.7);
  animation: smoke-rise 3.2s infinite ease-out;
}

.smoke-puff.p1 { width: 50%; aspect-ratio: 1; animation-delay: 0s; }
.smoke-puff.p2 { width: 70%; aspect-ratio: 1; animation-delay: 1.1s; }
.smoke-puff.p3 { width: 90%; aspect-ratio: 1; animation-delay: 2.2s; }

@keyframes smoke-rise {
  0%   { transform: translateX(-50%) translateY(0) scale(0.6); opacity: 0.8; }
  60%  { opacity: 0.4; }
  100% { transform: translateX(-50%) translateY(-230%) scale(1.6); opacity: 0; }
}

/* 小屋这里原本还有一枚常驻脉冲圈（.cottage-beacon / .beacon-pulse），
   按用户「这种提示外圈要去掉」的要求整块删掉 ——
   可点性由悬停提示条（.cottage-tooltip，写着「点击进入工作室」）表达，够了。
   更早还删过一个 🏠 emoji 图钉：插画里本来就画着房子，再钉一个像是贴歪的贴纸。 */

.cottage-tooltip {
  position: absolute;
  bottom: calc(100% + 8px);
  left: 50%;
  transform: translateX(-50%);
  background: rgba(28, 22, 17, 0.9);
  backdrop-filter: blur(8px);
  padding: 6px 14px;
  border-radius: var(--radius-full);
  display: flex;
  flex-direction: column;
  align-items: center;
  white-space: nowrap;
  box-shadow: 0 6px 20px rgba(0,0,0,0.25);
  border: 1px solid rgba(255, 255, 255, 0.18);
  pointer-events: none;
  animation: float-bob 3s ease-in-out infinite;
}

@keyframes float-bob {
  0%, 100% { transform: translateX(-50%) translateY(0); }
  50%      { transform: translateX(-50%) translateY(-6px); }
}

.tooltip-main {
  color: #fff;
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.05em;
}

.tooltip-sub {
  color: var(--color-butter-yellow);
  font-size: 0.65rem;
  opacity: 0.9;
}

/* Home Editorial Text Overlay */
.home-hero-text {
  position: absolute;
  top: 18%;
  left: 48%;
  max-width: 520px;
  z-index: 10;
  animation: fadeInDown 1s cubic-bezier(0.2, 0.8, 0.2, 1) forwards;
}

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

.hero-badge-group {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.45rem;
  margin-bottom: 0.8rem;
}

.hero-tag {
  font-size: 0.8rem;
  color: rgba(28, 22, 17, 0.85);
  font-weight: 500;
  letter-spacing: 0.05em;
}

.hero-divider {
  color: rgba(28, 22, 17, 0.35);
  font-size: 0.75rem;
}

.hero-headline {
  font-family: var(--font-serif);
  font-size: 3.4rem;
  font-weight: 700;
  line-height: 1.1;
  color: #1a1614;
  letter-spacing: -0.02em;
  margin-bottom: 0.75rem;
  text-shadow: 0 1px 2px rgba(255, 255, 255, 0.4);
}

.hero-domains {
  font-family: var(--font-sans);
  font-size: 1.15rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  color: #2b3947;
  margin-bottom: 0.85rem;
}

.hero-tagline {
  font-family: var(--font-serif);
  font-size: 1.05rem;
  color: #3e332c;
  line-height: 1.6;
  margin-bottom: 1.75rem;
  background: rgba(255, 255, 255, 0.4);
  backdrop-filter: blur(4px);
  display: inline-block;
  padding: 0.4rem 0.8rem;
  border-radius: var(--radius-sm);
  border-left: 3px solid var(--color-terracotta);
}

.hero-cta-group {
  display: flex;
  align-items: center;
  gap: 0.9rem;
}

.editorial-btn {
  border: none;
  cursor: pointer;
  padding: 0.75rem 1.4rem;
  border-radius: var(--radius-full);
  font-family: var(--font-sans);
  font-size: 0.85rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  transition: all 0.25s cubic-bezier(0.2, 0.8, 0.2, 1);
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.08);
}

.editorial-btn.primary {
  background-color: #1a1512;
  color: #fff;
}

.editorial-btn.primary:hover {
  background-color: var(--color-terracotta);
  transform: translateY(-2px);
  box-shadow: 0 6px 18px rgba(183, 72, 56, 0.3);
}

.editorial-btn.secondary {
  background: rgba(255, 255, 255, 0.8);
  backdrop-filter: blur(6px);
  color: #2a201a;
  border: 1px solid rgba(0, 0, 0, 0.08);
}

.editorial-btn.secondary:hover {
  background: #fff;
  transform: translateY(-2px);
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.12);
}

.editorial-btn.text-link {
  background: transparent;
  color: #3b2f27;
  box-shadow: none;
  padding: 0.5rem 0.8rem;
}

.editorial-btn.text-link:hover {
  color: var(--color-terracotta);
  text-decoration: underline;
}

/* 首页的「推镜头」。小屋热区锁在插画 x1085..1255 上，
   而 cover 定位在中等宽度会把右侧裁掉 —— 1024×768 下可见范围只到 x1200，
   小屋被切掉三分之一、连提示条都被切一半。
   这里把镜头往左推，刚好让插画的右边界推到 x1260。
   位移推导：layer 居中时可见范围 = [688 − W/(2K), 688 + W/(2K)]，
   K = max(100vw, 100vh×1376/768)/1376；要 688 + W/(2K) + s/K = 1260，
   解得 s = max(0, 572K − W/2) —— 也就是下面这条 calc。

   ★ 只在「横向视口」生效（min-aspect-ratio 5/6）：竖屏手机里可见的插画宽度
     只有 355 原图像素，要推到小屋就得把坐着的人整个推出画面 ——
     那反而把首页的主角弄丢了。竖屏就让镜头居中（人 + 猫），
     进工作室走首页那颗「探索作品」按钮或顶部导航。 */
@media (max-width: 1180px) and (min-aspect-ratio: 5/6) {
  .home-art-layer {
    transform: translate(-50%, -50%)
               translateX(calc(-1 * max(0px, 0.4157 * max(100vw, 100vh * 1376 / 768) - 50vw)));
  }
}

/* ================= SCENE 2: STUDIO (Attic Desk) =================

   本区块按「先定坐标系，再显示层，最后操作层」排列：

     §1 坐标系   .studio-art-layer（与 1376×768 插画 1:1）
     §2 显示层   .lamp-bulb-dim（关灯压暗）/ .lamp-light-cone（灯光锥）
     §3 操作层   .hotspot（6 个原生 <button>）

   ★ 改坐标前必读：下面每个百分比都是对插画做像素级实测得到的，不是目测的。
     实测方法：把插画按 2~3 倍放大、叠 20~50px 坐标网格直接读数，再叠回插画目视校验。
     缩略图会读错，颜色阈值扫描会漏掉偏暗的边缘 —— 这个坑在杯腔和邮筒上都踩过。 */

/* ---------- §1 坐标系 ---------- */

/* 与 assets/studio_bg.jpg (1376×768) 严格 1:1，几何等价于
   object-fit: cover; object-position: center（和咖啡场景 .coffee-art、
   联系场景 .contact-art-layer 是同一套）。
   内部叠加元素一律用「插画像素百分比」定位 —— 原先用的是「视口百分比 + px」，
   视口一变整片热区就漂走；笔记本热区甚至只盖住电脑左边一半，点电脑没反应。 */
.studio-art-layer {
  position: absolute;
  left: 50%;
  top: 50%;
  /* ★ 归标系锁在插画上以后，窄屏只看得到插画的中间一小块（cover 裁切）。
     390×844 下可见范围只有原图 x510..865，而笔记本是 x780..975，右边一半被裁掉。
     这里把镜头往左推，刚好把桌面核心区（台灯开关 x716..770 · 手记 x622..740 ·
     笔记本 x780..975）整块推进画面。
     推导：layer 居中时可见范围 = [688 − W/(2K), 688 + W/(2K)]，K = max(100vw, 100vh×1376/768)/1376；
     要可见右边界到 975 得 s = max(0, 287K − W/2) —— 就是下面那条 calc。
     宽屏时 max() 取 0，所以不需要媒体查询就能自动失效。 */
  transform: translate(-50%, -50%)
             translateX(calc(-1 * max(0px, 0.2086 * max(100vw, 100vh * 1376 / 768) - 50vw)));
  width: max(100%, calc(100vh * 1376 / 768));
  height: max(100%, calc(100vw * 768 / 1376));
  pointer-events: none;
}

/* ---------- §2 显示层 ---------- */

/* 灯光锥。锥顶严格对齐插画里灯罩的开口（原图 x726..818 / y316..340）——
   灯罩是朝右下开口的，所以光柱也往右下走，落在桌面偏右的一片区域上。
   盒子的范围就是这条四边形的包围盒：原图 x660..920 / y314..510。

   ★ 这就是用户报的「打开灯之后灯光是错位的」：上一版用 top/left 视口百分比
     加固定 px 尺寸，1440 宽下锥顶落在原图 y276（灯罩腰部），换个视口又漂到别处。
     现在整套坐标锁在插画上，任何视口都贴着灯罩开口。 */
.lamp-light-cone {
  position: absolute;
  left: 47.965%;               /* 660/1376 */
  top: 40.885%;                /* 314/768  */
  width: 18.895%;              /* 260/1376 */
  height: 25.521%;             /* 196/768  */
  background: radial-gradient(ellipse at 44% 6%,
    rgba(255, 236, 150, 0.42) 0%,
    rgba(255, 232, 140, 0.17) 46%,
    rgba(255, 230, 130, 0.05) 74%,
    transparent 92%);
  /* 四边形：顶边贴着灯罩开口（左端低、右端高 —— 灯罩本身是斜的），
     底边在桌面上摊开成一片光斑。 */
  clip-path: polygon(25.4% 13.3%, 60.8% 1.0%, 96.9% 99.0%, 3.1% 99.0%);
  pointer-events: none;
  z-index: 15;
  opacity: 1;
  transition: opacity 0.34s ease;
  mix-blend-mode: screen;
}

.lamp-light-cone.off {
  opacity: 0;
}

/* 关灯时把灯罩里那颗画好的亮灯泡压暗。
   插画里台灯本来就是「开着」的（灯罩内是亮奶油色的灯泡），
   只把光锥藏掉的话灯罩里还亮着一颗灯 —— 看着像坏了。
   这层是一枚带模糊的椭圆，正好盖住灯泡（实测原图 x744..796 / y313..341）。
   ★ 用 multiply 而不是直接盖一层深灰：直接盖会变成一颗「灰球」，
     而 multiply 是把奶油色的灯泡按暖褐色压下去，读起来才像「灯丝灭了」。
     所以渐变要淡到白色（multiply 下白色 = 不改变），不是淡到透明。 */
.lamp-bulb-dim {
  position: absolute;
  left: 53.924%;               /* 742/1376 */
  top: 40.495%;                /* 311/768  */
  width: 4.070%;               /* 56/1376  */
  height: 4.167%;              /* 32/768   */
  border-radius: 50%;
  background: radial-gradient(ellipse at 50% 46%,
    rgba(84, 70, 42, 0.96) 0%,
    rgba(96, 80, 50, 0.86) 54%,
    rgba(150, 132, 92, 0.45) 80%,
    rgba(255, 255, 255, 0) 100%);
  mix-blend-mode: multiply;
  filter: blur(1.4px);
  opacity: 0;
  pointer-events: none;
  z-index: 16;
  transition: opacity 0.34s ease;
}

.lamp-bulb-dim.on {           /* 关灯时才亮起这层 */
  opacity: 1;
}

/* ---------- §3 操作层：6 个可点部件 ---------- */

/* 按钮重置。这些按钮在插画上是「隐形热区」，视觉反馈交给子元素。
   用原生 <button> 而不是 div[role=button]：回车/空格激活与焦点顺序交给浏览器。 */
.hotspot {
  position: absolute;
  z-index: 20;
  margin: 0;
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  appearance: none;
  -webkit-appearance: none;
  cursor: pointer;
  border-radius: 10px;
  -webkit-tap-highlight-color: transparent;
  /* ★ 必须显式打开：父层 .studio-art-layer 是 pointer-events:none
     （它整层覆盖在插画上，不关掉就会挡住背景图上的其它交互），
     所以子按钮要把事件重新打开。漏了这句的话热区几何全对、就是点不动。 */
  pointer-events: auto;
}
.hotspot:focus-visible { outline: none; }

/* 台灯开关 —— 单独做在底座那颗旋钮上（实测旋钮中心原图 (743,441)）。
   命中区 54×42 原图像素，比旋钮本身大一圈，好点。
   ★ 整只台灯不再是点击目标：用户要求「增加一个按钮开关，功能在那个按钮开关上」。 */
.hotspot-lamp-switch {
  left: 52.035%;               /* 716/1376 */
  top: 54.297%;                /* 417/768  */
  width: 3.924%;               /* 54/1376  */
  height: 5.469%;              /* 42/768   */
}

/* 开关上的小圆环。★ 用户要求去掉「提示外圈」—— 所以它**不再常驻**，
   只在鼠标移到开关上（或键盘聚焦）时才出现，当作纯粹的 hover 反馈。
   静止状态的插画上不留任何圈。 */
.lamp-switch-ring {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 66%;
  height: 66%;
  transform: translate(-50%, -50%);
  border-radius: 50%;
  border: 1.5px solid rgba(255, 240, 200, 0);
  box-shadow: 0 0 0 rgba(255, 214, 130, 0);
  transition: border-color 0.22s ease, box-shadow 0.22s ease, background 0.22s ease;
}
.hotspot-lamp-switch:hover .lamp-switch-ring,
.hotspot-lamp-switch:focus-visible .lamp-switch-ring {
  border-color: rgba(255, 246, 220, 0.95);
  background: rgba(255, 236, 190, 0.18);
  box-shadow: 0 0 14px rgba(255, 205, 110, 0.62);
}

/* 黑胶唱片机（原图 x70..390 / y375..585） */
.hotspot-turntable {
  left: 5.087%;                /* 70/1376  */
  top: 48.828%;                /* 375/768  */
  width: 23.256%;              /* 320/1376 */
  height: 27.344%;             /* 210/768  */
}

/* 笔记本电脑（原图 x780..975 / y353..533）—— 整台电脑都是热区。
   ★ 用户要求「直接点击电脑就能触发那个功能」：上一版用视口百分比 + 190px 宽，
     只盖住电脑左边一半，点屏幕右半边完全没反应。
   右边界收到 975 是为了避开华夫饼盘（盘的左缘在 976），两块命中区严格不重叠。 */
.hotspot-laptop {
  left: 56.686%;               /* 780/1376 */
  top: 45.964%;                /* 353/768  */
  width: 14.171%;              /* 195/1376 */
  height: 23.438%;             /* 180/768  */
}

/* 产品思考手记（桌上的纸和笔，原图 x622..740 / y460..520） */
.hotspot-notebook {
  left: 45.203%;               /* 622/1376 */
  top: 59.896%;                /* 460/768  */
  width: 8.576%;               /* 118/1376 */
  height: 7.813%;              /* 60/768   */
}

/* 胶片相机（原图 x1014..1126 / y424..506） */
.hotspot-camera {
  left: 73.692%;               /* 1014/1376 */
  top: 55.208%;                /* 424/768   */
  width: 8.140%;               /* 112/1376  */
  height: 10.677%;             /* 82/768    */
}

/* 关于我：锚在转盘下面那个木柜上（柜子 = 装档案的地方）。
   原图 x130..380 / y600..748。
   ★ 原先它挂在右上角 top:39% right:5% 的空白墙面上 —— 那里插画里什么都没有，
     是纯死控件（悬停出「关于我」、点下去跳 about，但底下是面空墙）。 */
.hotspot-folder {
  left: 9.448%;                /* 130/1376 */
  top: 78.125%;                /* 600/768  */
  width: 18.169%;              /* 250/1376 */
  height: 19.271%;             /* 148/768  */
}

/* ================= SCENE 3: COFFEE =================
   一台可交互的意式咖啡机。本区块按「先定坐标系，再显示层，最后操作层」排列：

     §1 容器与坐标系  .coffee-art / .coffee-art-img
     §2 显示层·压力表  .gauge-ring-glow / .gauge-needle
     §3 显示层·状态灯  .brew-led
     §4 显示层·机身屏  .machine-screen / .ms-*
     §5 显示层·出液    .coffee-stream
     §6 显示层·杯中液体 .cup-liquid / .cup-liquid-body / .cup-liquid-surface
     §7 显示层·杯口蒸汽 .coffee-steam-cloud / .steam-wisp
     §8 显示层·蒸汽棒  .wand-steam / .wand-puff
     §9 操作层         .machine-ctl（8 个按钮，唯一交互入口）
     §10 几何代理      .machine-bounds（只给回归探针用）

   显示层与操作层的分组也体现在 HTML 的 .machine-visuals / .machine-controls 上。
   所有状态类都挂在 .coffee-art 根上（is-on / is-heating / is-ready / is-brewing /
   is-steaming / is-empty），由 app.js 的状态机统一写，CSS 只负责「状态 → 长相」。

   ★ 改坐标前必读：下面每个百分比都是对插画做像素级实测得到的，不是目测的。
     改之前先看该规则上的注释，那里写了实测方法和原始像素值。 */

/* ---------- §1 容器与坐标系 ---------- */

/* --- 插画坐标系 ---------------------------------------------------------
   .coffee-art 与 assets/coffee_bg.jpg (1376x768) 严格 1:1，几何完全等价于
   object-fit:cover; object-position:center：
     宽 = max(视口宽, 视口高 × 1376/768)
     高 = max(视口高, 视口宽 × 768/1376)
   于是内部所有叠加元素只要按「背景图像素百分比」定位，就能在任意视口下
   精确贴合插画里的咖啡机 —— 原来用「场景百分比」定位，视口一变就漂走。
   背景图里咖啡机的实测包围盒是 x 618..1048 / y 192..630。 */
.coffee-art {
  /* 插画的实际渲染宽度。叠加元素的所有尺寸都以它为基准：
     --u = 1% 插画宽（桌面 1440x900 下 ≈ 16px）。这样叠加物跟机身同步缩放。 */
  --artw: max(100vw, calc(100vh * 1376 / 768));
  --u: calc(var(--artw) * 0.01);
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  width: max(100%, calc(100vh * 1376 / 768));
  height: max(100%, calc(100vw * 768 / 1376));
  /* 喝咖啡时手和杯子会移向画面右缘；裁掉超出插画的部分，
     免得它们溢出到相邻场景上去（窄屏推镜头时尤其明显）。 */
  overflow: hidden;
}

.coffee-art-img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
}

/* 显示层与操作层的两个容器。
   两者都是 inset:0 的绝对定位盒子，尺寸与 .coffee-art 完全一致，
   所以子元素里那些「插画百分比」坐标的参照物没变，加这层分组不影响任何几何。
   z-index 刻意留 auto —— 不创建新的堆叠上下文，子元素之间的层级关系
   （显示层 4~7 / 操作层 9 / 几何代理 1）与不加容器时完全一样。 */
.machine-visuals,
.machine-controls {
  position: absolute;
  inset: 0;
}

/* 显示层整体不吃事件：它是纯状态可视化，点它等于点空气。
   交互一律交给 §9 的 8 个 .machine-ctl 按钮。 */
.machine-visuals {
  pointer-events: none;
}

/* 注：原先这里还有一层 .coffee-machine-layer（同一张背景图按机身轮廓裁剪，
   曾用来做机身抖动）。抖动方案被用户否掉后，该层在 HTML 里已整块删除，
   对应的 CSS（含量好的 clip-path 多边形）也一并清掉，不留死代码。
   机身的所有动态反馈现在都由真实部件承担：机身液晶屏 + 仪表指针 +
   三颗指示灯 + 杯中液面 + 蒸汽/出液。 */

/* ---------- §2 显示层·压力表 ---------- */

/* 表盘压力环光晕：亮度/厚度跟随真实压力（--bar 由 JS 写）。
   圆心取插画里金色表圈连通域的中心 (772,266)，外径 84px，光晕再放大到 88px。 */
.gauge-ring-glow {
  position: absolute;
  left: 52.907%;              /* (772 − 44)/1376 */
  top: 28.906%;               /* (266 − 44)/768  */
  width: 6.395%;              /* 直径 88/1376 */
  height: 11.458%;            /* 直径 88/768  */
  transform: translate(-50%, -50%);
  border-radius: 50%;
  opacity: 0;
  z-index: 4;
  pointer-events: none;
  transition: opacity 0.35s ease, box-shadow 0.25s linear;
}

.coffee-art.is-on .gauge-ring-glow {
  opacity: calc(0.25 + var(--press, 0) * 0.75);
  box-shadow: inset 0 0 0 calc(1px + var(--press, 0) * 1.5px) rgba(255, 198, 90, calc(0.25 + var(--press, 0) * 0.55)),
              0 0 calc(var(--press, 0) * 26px) rgba(255, 190, 80, calc(var(--press, 0) * 0.45));
}

/* 仪表指针：底端固定在表盘圆心（top 用 calc 把「高度」减掉，
   这样 transform-origin 50% 100% 正好落在圆心上）。
   圆心 (772,266)、指针长 40px 都与插画里画好的那根针一致，
   所以 0 bar（静止角 0°）时两者完全重合，不会出现「重影」。
   角度由 JS 通过 --needle-angle 驱动 —— 读的是真实压力值，不是固定循环动画。 */
.gauge-needle {
  position: absolute;
  left: 56.105%;                 /* 772/1376 */
  top: 29.427%;                  /* (266 − 40)/768 */
  width: 0.218%;                 /* 3/1376 */
  height: 5.208%;                /* 40/768 */
  margin-left: -0.109%;
  background: linear-gradient(180deg, #241811 0%, #7a5430 100%);
  border-radius: 40% 40% 1px 1px / 10% 10% 1px 1px;
  transform-origin: 50% 100%;
  transform: rotate(var(--needle-angle, 0deg));
  transition: transform 0.12s linear;
  opacity: 0;
  z-index: 5;
  pointer-events: none;
}

.coffee-art.is-on .gauge-needle {
  opacity: 1;
}

/* ---------- §3 显示层·状态灯 ---------- */

/* 机身左侧三颗按钮旁的状态指示灯
   位置来自对插画做金色连通域分析实测的小圆点：(714,264)/(714,296)/(714,328)，
   直径约 6px。上一版误放在 x=724，正好落在按钮与表盘之间的空白处，是错的。
   颜色编码跟真机一致：暗=关机、琥珀=预热、绿=就绪、蓝白=萃取中、红=缺水 */
.brew-led {
  position: absolute;
  width: 0.654%;               /* 直径 9/1376 */
  height: 1.172%;              /* 直径 9/768  */
  transform: translate(-50%, -50%);
  border-radius: 50%;
  background: rgba(120, 110, 100, 0.35);
  box-shadow: none;
  z-index: 5;
  pointer-events: none;
  transition: background 0.3s ease, box-shadow 0.3s ease;
}
.brew-led.led-1 { left: 51.890%; top: 34.375%; }   /* (714, 264) → 电源灯 */
.brew-led.led-2 { left: 51.890%; top: 38.542%; }   /* (714, 296) → 加热灯 */
.brew-led.led-3 { left: 51.890%; top: 42.708%; }   /* (714, 328) → 萃取灯 */

/* 开机待机：三颗灯全部亮起暗绿 */
.coffee-art.is-on .brew-led {
  background: rgba(126, 222, 140, 0.85);
  box-shadow: 0 0 8px rgba(110, 220, 130, 0.55);
}
/* 预热：琥珀色依次追逐 */
.coffee-art.is-heating .brew-led {
  background: rgba(255, 196, 80, 0.95);
  animation: led-chase 0.9s ease-in-out infinite;
}
.coffee-art.is-heating .brew-led.led-2 { animation-delay: 0.3s; }
.coffee-art.is-heating .brew-led.led-3 { animation-delay: 0.6s; }

/* 萃取中：三灯快闪 */
.coffee-art.is-brewing .brew-led {
  background: rgba(255, 244, 214, 0.98);
  animation: led-blink 0.5s ease-in-out infinite;
}
.coffee-art.is-brewing .brew-led.led-2 { animation-delay: 0.16s; }
.coffee-art.is-brewing .brew-led.led-3 { animation-delay: 0.32s; }

/* 缺水：三灯红色告警 */
.coffee-art.is-empty .brew-led {
  background: rgba(255, 96, 76, 0.95);
  animation: led-alarm 1s ease-in-out infinite;
}

@keyframes led-chase {
  0%, 100% { box-shadow: 0 0 0 rgba(255, 196, 80, 0);     opacity: 0.35; }
  50%      { box-shadow: 0 0 14px rgba(255, 196, 80, 0.95); opacity: 1; }
}

@keyframes led-alarm {
  0%, 100% { box-shadow: 0 0 4px rgba(255, 90, 70, 0.5); }
  50%      { box-shadow: 0 0 16px rgba(255, 90, 70, 1); }
}

@keyframes led-blink {
  0%, 100% { box-shadow: 0 0 0 rgba(255, 236, 190, 0);      opacity: 0.35; }
  50%      { box-shadow: 0 0 14px rgba(255, 236, 190, 0.95); opacity: 1; }
}

/* ---------- §4 显示层·机身屏 ---------- */

/* ===== 机身液晶屏 =====
   用户要求去掉原先挂在文案面板里的独立控制台，把屏幕做在咖啡机上。
   位置取自插画实测的机身右侧面板干净区。上一版放在 x900..1030：
     · 右缘 1030 正好压在机身轮廓线（1030）上，视觉上像挂在机身外面；
     · 更要命的是窄视口 —— 430×932 这类「窄而高」的视口下插画按高度放大到 1670px 宽，
       可见的插画横向范围只有 354 个原图像素，屏幕右缘会越出视口 6~7px。
   实测面板的干净矩形是 x893..1029 / y240..344，所以往里收：
   x902..1012 / y258..334，四边各留出 9 / 17 / 18 / 10 原图像素的内缩，
   既像真的嵌在面板里，也让「部件+屏幕」的横向跨度（x679..1012）收进窄视口可见范围。
   屏幕只负责「显示」，操作入口是插画上的 8 个 .machine-ctl 部件。 */
.machine-screen {
  /* 尺寸单位 --u 由 .coffee-art 提供（1u = 1% 插画宽） */
  position: absolute;
  left: 65.5523%;                /* 902/1376 */
  top: 33.5938%;                 /* 258/768  */
  width: 7.9942%;                /* 110/1376 */
  height: 9.8958%;               /* 76/768   */
  z-index: 5;
  pointer-events: none;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  padding: calc(var(--u) * 0.40) calc(var(--u) * 0.46);
  border-radius: calc(var(--u) * 0.22);
  background: linear-gradient(180deg, #262119 0%, #15120e 100%);
  border: 1px solid #4c4236;
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.05),
              inset 0 1px 7px rgba(0, 0, 0, 0.65),
              0 2px 7px rgba(30, 18, 8, 0.35);
  font-family: var(--font-mono);
  overflow: hidden;
}
/* 扫描线，让屏幕更像真机的 LCD */
.machine-screen::after {
  content: ""; position: absolute; inset: 0;
  background: repeating-linear-gradient(180deg,
    rgba(255, 255, 255, 0.045) 0 1px, transparent 1px 3px);
  pointer-events: none;
}
.ms-row { display: flex; align-items: center; position: relative; z-index: 1; }
.ms-top { gap: calc(var(--u) * 0.16); }
.ms-dot {
  width: calc(var(--u) * 0.30); height: calc(var(--u) * 0.30);
  border-radius: 50%; flex: none; background: #6b6257;
  transition: background 0.3s ease, box-shadow 0.3s ease;
}
.machine-screen.is-off     .ms-dot { background: #6b6257; }
.machine-screen.is-heating .ms-dot { background: #ffc450; box-shadow: 0 0 calc(var(--u) * 0.5) #ffc450; animation: led-blink 0.9s ease-in-out infinite; }
.machine-screen.is-ready   .ms-dot { background: #6ee08a; box-shadow: 0 0 calc(var(--u) * 0.5) #6ee08a; }
.machine-screen.is-busy    .ms-dot { background: #9fd8ff; box-shadow: 0 0 calc(var(--u) * 0.5) #9fd8ff; animation: led-blink 0.5s ease-in-out infinite; }
.machine-screen.is-empty   .ms-dot { background: #ff604c; box-shadow: 0 0 calc(var(--u) * 0.5) #ff604c; animation: led-alarm 1s ease-in-out infinite; }

.ms-status {
  font-size: calc(var(--u) * 0.44); font-weight: 700; letter-spacing: 0.04em;
  color: #ffe9c2; white-space: nowrap;
}
.machine-screen.is-off .ms-status { color: #7d7469; }
.ms-metrics { gap: calc(var(--u) * 0.30); align-items: baseline; }
.ms-metric {
  display: inline-flex; align-items: baseline; gap: calc(var(--u) * 0.06);
  font-size: calc(var(--u) * 0.34); color: #8a8073;
}
.ms-metric b {
  font-size: calc(var(--u) * 0.62); font-weight: 700; color: #ffd894;
  font-variant-numeric: tabular-nums; line-height: 1;
}
.ms-metric i { font-style: normal; font-size: calc(var(--u) * 0.30); }
.ms-foot {
  display: flex; align-items: center; gap: calc(var(--u) * 0.20);
  position: relative; z-index: 1;
}
.ms-mode {
  font-size: calc(var(--u) * 0.34); letter-spacing: 0.06em;
  color: #8fb8a0; white-space: nowrap;
}
.ms-water {
  margin-left: auto; flex: 0 1 auto; width: 42%;
  height: calc(var(--u) * 0.20); border-radius: calc(var(--u) * 0.1);
  background: #2c2620; overflow: hidden;
}
.ms-water b {
  display: block; height: 100%; width: 100%;
  background: linear-gradient(90deg, #4fb0e8, #7fe0ff);
  border-radius: inherit;
  transition: width 0.4s ease, background 0.4s ease;
}
.machine-screen.is-empty .ms-water b { background: linear-gradient(90deg, #c0392b, #ff604c); }

/* ---------- §5 显示层·出液 ---------- */

/* 冲煮头出液：一根 3px 宽的细流，从出液口 (809,442) 落到杯口内沿 (508)。
   「落点必须在杯口内沿之上」是硬约束 —— 否则会看到水流悬空、液面另起一块。 */
.coffee-stream {
  position: absolute;
  left: 58.7936%;              /* 809/1376 —— 冲煮头出液口实测 (788..830) 的中心 */
  top: 57.5521%;               /* 442/768 —— 出液口底部，紧贴冲煮头下沿 */
  width: 0.218%;
  height: 0;
  transform: translateX(-50%);
  background: linear-gradient(180deg, #3d2213 0%, #6b3d22 100%);
  border-radius: 0 0 2px 2px;
  opacity: 0;
  z-index: 6;
  pointer-events: none;
  transition: height 0.28s ease, opacity 0.28s ease;
}

.coffee-stream.dripping {
  height: 8.5938%;             /* 66/768 —— 从 442 落到 508，正好进杯口内沿，避免「水流悬空、液面另起一块」 */
  opacity: 0.92;
  animation: stream-wiggle 0.28s infinite ease-in-out;
}

@keyframes stream-wiggle {
  0%, 100% { transform: translateX(-50%) scaleX(1); }
  50%      { transform: translateX(-50%) scaleX(1.35); }
}

/* ---------- §6 显示层·杯中液体 ---------- */

/* ===== 杯中液体 =====
   用户两次反馈都出在这里：「杯子都脱模了」「接满咖啡的样子不对」。前后修正了五轮，
   教训是 —— **杯腔边界只能程序化实测 + 把轮廓叠回插画上目视校验，两样缺一不可**。

   五轮估值：x759..851 → x764..850 → x773..846 → x776..856（都错）→ **x775..845（对）**。
   最后一版之所以越修越偏，是因为我把**杯壁的高光带**误读成了「杯口白圈的内沿」：
   逐行找 lum≥247 的亮段时，杯子左壁那条 16px 宽的亮带和右壁那条 5px 宽的亮带
   都会被当成白圈，于是「白圈之内」被算成了整只杯子（含壁），杯腔被撑到 856 ——
   右壁只剩 4px，液体自然盖住杯壁、看着像溢出。
   叠图一看就清楚了：**杯口白圈其实就是杯壁的顶面**，左右壁各约 18px 厚，
   所以杯腔是 775..845（宽 70），不是一个 80 宽的大口。

   定案数据（原图 1376×768）：
     · 杯口内沿椭圆：中心 (810, 496)，半轴 (35, 10.85) → x775..845 / y485.2..506.9
     · 杯内底椭圆　：中心 (810, 551)，半轴 (22, 6.5)   → x788..832 / y544.5..557.5
     · 两枚椭圆扁率都取 0.31（≈ 该视角下水平圆的透视压缩比 sin18°），必须一致，
       否则液面在升降过程中会「换个角度看杯子」。
     · 侧壁：上段近垂直（y500~515 都停在 775/845），下段才收口。
   容器 = 杯腔包围盒，clip-path 复刻「顶弧 + 两侧 + 底弧」；液体与液面共用这套裁剪。 */
.cup-liquid {
  position: absolute;
  left: 56.3227%;              /* 775/1376 */
  top: 63.1510%;               /* 485/768  */
  width: 5.0872%;              /* 70/1376  */
  height: 9.6354%;             /* 74/768   */
  overflow: hidden;
  /* ★ 杯里没咖啡时整层隐形。这条必须挂在容器上，不能只挂在液面上 ——
     液柱的高度公式里有一个「基础高度」（补偿液面椭圆在杯底的那半截），
     fill=0 时液柱仍有十几 px 高，只隐藏液面的话杯底会留下一块
     「没接咖啡也有咖啡」的褐色。用户就是这么发现它的。
     系数取 30 而不是 14：让它在 fill≈3% 就完全不透明，
     否则萃取刚开始那半秒里咖啡会淡得看不见。 */
  opacity: calc(var(--fill, 0) * 30);
  transition: opacity 0.3s ease;
  /* 18 顶点闭合轮廓：顶弧 7 点（杯口内沿后半椭圆）→ 右壁 4 点 → 底弧 5 点 → 左壁 4 点。
     顶点够密，杯壁与杯底的转折处才不会露出硬棱。 */
  clip-path: polygon(
    0% 14.86%,
    9.55% 6.52%,
    34.55% 1.36%,
    50% 0.67%,
    65.45% 1.36%,
    90.45% 6.52%,
    100% 14.86%,
    100% 40.50%,
    97.10% 60.80%,
    88.60% 81.10%,
    81.43% 89.19%,
    72.22% 95.40%,
    50% 97.97%,
    27.78% 95.40%,
    18.57% 89.19%,
    17.10% 81.10%,
    7.10% 60.80%,
    1.40% 40.50%
  );
  z-index: 6;
  pointer-events: none;
}

/* 液柱。液位由 JS 写 --fill（0~1）；颜色也走变量：浓缩偏深、美式偏浅、拿铁带奶色。

   ★ 这里**不做**按液位的横向收窄，而是铺满整个容器宽度，让容器的 clip-path 统一裁形。
     原因：液面的宽度必须等于「该液位下杯腔的宽度」，而杯腔的侧壁是圆的（中段偏外鼓），
     用一条从 22px 到 35px 的线性公式去逼近它，中段会差 6px ——
     结果就是液体两侧各露出一条杯壁的缝，看着像液体没贴着杯子。
     交给 clip-path 之后，任何液位都严丝合缝：clip 里写的就是杯腔的真实轮廓。

     高度：液面椭圆的中心要落在真实液位上，所以从容器底算起的高度
     = 杯内底半高 + fill ×（杯口半高 − 杯内底半高），实测拟合 19.73% + fill × 79.59%
     （容器高 74px）。顶面弧的竖直半径随液位收窄（6.6 → 10.5px，即 0.480u → 0.763u）——
     它等于液面椭圆的半高，而椭圆半高 = 扁率 × 半宽，半宽跟着杯腔收，半高就必须跟着收；
     不收的话低位液面会鼓成一个半球，杯子看着像装了半杯布丁。 */
.cup-liquid-body {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: calc(19.73% + var(--fill, 0) * 79.59%);
  /* 两层叠加：底下是纵向的咖啡色阶，上面压一层横向明暗。
     横向这层很关键 —— 插画的光从左上来，杯身左亮右暗；
     液体如果只有纵向渐变，就会跟杯身的受光方向打架，看着像「贴在杯口的另一块东西」。
     跟着杯身一起左亮右暗，才读成「盛在杯子里的液体」。 */
  background:
    linear-gradient(90deg,
      rgba(255, 250, 238, 0.13) 0%,
      rgba(255, 250, 238, 0.02) 30%,
      rgba(0, 0, 0, 0.10) 64%,
      rgba(0, 0, 0, 0.24) 100%),
    linear-gradient(180deg,
      var(--coffee-hi, #6d4023) 0%,
      var(--coffee-mid, #523016) 46%,
      var(--coffee-lo, #331b0d) 100%);
  /* 顶面做成上凸的椭圆弧 = 液面椭圆的后半。
     竖直半径随液位收窄：6.6px（杯底半高）→ 10.5px（杯口半高），即 0.480u → 0.763u。
     写成「长度×数 + 长度×数×变量」的形式，避免嵌套括号在旧内核上失效。 */
  border-radius: 50% 50% 0 0 /
    calc(var(--u) * 0.480 + var(--u) * 0.283 * var(--fill, 0))
    calc(var(--u) * 0.480 + var(--u) * 0.283 * var(--fill, 0))
    0 0;
  transition: height 0.95s cubic-bezier(0.4, 0, 0.2, 1),
              border-radius 0.95s cubic-bezier(0.4, 0, 0.2, 1),
              background 0.5s ease;
}

/* 液面：一枚椭圆，中心正好落在当前液位上（fill=0 时落在杯内底中心 (810,551)，
   fill=1 时落在杯口内沿中心 (810,496)）。
   和液柱一样铺满容器宽度：椭圆的左右两端会被 clip 切在杯壁上 ——
   这正是「液体贴着杯壁」在投影里该有的样子（不是收成一个尖角）。
   半高随液位收窄（13.2 → 21px），扁率恒为 0.30，与杯口内沿一致，
   所以任何液位下液面都跟杯口同一「视角」。
   另外加了一圈内阴影当「弯月面」，这是让画面读成「液体」而不是「固体」的关键一笔。 */
.cup-liquid-surface {
  position: absolute;
  left: 0;
  right: 0;
  height: calc(17.84% + var(--fill, 0) * 10.54%);   /* 椭圆的整个高度 13.2 → 21px */
  bottom: calc(1.89% + var(--fill, 0) * 69.05%);
  border-radius: 50%;
  background:
    linear-gradient(90deg,
      rgba(255, 250, 238, 0.10) 0%,
      rgba(0, 0, 0, 0) 42%,
      rgba(0, 0, 0, 0.18) 100%),
    radial-gradient(ellipse at 46% 32%,
      var(--surface-hi, #9c6236) 0%,
      var(--surface-lo, #633a1c) 72%,
      var(--surface-lo, #633a1c) 100%);
  box-shadow: inset 0 0 0 1px rgba(46, 26, 12, 0.28),
              inset 0 calc(var(--u) * 0.10) calc(var(--u) * 0.22) rgba(255, 240, 214, 0.10);
  opacity: calc(var(--fill, 0) * 14);
  transition: bottom 0.95s cubic-bezier(0.4, 0, 0.2, 1),
              left 0.95s cubic-bezier(0.4, 0, 0.2, 1),
              right 0.95s cubic-bezier(0.4, 0, 0.2, 1),
              height 0.95s cubic-bezier(0.4, 0, 0.2, 1),
              opacity 0.3s ease;
}

/* 液面高光：一枚偏心的小亮斑，偏左上（光源在左上方）。
   这是让画面读成「液体」而不是「实心件」的关键一笔 —— 固体的顶面不会有这种
   小面积、高对比的镜面反射。放在左上而不是正中，才不像一盏灯打在球上。 */
.cup-liquid-surface::after {
  content: "";
  position: absolute;
  left: 21%;
  top: 20%;
  width: 25%;
  height: 30%;
  border-radius: 50%;
  background: radial-gradient(ellipse at 42% 44%,
    rgba(255, 246, 226, 0.62) 0%,
    rgba(255, 240, 210, 0.22) 46%,
    rgba(255, 240, 210, 0) 76%);
  pointer-events: none;
}

/* 杯底投影：液体与杯内底接触处压一层暗，模拟杯底在内壁上打的回影。
   不加这一笔，液体底部会是一条干净的直边，读起来像「放在杯子里的一块东西」。
   用 ::after 铺在容器内并跟随 clip-path，所以只会出现在杯腔轮廓之内。 */
.cup-liquid::after {
  content: "";
  position: absolute;
  inset: 0;
  background: radial-gradient(ellipse 76% 40% at 50% 97%,
    rgba(20, 10, 4, 0.42) 0%,
    rgba(20, 10, 4, 0.16) 52%,
    rgba(20, 10, 4, 0) 78%);
  pointer-events: none;
}

/* 萃取中液面是按 80ms 一跳连续爬升的，0.95s 的缓动永远追不上目标值，
   会看到液面「黏在后面」。萃取期间把过渡压到 0.12s linear，让它跟手。 */
.coffee-art.is-brewing .cup-liquid-body,
.coffee-art.is-brewing .cup-liquid-surface {
  transition-duration: 0.12s;
  transition-timing-function: linear;
}

/* ---------- §7 显示层·杯口蒸汽 ---------- */

/* 杯口蒸汽：从杯口 (780..850, 395..480) 升起并向右飘散 */
.coffee-steam-cloud {
  position: absolute;
  left: 55.814%;               /* 768/1376 —— 宽 80 的云盒中心落在杯口中心 (808) 上 */
  top: 52.083%;                /* 400/768  */
  width: 5.814%;               /* 80/1376  */
  height: 12.370%;             /* 95/768   */
  opacity: 0;
  z-index: 7;
  pointer-events: none;
  transition: opacity 0.45s ease;
}

.coffee-steam-cloud.steaming {
  opacity: 1;
}

.steam-wisp {
  position: absolute;
  bottom: 4%;
  border-radius: 50%;
  /* 背景是奶油色机身（实测 mean RGB 182,169,149，最亮处已接近纯白），
     纯白蒸汽压上去亮度只提升 ~5，肉眼基本看不见。所以内核保留白、
     外圈加一层暖灰压边，蒸汽才有「形」。 */
  background: radial-gradient(circle,
      rgba(255, 255, 255, 0.96) 0%,
      rgba(255, 253, 248, 0.80) 30%,
      rgba(206, 190, 168, 0.34) 58%,
      rgba(178, 162, 140, 0) 80%);
  filter: blur(1.4px);
  animation: steam-rise 2.6s ease-out infinite;
}

.steam-wisp.w1 { width: 52%; height: 24%; left: 4%;  animation-delay: 0s; }
.steam-wisp.w2 { width: 62%; height: 28%; left: 22%; animation-delay: 0.65s; }
.steam-wisp.w3 { width: 44%; height: 21%; left: 40%; animation-delay: 1.3s; }
.steam-wisp.w4 { width: 56%; height: 26%; left: 12%; animation-delay: 1.95s; }

@keyframes steam-rise {
  0%   { transform: translate3d(0, 0, 0) scale(0.5); opacity: 0; }
  16%  { opacity: 0.92; }
  70%  { opacity: 0.55; }
  100% { transform: translate3d(46%, -340%, 0) scale(1.55); opacity: 0; }
}

/* ---------- §8 显示层·蒸汽棒 ---------- */

/* ===== 蒸汽棒出汽 =====
   棒口在原图 (937,489)，汽往下偏左喷散。 */
.wand-steam {
  position: absolute;
  left: 63.808%;               /* 878/1376 —— 盒内 right:34% 处正好是棒口 (920,461) */
  top: 58.854%;                /* 452/768  */
  width: 4.651%;               /* 64/1376  */
  height: 18.229%;             /* 140/768  */
  opacity: 0;
  z-index: 7;
  pointer-events: none;
  transition: opacity 0.3s ease;
}

.coffee-art.is-steaming .wand-steam {
  opacity: 1;
}

.wand-puff {
  position: absolute;
  right: 34%;
  top: 4%;
  border-radius: 50%;
  background: radial-gradient(circle,
      rgba(255, 255, 255, 0.95) 0%,
      rgba(255, 253, 248, 0.72) 32%,
      rgba(206, 190, 168, 0.30) 60%,
      rgba(178, 162, 140, 0) 82%);
  filter: blur(1.6px);
  animation: wand-puff-out 1.9s ease-out infinite;
}

.wand-puff.p1 { width: 30%; height: 17%; animation-delay: 0s; }
.wand-puff.p2 { width: 38%; height: 21%; animation-delay: 0.42s; }
.wand-puff.p3 { width: 26%; height: 15%; animation-delay: 0.88s; }
.wand-puff.p4 { width: 34%; height: 19%; animation-delay: 1.32s; }

/* 蒸汽棒朝右下方，所以汽往右下散（原来是往左下，方向是反的） */
@keyframes wand-puff-out {
  0%   { transform: translate3d(0, 0, 0) scale(0.45); opacity: 0; }
  18%  { opacity: 0.95; }
  62%  { opacity: 0.5; }
  100% { transform: translate3d(55%, 200%, 0) scale(1.9); opacity: 0; }
}

/* ---------- §9 操作层：8 个可点部件 ---------- */

/* ===== 插画上的可点部件 =====
   8 个原生 <button>，是这一屏唯一的交互入口。
   坐标全部来自对背景图做颜色连通域分析得到的真实包围盒（原图 1376x768）。
   命中区之间刻意不重叠 —— 左侧三颗旋钮中心只隔 31px，所以每块取 30px。

   下面是按钮重置：这些按钮在插画上是「隐形的热区」，视觉反馈全交给
   .ctl-ring / .ctl-tip 两个子元素，所以要把浏览器默认的按钮外观全部清掉。
   用 <button> 而不是 div[role=button]，回车/空格激活与焦点顺序都由浏览器负责。 */
.machine-ctl {
  position: absolute;
  z-index: 9;
  margin: 0;
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  appearance: none;
  -webkit-appearance: none;
  cursor: pointer;
  border-radius: 8px;
  -webkit-tap-highlight-color: transparent;
}
.machine-ctl .ctl-ring {
  position: absolute; inset: 0;
  border-radius: 50%;
  border: 1.5px solid rgba(255, 236, 190, 0);
  box-shadow: 0 0 0 rgba(255, 210, 130, 0);
  transition: border-color 0.2s ease, box-shadow 0.2s ease, background 0.2s ease;
}
.machine-ctl.ctl-mode .ctl-ring,
.machine-ctl.ctl-group .ctl-ring,
.machine-ctl.ctl-tray .ctl-ring,
.machine-ctl.ctl-cup .ctl-ring { border-radius: 10px; }
.machine-ctl:hover .ctl-ring,
.machine-ctl:focus-visible .ctl-ring {
  border-color: rgba(255, 236, 190, 0.9);
  box-shadow: 0 0 14px rgba(255, 200, 110, 0.55);
  background: rgba(255, 236, 190, 0.12);
}
.machine-ctl:focus-visible { outline: none; }
.machine-ctl.is-pressed .ctl-ring {
  background: rgba(255, 236, 190, 0.3);
  border-color: #fff;
}
/* 悬浮标签 */
.ctl-tip {
  position: absolute;
  left: 50%;
  bottom: calc(100% + 5px);
  transform: translateX(-50%) translateY(3px);
  padding: 0.16rem 0.44rem;
  border-radius: 999px;
  background: rgba(26, 18, 14, 0.9);
  color: #ffe9c2;
  font-size: 0.58rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  white-space: nowrap;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.18s ease, transform 0.18s ease;
  z-index: 2;
}
.ctl-tip b { color: #ffd08a; }
.machine-ctl:hover .ctl-tip,
.machine-ctl:focus-visible .ctl-tip { opacity: 1; transform: translateX(-50%) translateY(0); }
/* 关机时电源旋钮脉冲提示 */
.machine-ctl.ctl-power.is-hint .ctl-ring {
  border-color: rgba(255, 236, 190, 0.85);
  animation: ctl-pulse 1.6s ease-in-out infinite;
}
@keyframes ctl-pulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(255, 210, 130, 0.55); }
  70%      { box-shadow: 0 0 0 12px rgba(255, 210, 130, 0); }
}

/* 各部件几何（原图像素 → .coffee-art 百分比）。
   全部来自对插画做颜色连通域分析得到的真实包围盒，不是肉眼估的：
     三颗按钮 (694,264)/(692,295)/(694,327) 金色连通域 d≈24 → 取 30px 命中区
     功能旋钮 金色连通域 (820,290)-(874,344) 中心 (847,317) → 取 68px 命中区
     冲煮头   机身深色块 (750,365)-(880,450)
     蒸汽棒   (888,342)-(952,470)，阀钮 (929,414)
     杯子     杯腔 (776,487)-(856,567)，命中区外扩到杯体轮廓
     滴水盘   (658,574)-(990,622)

   命中区刻意两两不重叠 —— 后出现的元素在同 z-index 下赢命中测试，
   所以先按「谁的包围盒更小更精确」排序，再检查重叠：
     ctl-power/grind/volume 左侧三旋钮，中心只隔 31px，各取 30px 不越界；
     ctl-tray 上沿从 548 下移到 574（杯子底沿），避开 ctl-cup 的下半段，
     否则点杯子底部会被判成「加水」。 */
/* ===== 电源键（重新设计）=====
   用户反馈「电源键太小了」。原来它和旁边两颗旋钮一样只是 30×30 的隐形热区，
   既不好点、也看不出这是个电源。两处放大：

     ① 命中区 30×30 → 95×47（约 5 倍面积）。只能往左、往上扩：
        往下会撞 ctl-grind（它的顶边正好在 y279），往右会撞 ctl-mode（从 x813 起），
        往右下则是冲煮头。扩完后与相邻部件仍然零重叠。
     ② 视觉上给一个**常驻可见**的圆环 + 电源符号，套在插画里那颗金色按钮外圈
        （按钮中心 (693,264)，符号环外径 38px）。别的部件都是 hover 才显环，
        只有电源键常显 —— 它是整台机器唯一的入口，找不到它这一屏就没法用。 */
.ctl-power { left: 47.456%; top: 30.208%; width: 6.905%; height: 6.120%; }  /* (653,232)-(748,279) */

/* 圆环重定位：不铺满（盒子已经比按钮大得多），而是精确套在金色按钮上。
   盒内按钮中心 = ((693−653)/95, (264−232)/47) = (42.105%, 68.085%)。 */
.ctl-power .ctl-ring {
  inset: auto;
  left: 42.105%;
  top: 68.085%;
  width: calc(var(--u) * 2.762);      /* 38px */
  height: calc(var(--u) * 2.762);
  transform: translate(-50%, -50%);
  border-radius: 50%;
  /* 描边比别的部件粗一档：这条环是「常驻可见」的，1.5px 在浅色机身上几乎看不见 */
  border-width: calc(var(--u) * 0.17);
  border-color: rgba(255, 236, 190, 0.78);
  background: radial-gradient(circle at 38% 32%,
    rgba(255, 244, 214, 0.26), rgba(255, 220, 150, 0.08) 62%, rgba(255, 210, 130, 0) 78%);
  box-shadow: 0 0 10px rgba(255, 200, 110, 0.30),
              inset 0 0 6px rgba(255, 226, 170, 0.22);
}
/* 电源符号 ⏻：开口朝上的圆弧 + 中间一条竖线（两个伪元素拼出来，不依赖字体里有没有这个码位） */
.ctl-power::before {
  content: "";
  position: absolute;
  left: 42.105%; top: 68.085%;
  width: calc(var(--u) * 1.05);
  height: calc(var(--u) * 1.05);
  transform: translate(-50%, -50%);
  border: calc(var(--u) * 0.155) solid rgba(96, 66, 32, 0.62);
  border-top-color: transparent;
  border-radius: 50%;
}
.ctl-power::after {
  content: "";
  position: absolute;
  left: 42.105%; top: 68.085%;
  width: calc(var(--u) * 0.155);
  height: calc(var(--u) * 0.72);
  transform: translate(-50%, -84%);
  border-radius: 999px;
  background: rgba(96, 66, 32, 0.62);
}
.machine-ctl.ctl-power:hover .ctl-ring,
.machine-ctl.ctl-power:focus-visible .ctl-ring {
  border-color: rgba(255, 244, 214, 0.95);
  background: radial-gradient(circle at 38% 32%,
    rgba(255, 244, 214, 0.44), rgba(255, 220, 150, 0.15) 62%, rgba(255, 210, 130, 0) 78%);
  box-shadow: 0 0 18px rgba(255, 200, 110, 0.72),
              inset 0 0 10px rgba(255, 226, 170, 0.40);
}
/* 开机：环与符号一起转成绿——一眼看出机器是不是开着 */
.machine-ctl.ctl-power.is-on .ctl-ring {
  border-color: rgba(150, 255, 196, 0.92);
  background: radial-gradient(circle at 38% 32%,
    rgba(206, 255, 226, 0.34), rgba(110, 224, 138, 0.13) 62%, rgba(110, 224, 138, 0) 78%);
  box-shadow: 0 0 16px rgba(110, 224, 138, 0.62),
              inset 0 0 8px rgba(160, 255, 200, 0.35);
}
.machine-ctl.ctl-power.is-on::before { border-color: rgba(28, 104, 60, 0.92); border-top-color: transparent; }
.machine-ctl.ctl-power.is-on::after  { background: rgba(28, 104, 60, 0.92); }
/* 悬浮标签也跟着按钮走，而不是跟着那个放大的盒子居中 */
.machine-ctl.ctl-power .ctl-tip {
  left: 42.105%;
  bottom: calc(31.915% + var(--u) * 2.4);
}

.ctl-grind  { left: 49.273%; top: 36.458%; width: 2.180%; height: 3.906%; }  /* c(693,295) */
.ctl-volume { left: 49.273%; top: 40.625%; width: 2.180%; height: 3.906%; }  /* c(693,327) */
.ctl-mode   { left: 59.084%; top: 36.849%; width: 4.942%; height: 8.854%; }  /* c(847,317) d68 */
.ctl-group  { left: 54.506%; top: 47.526%; width: 9.448%; height: 11.068%; } /* (750,365)-(880,450) */
.ctl-wand   { left: 64.535%; top: 44.531%; width: 4.651%; height: 16.667%; } /* (888,342)-(952,470) */
.ctl-cup    { left: 54.795%; top: 61.719%; width: 10.030%; height: 13.021%; }/* (754,474)-(892,574) */
.ctl-tray   { left: 47.820%; top: 74.740%; width: 24.128%; height: 6.250%; } /* (658,574)-(990,622) */

/* 未开机就按插画部件：和 LCD 按键一样抖一下 */
.machine-ctl.is-nudge { animation: ctl-nudge 0.4s ease; }
/* 未满足前置条件时抖一下 —— 比默默无反应清楚得多 */
@keyframes ctl-nudge {
  0%, 100% { transform: translateX(0); }
  25% { transform: translateX(-3px); }
  75% { transform: translateX(3px); }
}

/* ---------- §10 几何代理 ---------- */

/* 机身几何标记：只给布局回归探针当「插画物体」的代理，不响应点击 */
.machine-bounds {
  position: absolute;
  top: 25.000%;
  left: 44.912%;
  width: 31.250%;
  height: 57.031%;
  pointer-events: none;
  z-index: 1;
}

/* 咖啡机交互热区已拆成 .machine-ctl 的 8 个独立部件（见上方 SCENE 3 区块），
   整机包围盒只保留为不响应点击的几何标记 .machine-bounds，
   供布局回归探针当「插画物体」的代理使用。 */

/* ---------- 档案袋纸片：点开放大 ---------- */

/* 档案袋里每张纸片都能点开看，交互与「胶片记录的光阴」一致。
   ★ 这里只加「可点」的视觉反馈，纸片本身的造型（旋转角度、绝对定位）一概不动 ——
     那些是「散落在档案袋里」的构图，点开之后才需要还原成正常排版。 */
#folder-opened-view .paper-element.is-zoomable {
  cursor: pointer;
  transition: transform 0.22s cubic-bezier(0.2, 0.8, 0.2, 1),
              box-shadow 0.22s ease,
              filter 0.22s ease;
}
#folder-opened-view .paper-element.is-zoomable:hover {
  transform: translateY(-4px) scale(1.025) rotate(0deg);
  box-shadow: 0 16px 30px rgba(58, 38, 20, 0.28);
  filter: brightness(1.03);
  z-index: 5;
}
#folder-opened-view .paper-element.is-zoomable:focus-visible {
  outline: 2px solid var(--color-terracotta);
  outline-offset: 3px;
}
/* 右上角一个小角标，暗示「可以点开」 */
#folder-opened-view .paper-element.is-zoomable::after {
  content: "⤢";
  position: absolute;
  top: 6px;
  right: 8px;
  font-size: 0.7rem;
  line-height: 1;
  color: rgba(90, 62, 34, 0.42);
  pointer-events: none;
  transition: color 0.2s ease, transform 0.2s ease;
}
#folder-opened-view .paper-element.is-zoomable:hover::after {
  color: var(--color-terracotta);
  transform: scale(1.18);
}

/* 放大视图。被点的纸片是整块克隆进来的，所以内容永远和档案袋里一致；
   但它的定位是「散落在档案袋页面上」的绝对定位 + 旋转，进到这里必须全部还原：
   还原成正常流之后，它才会按弹窗宽度重新排版、把内容摊开。 */
.dossier-zoom-dialog {
  /* 收窄到 640：纸片在档案袋里是竖着的一张纸，弹窗太宽它会摊成一条，
     「放大」的感觉反而没了。 */
  max-width: 640px;
  width: min(92vw, 640px);
}
.dossier-zoom-body {
  max-height: 66vh;
  overflow-y: auto;
  padding: 0.6rem 1rem 1.5rem;
  scrollbar-width: thin;
  /* 内容少的纸片（邮筒徽章、行李牌）垂直居中，不然会贴着弹窗顶部显得空 */
  display: flex;
  flex-direction: column;
  justify-content: center;
}
.dossier-zoom-body .paper-element {
  position: static !important;
  inset: auto !important;
  width: 100% !important;
  max-width: none !important;
  height: auto !important;
  margin: 0 !important;
  transform: none !important;
  box-shadow: 0 10px 26px rgba(58, 38, 20, 0.16) !important;
  /* zoom 会连同布局空间一起放大（不像 transform: scale 只放大画面），
     所以弹窗高度会跟着内容走，不会出现「放大后溢出、容器还是原高」。 */
  zoom: 1.34;
  /* 给个下限高度：纸片原本是固定高度的「一张纸」，还原成正常流之后
     高度只剩内容那么点，小纸片会缩成一条。 */
  min-height: 230px;
}
.dossier-zoom-body .paper-element::after { content: none !important; }

/* ================= SCENE 4: ABOUT (Kraft Dossier) ================= */

/* ---------- 档案袋里的双语排版 ----------
   主要读者是中文用户，所以中文是正文、英文退成「注释」：
   小一号、浅一点，挂在中文后面。这样既保留了档案袋的复古英文质感，
   又不会让不懂英文的人读不懂内容。
   .en       行内（跟在中文后面，太长时自然换行）
   .en-block 另起一行（给大标题用，避免中英挤在同一行折成三行） */
.en {
  font-size: 0.76em;
  opacity: 0.7;
  letter-spacing: 0.05em;
  font-weight: 500;
}
.en-block {
  display: block;
  margin-top: 0.12em;
  font-size: 0.62em;
  opacity: 0.66;
  letter-spacing: 0.08em;
}
/* 卡片顶部的「小标签」（CAPABILITY TICKET 之类）里中英并排会很挤，同样让英文另起一行 */
.ticket-badge .en,
.receipt-title .en,
.info-table-title .en,
.exp-card-header .en { display: block; margin-top: 0.15em; font-size: 0.78em; }

/* 3D Dossier Stage */
.dossier-stage {
  position: absolute;
  top: 53%;
  left: 50%;
  transform: translate(-50%, -50%);
  perspective: 1600px;
  z-index: 20;
}

.dossier-wrapper {
  position: relative;
  transition: transform 0.6s cubic-bezier(0.2, 0.8, 0.2, 1);
}

/* CLOSED FOLDER VIEW */
.folder-cover-closed {
  position: relative;
  width: 420px;
  height: 560px;
  background: #cbb494;
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-kraft);
  border: 1px solid #a89173;
  display: none;
  cursor: pointer;
  transition: transform 0.3s ease, box-shadow 0.3s ease;
}

.dossier-wrapper:not(.open) .folder-cover-closed {
  display: block;
}

.folder-cover-closed:hover {
  transform: translateY(-6px) rotate(-0.5deg);
  box-shadow: 0 24px 50px rgba(18, 12, 9, 0.45);
}

.folder-clip-top {
  position: absolute;
  top: -18px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 5;
}

.metal-binder-clip {
  width: 58px;
  height: 32px;
  background: linear-gradient(180deg, #d8dee3 0%, #8b959e 50%, #4a5259 100%);
  border-radius: 4px 4px 2px 2px;
  box-shadow: 0 4px 10px rgba(0,0,0,0.3);
  position: relative;
}

.metal-binder-clip::after {
  content: "";
  position: absolute;
  top: -12px;
  left: 50%;
  transform: translateX(-50%);
  width: 36px;
  height: 16px;
  border: 3px solid #bfc7cd;
  border-bottom: none;
  border-radius: 10px 10px 0 0;
}

.folder-cover-paper {
  position: relative;
  width: 100%;
  height: 100%;
  padding: 3rem 2.2rem 2.5rem;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
}

.tape-strip.top-left {
  position: absolute;
  top: 15px;
  left: -12px;
  width: 80px;
  height: 24px;
  background: rgba(255, 255, 255, 0.45);
  transform: rotate(-15deg);
  box-shadow: 0 2px 4px rgba(0,0,0,0.06);
}

.paper-label-main {
  background: #fbf7ee;
  padding: 1.8rem 1.6rem;
  border: 1px dashed #c0b099;
  border-radius: var(--radius-sm);
  box-shadow: 0 4px 12px rgba(0,0,0,0.05);
}

.label-header {
  font-family: var(--font-serif);
  font-size: 1.6rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  color: #2a201a;
  border-bottom: 2px solid #2a201a;
  padding-bottom: 0.4rem;
  margin-bottom: 0.5rem;
}

.label-subtitle {
  font-size: 0.85rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  color: #5d4f45;
  margin-bottom: 1rem;
}

.label-meta {
  font-family: var(--font-mono);
  font-size: 0.78rem;
  color: #726256;
}

.label-meta .val {
  color: var(--color-terracotta);
  font-weight: 600;
}

.red-stamp-box {
  margin: 1.5rem auto;
  border: 3px solid #b73e2d;
  color: #b73e2d;
  padding: 0.6rem 1.6rem;
  transform: rotate(-8deg);
  font-family: var(--font-mono);
  font-size: 1.1rem;
  font-weight: 800;
  letter-spacing: 0.12em;
  text-align: center;
  border-radius: var(--radius-sm);
  background: rgba(183, 62, 45, 0.04);
}

.paperclip-note {
  position: absolute;
  bottom: 20px;
  left: 20px;
}

.tiny-kraft-tag {
  background: #dfcbaf;
  padding: 4px 10px;
  font-family: var(--font-mono);
  font-size: 0.7rem;
  font-weight: 700;
  color: #4f3f33;
  border-radius: 2px;
  box-shadow: 0 2px 6px rgba(0,0,0,0.1);
}

.folder-index-tab {
  position: absolute;
  right: -24px;
  top: 140px;
  background: #739886;
  color: #fff;
  padding: 12px 6px;
  border-radius: 0 6px 6px 0;
  font-size: 0.65rem;
  font-weight: 700;
  letter-spacing: 0.15em;
  writing-mode: vertical-rl;
  box-shadow: 2px 4px 8px rgba(0,0,0,0.15);
}

/* OPENED FOLDER SPREAD VIEW */
.folder-spread-opened {
  position: relative;
  width: 960px;
  max-width: 94vw;
  height: 600px;
  max-height: 82vh;
  background: #c7b090;
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-kraft);
  display: none;
  grid-template-columns: 1fr 1fr;
  border: 1px solid #a89173;
}

.dossier-wrapper.open .folder-spread-opened {
  display: grid;
  animation: folderUnfold 0.6s cubic-bezier(0.16, 1, 0.3, 1) forwards;
}

@keyframes folderUnfold {
  from { opacity: 0; transform: scale(0.96) rotateX(4deg); }
  to { opacity: 1; transform: scale(1) rotateX(0deg); }
}

.folder-spine-crease {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 50%;
  width: 14px;
  transform: translateX(-50%);
  background: linear-gradient(90deg, rgba(0,0,0,0.12) 0%, rgba(255,255,255,0.08) 50%, rgba(0,0,0,0.18) 100%);
  z-index: 5;
  pointer-events: none;
}

.spread-binder-clip {
  position: absolute;
  top: -14px;
  left: 25%;
  z-index: 10;
}

.metal-clip-body {
  width: 48px;
  height: 24px;
  background: linear-gradient(180deg, #d8dee3 0%, #8b959e 100%);
  border-radius: 3px;
  box-shadow: 0 3px 8px rgba(0,0,0,0.25);
}

/* 展开态的侧标签 = 「合上」开关。
   开合入口做在档案袋本体上（合上态点封面、展开态点这个标签），
   不再有挂在页面上的控制条 —— 那一条已按用户要求整块移除。
   hover / 聚焦时向右滑出一点、底色转陶土色，提示「可以拉」。 */
.spread-index-tab {
  position: absolute;
  right: -24px;
  top: 160px;
  background: #739886;
  color: #fff;
  padding: 14px 6px;
  border-radius: 0 6px 6px 0;
  font-size: 0.65rem;
  font-weight: 700;
  letter-spacing: 0.15em;
  writing-mode: vertical-rl;
  box-shadow: 2px 4px 8px rgba(0,0,0,0.15);
  cursor: pointer;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  transition: right 0.24s cubic-bezier(0.2, 0.8, 0.2, 1),
              background 0.24s ease,
              box-shadow 0.24s ease;
}
.spread-index-tab .tab-close-hint {
  font-size: 0.8rem;
  line-height: 1;
  opacity: 0.55;
  transform: rotate(90deg);
  transition: opacity 0.24s ease, transform 0.24s ease;
}
.spread-index-tab:hover,
.spread-index-tab:focus-visible {
  right: -33px;
  background: var(--color-terracotta);
  box-shadow: 5px 7px 16px rgba(0, 0, 0, 0.26);
}
.spread-index-tab:hover .tab-close-hint,
.spread-index-tab:focus-visible .tab-close-hint {
  opacity: 1;
  transform: rotate(90deg) scale(1.15);
}
.spread-index-tab:focus-visible { outline: 2px solid #fff; outline-offset: -3px; }

.spread-page {
  position: relative;
  padding: 1.8rem 1.6rem;
  overflow-y: auto;
  scrollbar-width: thin;
  scrollbar-color: #a89173 transparent;
}

.spread-page::-webkit-scrollbar {
  width: 5px;
}
.spread-page::-webkit-scrollbar-thumb {
  background: #a89173;
  border-radius: 4px;
}

/* Paper Artifact Elements */
.paper-element {
  position: relative;
  background: #fbf7f0;
  box-shadow: 0 4px 14px rgba(28, 20, 16, 0.08);
  border-radius: var(--radius-sm);
  transition: transform 0.2s ease, box-shadow 0.2s ease;
}

.paper-element:hover {
  transform: translateY(-2px);
  box-shadow: 0 6px 18px rgba(28, 20, 16, 0.12);
}

/* 1. 35mm Film Strip */
.film-strip-card {
  background: #181513;
  padding: 8px 12px;
  border-radius: 3px;
  margin-bottom: 1rem;
  cursor: pointer;
  box-shadow: 0 6px 16px rgba(0,0,0,0.2);
}

.film-frames-track {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}

.film-frame {
  position: relative;
  flex: 1;
  aspect-ratio: 4/3;
  background: #000;
  overflow: hidden;
  border-radius: 2px;
}

.film-frame img {
  display: block;              /* FIX: inline 图片的基线间隙会让帧内容高出 4px 被裁 */
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: contrast(108%) sepia(10%);
  transition: transform 0.3s ease;
}

.film-frame:hover img {
  transform: scale(1.08);
}

.frame-num {
  position: absolute;
  bottom: 2px;
  right: 4px;
  color: #df9e48;
  font-family: var(--font-mono);
  font-size: 0.55rem;
  font-weight: 700;
}

/* 2. Vintage Ticket: THINGS I CAN DO */
.vintage-ticket-card {
  background: #efe3ce;
  border: 1px solid #d8c3a4;
  padding: 1rem 1.1rem;
  margin-bottom: 1rem;
}

.ticket-header {
  margin-bottom: 0.6rem;
  border-bottom: 1px dashed #bba486;
  padding-bottom: 0.4rem;
}

.ticket-badge {
  font-family: var(--font-mono);
  font-size: 0.6rem;
  letter-spacing: 0.1em;
  color: #7b6852;
}

.ticket-title {
  font-family: var(--font-serif);
  font-size: 1.15rem;
  font-weight: 700;
  color: #2b1f18;
}

.ticket-body {
  display: flex;
  flex-direction: column;
  gap: 0.45rem;
  margin-bottom: 0.6rem;
}

.ticket-item {
  display: flex;
  align-items: flex-start;
  gap: 0.6rem;
}

.ticket-item .item-num {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  font-weight: 700;
  color: var(--color-terracotta);
}

.ticket-item .item-desc strong {
  display: block;
  font-size: 0.78rem;
  color: #261c16;
}

.ticket-item .item-desc span {
  display: block;
  font-size: 0.68rem;
  color: #6d5d51;
}

.ticket-perforation {
  border-top: 1px dashed #bba486;
  margin: 0.5rem 0;
}

.ticket-footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.ticket-barcode {
  font-family: var(--font-mono);
  font-size: 0.65rem;
  letter-spacing: 0.2em;
  color: #4b3d32;
}

.ticket-admit {
  font-family: var(--font-mono);
  font-size: 0.7rem;
  font-weight: 800;
  color: var(--color-terracotta);
  border: 1px solid var(--color-terracotta);
  padding: 2px 6px;
  border-radius: 2px;
}

/* 3. Supermarket Receipt: THINGS I LIKE */
.thermal-receipt-card {
  background: #fbf9f5;
  padding: 0.9rem 1rem;
  font-family: var(--font-mono);
  font-size: 0.68rem;
  color: #3b322a;
  border: 1px solid #e2d8cb;
  margin-bottom: 1rem;
}

.receipt-title {
  font-size: 0.82rem;
  font-weight: 700;
  text-align: center;
  letter-spacing: 0.08em;
}

.receipt-date {
  text-align: center;
  color: #8b7d70;
  margin-bottom: 0.4rem;
}

.receipt-dash {
  color: #c9bea2;
  text-align: center;
  overflow: hidden;
  white-space: nowrap;
}

.receipt-line {
  display: flex;
  justify-content: space-between;
  line-height: 1.5;
}

.receipt-total {
  display: flex;
  justify-content: space-between;
  font-weight: 700;
  margin: 0.4rem 0;
}

.receipt-barcode {
  text-align: center;
  letter-spacing: 0.25em;
  margin-top: 0.4rem;
  color: #55493e;
}

/* 4. Kraft Quote Sticky Note */
.kraft-quote-note {
  background: #f8e6b7;
  padding: 0.75rem 1rem;
  border-radius: 2px;
  transform: rotate(-1deg);
  box-shadow: 0 4px 10px rgba(0,0,0,0.06);
}

.quote-tape {
  position: absolute;
  top: -8px;
  left: 50%;
  transform: translateX(-50%);
  width: 44px;
  height: 14px;
  background: rgba(255, 255, 255, 0.65);
  box-shadow: 0 1px 3px rgba(0,0,0,0.08);
}

.quote-text {
  font-family: var(--font-serif);
  font-size: 0.82rem;
  color: #36291e;
  line-height: 1.5;
  text-align: center;
  font-style: italic;
}

/* RIGHT PAGE DETAILS */
.experience-header-section {
  margin-bottom: 0.9rem;
}

.spread-main-title {
  font-family: var(--font-serif);
  font-size: 1.35rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  color: #241b16;
  border-bottom: 2px solid #241b16;
  padding-bottom: 0.35rem;
}

/* Basic Info Card */
.basic-info-card {
  padding: 0.9rem 1rem;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  margin-bottom: 1rem;
  background: #fbf7ee;
}

.info-table-title {
  font-family: var(--font-serif);
  font-size: 0.8rem;
  font-weight: 700;
  color: #352820;
  margin-bottom: 0.4rem;
}

.info-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0.35rem 1rem;
}

.info-cell {
  font-size: 0.72rem;
  display: flex;
  align-items: center;
  gap: 0.4rem;
}

.info-cell .k {
  color: #7d6e62;
  font-family: var(--font-mono);
  font-size: 0.65rem;
}

.info-cell .v {
  font-weight: 600;
  color: #231913;
}

.info-cell .v.highlight {
  color: var(--color-terracotta);
  text-transform: uppercase;
  font-weight: 700;
}

.id-photo-frame {
  position: relative;
  width: 68px;
  height: 80px;
  background: #fff;
  padding: 3px;
  border: 1px solid #d5c7b5;
  border-radius: 3px;
  box-shadow: 0 4px 10px rgba(0,0,0,0.08);
  flex-shrink: 0;
}

.id-photo-img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: 2px;
}

.id-photo-pin {
  position: absolute;
  top: -6px;
  left: 50%;
  transform: translateX(-50%);
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: #c54131;
  box-shadow: 0 2px 4px rgba(0,0,0,0.2);
}

/* Experience List */
.experience-list-card {
  padding: 1rem;
  background: #fdfbf8;
  margin-bottom: 1rem;
}

.exp-card-header {
  font-family: var(--font-serif);
  font-size: 0.82rem;
  font-weight: 700;
  color: #2b1f18;
  margin-bottom: 0.7rem;
  border-bottom: 1px solid #e8dfd3;
  padding-bottom: 0.3rem;
}

.exp-items-group {
  display: flex;
  flex-direction: column;
  gap: 0.8rem;
}

.exp-row {
  border-left: 2px solid #cebaa0;
  padding-left: 0.65rem;
}

.exp-meta {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  flex-wrap: wrap;             /* FIX: 窄屏下公司名与英文职位会挤在同一行互相压住 */
  gap: 0.15rem 0.6rem;
  margin-bottom: 0.2rem;
}

.exp-corp {
  font-size: 0.78rem;
  font-weight: 700;
  color: #241a15;
}

.exp-role {
  font-family: var(--font-mono);
  font-size: 0.62rem;
  color: var(--color-terracotta);
  font-weight: 600;
  /* 补了中文之后「中文职位 + 英文职位」并排会把卡片右缘撑到裁字，
     所以让英文另起一行、整体右对齐。 */
  text-align: right;
}
.exp-role .en {
  display: block;
  font-size: 0.94em;
  opacity: 0.82;
  letter-spacing: 0.02em;
}

.exp-desc {
  font-size: 0.7rem;
  color: #55483e;
  line-height: 1.5;
}

/* Bottom Accessories */
.spread-bottom-accessories {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
}

.luggage-tag-card {
  position: relative;
  background: #dfcbaf;
  border: 1px solid #c9b08f;
  padding: 0.7rem 1rem;
  border-radius: var(--radius-sm);
  flex: 1;
}

.tag-headline {
  font-family: var(--font-mono);
  font-size: 0.68rem;
  font-weight: 800;
  color: #2b1f18;
  letter-spacing: 0.05em;
}

.tag-script {
  font-family: var(--font-serif);
  font-size: 0.75rem;
  color: #574639;
  font-style: italic;
  margin: 0.15rem 0;
}

.tag-stamp-circle {
  font-family: var(--font-mono);
  font-size: 0.55rem;
  color: var(--color-terracotta);
  font-weight: 700;
}

.vintage-mailbox-badge {
  width: 54px;
  height: 64px;
  background: #ba3d2e;
  border-radius: 8px 8px 3px 3px;
  box-shadow: 0 4px 10px rgba(0,0,0,0.18);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  color: #fff;
  flex-shrink: 0;
  border: 1px solid #99281a;
}

.badge-post-label {
  font-family: var(--font-mono);
  font-size: 0.6rem;
  font-weight: 800;
  letter-spacing: 0.1em;
}

.badge-slot {
  width: 28px;
  height: 4px;
  background: #420f09;
  border-radius: 2px;
  margin-top: 5px;
}

/* ================= SCENE 5: CONTACT ================= */
.contact-editorial-panel {
  position: absolute;
  top: 50%;
  left: 50%;
  /* ★ 默认收起：既不显示也不吃事件。
     用户要求「要点一下才显示」—— 入口是插画上那个红色邮筒（.mailbox-hotspot）。
     收起态保留 translate(-50%,-50%) 的居中，再叠一点位移与缩放，展开时才有「浮出来」的感觉。 */
  opacity: 0;
  pointer-events: none;
  transform: translate(-50%, -50%) translateY(22px) scale(0.97);
  transition: opacity 0.34s cubic-bezier(0.2, 0.8, 0.2, 1),
              transform 0.34s cubic-bezier(0.2, 0.8, 0.2, 1);
  width: 640px;
  max-width: 92vw;
  background: rgba(251, 247, 240, 0.94);
  backdrop-filter: blur(16px);
  -webkit-backdrop-filter: blur(16px);
  border: 1px solid #decbb2;
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-editorial);
  z-index: 20;
  overflow: hidden;
  max-height: 86vh;
  overflow-y: auto;
}

.contact-editorial-panel.is-open {
  opacity: 1;
  pointer-events: auto;
  transform: translate(-50%, -50%);
}

/* 面板右上角的关闭按钮 —— 用户要求「能点掉」 */
.panel-close-btn {
  position: absolute;
  top: 0.7rem;
  right: 0.8rem;
  z-index: 3;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  border: 1px solid #decbb2;
  background: rgba(255, 253, 248, 0.92);
  color: #6b5a4c;
  font-size: 1.15rem;
  line-height: 1;
  cursor: pointer;
  display: grid;
  place-items: center;
  transition: background 0.2s ease, color 0.2s ease, transform 0.2s ease;
}
.panel-close-btn:hover,
.panel-close-btn:focus-visible {
  background: var(--color-terracotta);
  color: #fff;
  transform: scale(1.08);
}
.panel-close-btn:focus-visible { outline: 2px solid var(--color-terracotta); outline-offset: 2px; }

/* ===== 邮筒热区：写一封信的入口 =====
   .contact-art-layer 与插画 1376×768 严格 1:1 —— 复刻 object-fit:cover 的数学
   （和咖啡场景 .coffee-art 同一套做法），所以热区可以直接用插画像素百分比定位，
   在任何视口下都精确套在邮筒上。层本身不吃事件，只有按钮吃。 */
.contact-art-layer {
  position: absolute;
  left: 50%;
  top: 50%;
  /* ★ 邮筒热区锁在插画 x352..588 上，而 cover 定位在窄屏只看得到插画中间：
     390×844 下可见范围只有 x510..865，邮筒被裁得只剩右一三分一，
     贴在屏幕最左边。这里把镜头往右推，把可见左边界推到 x340。
     推导：可见左边界 = 688 − W/(2K)，K = max(100vw, 100vh×1376/768)/1376；
     要它到 340 得 s = max(0, 348K − W/2)（正值 = 往右推）。
     宽屏时 max() 取 0，自动失效。 */
  transform: translate(-50%, -50%)
             translateX(calc(max(0px, 0.2529 * max(100vw, 100vh * 1376 / 768) - 50vw)));
  width: max(100%, calc(100vh * 1376 / 768));
  height: max(100%, calc(100vw * 768 / 1376));
  pointer-events: none;
}
.mailbox-hotspot {
  position: absolute;
  /* 坐标来自「2 倍放大 + 50px 坐标网格」的原图实测：邮筒本体 x360..580 / y162..475。
     ★ 别从缩略图或缩放后的截图上读坐标 —— 第一版就是这么错的，
       而且用颜色阈值扫描也不行（邮筒右下偏暗，阈值一收就把那半截漏掉了）。
       四边各外扩约 8px，让热区更好点。 */
  left: 25.581%;               /* 352/1376 */
  top: 20.052%;                /* 154/768  */
  width: 17.151%;              /* 236/1376 */
  height: 42.839%;             /* 329/768  */
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
  pointer-events: auto;
  -webkit-tap-highlight-color: transparent;
}
.mailbox-ring {
  position: absolute;
  inset: 0;
  border-radius: 18px;
  border: 2px dashed rgba(214, 74, 58, 0);
  transition: border-color 0.24s ease, box-shadow 0.24s ease, background 0.24s ease;
  /* ★ 用户要求去掉「提示外圈」：这里原本有一条常驻的呼吸光晕，
     现在整块删掉 —— 圈只在鼠标移上来（或键盘聚焦）时出现，当纯粹的 hover 反馈。
     静止的插画上不留任何圈。 */
}
.mailbox-hotspot:hover .mailbox-ring,
.mailbox-hotspot:focus-visible .mailbox-ring {
  border-color: rgba(214, 74, 58, 0.85);
  background: rgba(214, 74, 58, 0.10);
  box-shadow: 0 0 24px rgba(214, 74, 58, 0.48);
}
.mailbox-tip {
  position: absolute;
  left: 50%;
  bottom: calc(100% + 8px);
  transform: translateX(-50%) translateY(4px);
  padding: 0.22rem 0.62rem;
  border-radius: 999px;
  background: rgba(26, 18, 14, 0.9);
  color: #ffe9c2;
  font-size: 0.7rem;
  font-weight: 600;
  white-space: nowrap;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.2s ease, transform 0.2s ease;
}
.mailbox-hotspot:hover .mailbox-tip,
.mailbox-hotspot:focus-visible .mailbox-tip { opacity: 1; transform: translateX(-50%) translateY(0); }
.mailbox-hotspot:focus-visible { outline: none; }

.panel-inner {
  padding: 2.2rem 2.4rem;
  position: relative;
}

.panel-stamp {
  position: absolute;
  top: 1.8rem;
  right: 2.4rem;
  border: 2px solid var(--color-terracotta);
  color: var(--color-terracotta);
  font-family: var(--font-mono);
  font-size: 0.75rem;
  font-weight: 800;
  padding: 3px 10px;
  letter-spacing: 0.15em;
  transform: rotate(6deg);
}

.panel-title {
  font-family: var(--font-serif);
  font-size: 1.85rem;
  font-weight: 700;
  color: #241c17;
  /* FIX: 右上角 .panel-stamp 是绝对定位，标题会整段跑到邮戳底下。
     实测 390px 重叠 57%、360px 重叠 76%。 */
  padding-right: 6.2rem;
  margin-bottom: 0.4rem;
}

.panel-subtitle {
  font-size: 0.88rem;
  color: #635347;
  line-height: 1.6;
  margin-bottom: 1.5rem;
}

.editorial-letter-form {
  display: flex;
  flex-direction: column;
  gap: 1rem;
  margin-bottom: 1.5rem;
}

.form-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1rem;
}

.form-field {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
}

.form-field label {
  font-size: 0.75rem;
  font-weight: 600;
  color: #42352b;
}

.form-field input, .form-field textarea {
  background: #fff;
  border: 1px solid #dcd1c2;
  border-radius: var(--radius-sm);
  padding: 0.65rem 0.9rem;
  font-family: var(--font-sans);
  font-size: 0.82rem;
  color: #2b2019;
  outline: none;
  transition: border-color 0.2s ease, box-shadow 0.2s ease;
}

.form-field input:focus, .form-field textarea:focus {
  border-color: #2b1f18;
  box-shadow: 0 0 0 3px rgba(43, 31, 24, 0.08);
}

.form-actions {
  display: flex;
  justify-content: flex-end;
}

.contact-channel-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.6rem;
  padding-top: 1.2rem;
  border-top: 1px dashed #ded3c1;
  margin-bottom: 1.2rem;
}

.channel-chip {
  background: #f1e7d8;
  padding: 0.4rem 0.85rem;
  border-radius: var(--radius-full);
  font-size: 0.75rem;
  color: #3b2e24;
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  cursor: pointer;
  text-decoration: none;
  transition: all 0.2s ease;
}

.channel-chip:hover {
  background: #2b1f18;
  color: #fff;
}

.copy-tag {
  font-size: 0.65rem;
  background: rgba(0, 0, 0, 0.08);
  padding: 1px 5px;
  border-radius: 4px;
}

.recent-letters-section {
  background: #f6efe4;
  border-radius: var(--radius-md);
  padding: 1rem 1.2rem;
}

.guestbook-title {
  display: flex;
  justify-content: space-between;
  align-items: center;
  font-size: 0.78rem;
  font-weight: 700;
  color: #382b21;
  margin-bottom: 0.75rem;
}

.guestbook-counter {
  font-family: var(--font-mono);
  font-size: 0.7rem;
  color: var(--color-terracotta);
}

.letters-stream {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
}

.guestbook-card {
  background: #fff;
  padding: 0.7rem 0.9rem;
  border-radius: var(--radius-sm);
  border-left: 3px solid var(--color-terracotta);
  box-shadow: 0 2px 6px rgba(0,0,0,0.04);
}

.guestbook-header {
  display: flex;
  justify-content: space-between;
  flex-wrap: wrap;             /* FIX: 长昵称 + 时间戳在窄屏会互相挤压 */
  gap: 0.15rem 0.6rem;
  margin-bottom: 0.25rem;
}

.guestbook-author {
  font-size: 0.76rem;
  font-weight: 700;
  color: #241c17;
  overflow-wrap: anywhere;
}

.guestbook-time {
  font-size: 0.65rem;
  color: #928376;
  font-family: var(--font-mono);
  flex-shrink: 0;
  white-space: nowrap;
}

.guestbook-body {
  font-size: 0.74rem;
  color: #55483d;
  line-height: 1.45;
}

/* ================= EDITORIAL MODALS & LIGHTBOXES ================= */
.editorial-modal {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: flex;
  align-items: center;
  justify-content: center;
  opacity: 0;
  pointer-events: none;
  visibility: hidden;
  transition: opacity 0.3s ease, visibility 0.3s ease;
}

.editorial-modal.active {
  opacity: 1;
  pointer-events: auto;
  visibility: visible;
}

.modal-backdrop {
  position: absolute;
  inset: 0;
  background: rgba(18, 14, 11, 0.65);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
}

.modal-dialog {
  position: relative;
  background: #fbf7ee;
  border-radius: var(--radius-lg);
  box-shadow: 0 24px 60px rgba(0, 0, 0, 0.35);
  border: 1px solid #decbb4;
  overflow: hidden;
  z-index: 10;
  transform: scale(0.95) translateY(12px);
  transition: transform 0.3s cubic-bezier(0.16, 1, 0.3, 1);
  display: flex;
  flex-direction: column;
}

.editorial-modal.active .modal-dialog {
  transform: scale(1) translateY(0);
}

.large-dialog {
  width: 860px;
  max-width: 92vw;
  max-height: 86vh;
}

.book-dialog {
  width: 720px;
  max-width: 90vw;
  max-height: 82vh;
}

.photo-dialog {
  width: 760px;
  max-width: 90vw;
  max-height: 86vh;
}

.small-dialog {
  width: 380px;
  max-width: 90vw;
}

.modal-header {
  padding: 1.4rem 1.8rem;
  display: flex;
  align-items: center;
  justify-content: space-between;
  border-bottom: 1px solid #ebd9c4;
}

.modal-kicker {
  font-family: var(--font-mono);
  font-size: 0.65rem;
  letter-spacing: 0.12em;
  color: var(--color-terracotta);
  font-weight: 700;
  display: block;
}

.modal-title {
  font-family: var(--font-serif);
  font-size: 1.4rem;
  font-weight: 700;
  color: #231b15;
}

.modal-close-btn {
  background: transparent;
  border: none;
  font-size: 1.8rem;
  color: #726255;
  cursor: pointer;
  line-height: 1;
  transition: color 0.2s ease;
}

.modal-close-btn:hover {
  color: var(--color-terracotta);
}

/* Project Filter & Grid */
.project-filter-bar {
  display: flex;
  align-items: center;
  flex-wrap: wrap;             /* FIX: 窄屏下「影像与叙事」标签会溢出弹层右边缘 */
  gap: 0.5rem;
  padding: 0.8rem 1.8rem;
  background: #f4ecdf;
  border-bottom: 1px solid #e7d8c5;
}

.filter-tab {
  background: transparent;
  border: 1px solid transparent;
  padding: 0.4rem 0.9rem;
  border-radius: var(--radius-full);
  font-size: 0.76rem;
  font-weight: 600;
  color: #55463a;
  cursor: pointer;
  transition: all 0.2s ease;
}

.filter-tab:hover {
  background: rgba(0, 0, 0, 0.05);
}

.filter-tab.active {
  background: #251b15;
  color: #fff;
}

.projects-grid {
  padding: 1.8rem;
  overflow-y: auto;
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  /* FIX: 不加这三行时，网格在「高度受限的 flex 子项」里会把 auto 行均分成
     容器剩余高度（实测 1366x768 下每行只剩 214px，而卡片内容需要 321px），
     卡片被 overflow:hidden 直接切掉正文。 */
  grid-auto-rows: max-content;
  align-content: start;
  flex: 1 1 auto;
  min-height: 0;
  gap: 1.4rem;
}

.project-card {
  background: #fff;
  border-radius: var(--radius-md);
  border: 1px solid #ebd9c5;
  overflow: hidden;
  box-shadow: 0 4px 14px rgba(0,0,0,0.05);
  display: flex;
  flex-direction: column;
  transition: transform 0.25s ease, box-shadow 0.25s ease;
}

.project-card:hover {
  transform: translateY(-4px);
  box-shadow: 0 10px 24px rgba(0,0,0,0.1);
}

.card-cover-media {
  height: 140px;
  position: relative;
  padding: 1rem;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
}

.card-cover-media.grad-warm { background: linear-gradient(135deg, #dfa97b 0%, #c46d4a 100%); }
.card-cover-media.grad-blue { background: linear-gradient(135deg, #7ba2c0 0%, #3a688b 100%); }
.card-cover-media.grad-forest { background: linear-gradient(135deg, #8ea77f 0%, #476a43 100%); }
.card-cover-media.grad-amber { background: linear-gradient(135deg, #e3ba6d 0%, #a87e38 100%); }

.cover-chip {
  align-self: flex-start;
  background: rgba(255, 255, 255, 0.9);
  color: #241b16;
  font-size: 0.65rem;
  font-weight: 700;
  padding: 2px 8px;
  border-radius: var(--radius-full);
}

.mock-device {
  background: rgba(255, 255, 255, 0.92);
  border-radius: 6px;
  padding: 6px;
  box-shadow: 0 4px 10px rgba(0,0,0,0.15);
}

.mock-topbar {
  display: flex;
  gap: 3px;
  margin-bottom: 4px;
}

.mock-topbar span {
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: #cca496;
}

.mock-content {
  font-size: 0.65rem;
  color: #3a2e26;
  display: flex;
  flex-direction: column;
  gap: 3px;
}

.mock-bubble {
  padding: 3px 6px;
  border-radius: 4px;
  font-size: 0.6rem;
}

.mock-bubble.right { background: #e8ded2; align-self: flex-end; }
.mock-bubble.left { background: #fbf6ef; border: 1px solid #ebd9c5; }

.mock-nodes {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  font-size: 0.65rem;
  font-weight: 700;
  color: #3b2a21;
}

.card-body {
  padding: 1.1rem;
  display: flex;
  flex-direction: column;
  flex: 1;
}

.card-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem;
  margin-bottom: 0.5rem;
}

.card-tags span {
  background: #f4ecdf;
  color: #68574a;
  font-size: 0.65rem;
  padding: 2px 6px;
  border-radius: 3px;
}

.card-title {
  font-family: var(--font-serif);
  font-size: 1.05rem;
  font-weight: 700;
  color: #231b15;
  margin-bottom: 0.4rem;
}

.card-desc {
  font-size: 0.76rem;
  color: #5d4e43;
  line-height: 1.5;
  margin-bottom: 0.8rem;
  flex: 1;
}

.card-meta-list {
  border-top: 1px dashed #ede2d3;
  padding-top: 0.6rem;
  font-size: 0.7rem;
  color: #726256;
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
}

/* Notebook Modal */
.notebook-content-body {
  padding: 2rem;
  overflow-y: auto;
  flex: 1 1 auto;              /* FIX: 作为 .modal-dialog 的 flex 子项，不收缩就滚不动 */
  min-height: 0;
}

.sketch-paper {
  background: #fff;
  border: 1px solid #ebd9c5;
  padding: 1.8rem 2rem;
  border-radius: var(--radius-sm);
  box-shadow: 0 4px 16px rgba(0,0,0,0.05);
}

.sketch-heading {
  font-family: var(--font-mono);
  font-size: 0.85rem;
  font-weight: 700;
  color: var(--color-terracotta);
  margin-top: 1.4rem;
  margin-bottom: 0.6rem;
}

.sketch-heading:first-child {
  margin-top: 0;
}

.sketch-para {
  font-size: 0.85rem;
  color: #3d3027;
  line-height: 1.7;
}

.principles-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 1rem;
  margin-top: 0.8rem;
}

.principle-box {
  background: #faf6ef;
  padding: 1rem;
  border-radius: var(--radius-sm);
  border: 1px solid #ebd8c2;
}

.principle-box .p-num {
  font-family: var(--font-mono);
  font-size: 1.1rem;
  font-weight: 800;
  color: var(--color-terracotta);
}

.principle-box .p-title {
  font-family: var(--font-serif);
  font-size: 0.9rem;
  font-weight: 700;
  margin: 0.2rem 0 0.4rem;
  color: #241a15;
}

.principle-box p {
  font-size: 0.72rem;
  color: #5d4f44;
  line-height: 1.5;
}

.inspiration-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-top: 0.6rem;
}

.inspiration-tags span {
  background: #f0e6d6;
  padding: 4px 10px;
  border-radius: var(--radius-full);
  font-size: 0.74rem;
  color: #47392f;
}

/* Photo Gallery Lightbox */
.photo-gallery-body {
  padding: 1.5rem;
  display: flex;
  flex-direction: column;
  gap: 1rem;
  flex: 1 1 auto;              /* FIX: 矮屏（<=700px 高）下相册会撑破弹层且无法滚动 */
  min-height: 0;
  overflow-y: auto;
}

.lightbox-viewer {
  width: 100%;
  aspect-ratio: 16/10;
  background: #110e0c;
  border-radius: var(--radius-md);
  overflow: hidden;
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
}

.lightbox-viewer img {
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
  transition: opacity 0.25s ease;
}

.lightbox-caption {
  position: absolute;
  bottom: 0;
  left: 0;
  right: 0;
  background: linear-gradient(180deg, transparent 0%, rgba(0,0,0,0.7) 100%);
  color: #fff;
  padding: 1rem 1.4rem;
  font-family: var(--font-serif);
  font-size: 0.88rem;
}

.photo-thumb-row {
  display: flex;
  gap: 0.8rem;
  justify-content: center;
}

.photo-thumb {
  width: 80px;
  height: 56px;
  object-fit: cover;
  border-radius: 4px;
  cursor: pointer;
  opacity: 0.6;
  border: 2px solid transparent;
  transition: all 0.2s ease;
}

.photo-thumb:hover {
  opacity: 0.9;
}

.photo-thumb.active {
  opacity: 1;
  border-color: var(--color-terracotta);
  transform: scale(1.05);
}

/* WeChat QR Modal */
.wechat-modal-content {
  padding: 1.8rem;
  display: flex;
  justify-content: center;
  flex: 1 1 auto;              /* FIX: 同上，矮屏下保证可滚动而不是被裁 */
  min-height: 0;
  overflow-y: auto;
}

.mock-qr-card {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.8rem;
}

.qr-pattern {
  width: 160px;
  height: 160px;
  background: #fff;
  border: 2px solid #231b15;
  border-radius: 12px;
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: 0 4px 16px rgba(0,0,0,0.08);
}

.qr-corner {
  position: absolute;
  width: 32px;
  height: 32px;
  border: 4px solid #231b15;
}
.qr-corner.top-left { top: 8px; left: 8px; }
.qr-corner.top-right { top: 8px; right: 8px; }
.qr-corner.bottom-left { bottom: 8px; left: 8px; }
.qr-center-icon { font-size: 2rem; }

.qr-id-text {
  font-family: var(--font-mono);
  font-size: 0.85rem;
  font-weight: 700;
  color: #241c16;
}

.qr-hint {
  font-size: 0.72rem;
  color: #7b6d61;
}

/* Toast */
.editorial-toast {
  position: fixed;
  bottom: 2rem;
  right: 2.5rem;
  max-width: calc(100vw - 3rem);   /* FIX: 长文案在窄屏会向左溢出屏幕 */
  background: #1a1512;
  color: #fff;
  padding: 0.7rem 1.4rem;
  border-radius: var(--radius-full);
  font-size: 0.8rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  box-shadow: 0 8px 24px rgba(0,0,0,0.3);
  z-index: 9999;
  opacity: 0;
  transform: translateY(12px);
  pointer-events: none;
  transition: all 0.3s cubic-bezier(0.2, 0.8, 0.2, 1);
  border: 1px solid rgba(255, 255, 255, 0.15);
}

.editorial-toast.show {
  opacity: 1;
  transform: translateY(0);
}

/* ================= TEXT SELECTION ================= */
/* 全局 user-select:none 会让访客无法选中/复制邮箱、经历等正文内容。
   交互控件保持不可选，正文恢复可选。 */
.hero-headline, .hero-domains, .hero-tagline,
.exp-desc, .card-desc, .guestbook-body, .sketch-para, .panel-subtitle,
.receipt-line, .ticket-item, .info-cell, .label-meta, .qr-id-text,
input, textarea {
  user-select: text;
  -webkit-user-select: text;
}

/* ================= RESPONSIVE ADJUSTMENTS ================= */

/* --- 小桌面 / 横屏平板 --- */
/* --- 窄桌面 --- */
@media (max-width: 1024px) {
  .home-hero-text {
    left: 40%;
    top: 15%;
  }
  .folder-spread-opened {
    width: 95vw;
    height: 75vh;
  }
}

/* --- 平板 --- */
@media (max-width: 900px) {
  .home-hero-text {
    left: 6%;
    right: 6%;
    top: 14%;
    max-width: 560px;
  }
  /* 控制台与文案层都已整块移除，这一屏的操作入口只剩插画上的 8 个部件。
     cover 定位在窄屏会把机器右半边裁掉 —— 390 宽时可见的插画范围只有 x670..1025，
     而机器是 x655..1040，功能旋钮、蒸汽棒、机身屏幕全在可见范围之外，根本点不到。
     这里把镜头横向推到机器上：「8 个部件 + 机身屏」的横向跨度是 x679..1012，
     中心 845.5，即元素宽度的 61.46%，所以需要左移 (0.6156 − 0.5) ≈ 11.56% 的元素宽度。
     元素宽度 = max(100vw, 100vh × 1376/768)，与 .coffee-art 的 width 同式。 */
  .coffee-art {
    transform: translate(-50%, -50%)
               translateX(calc(-0.1156 * max(100vw, 100vh * 1376 / 768)));
  }
}

/* --- 竖屏平板 / 大屏手机 --- */
@media (max-width: 768px) {
  .site-header {
    padding: 0 1rem;
  }
  .brand-name {
    display: none;
  }
  .header-nav {
    gap: 0.5rem;
    padding: 0.2rem 0.4rem;
  }
  .nav-item {
    padding: 0.3rem 0.6rem;
  }
  .home-hero-text {
    left: 7%;
    right: 7%;
    top: 14%;
    max-width: none;
  }
  .hero-headline {
    font-size: 2.4rem;
  }
  .hero-cta-group {
    flex-wrap: wrap;          /* FIX: 三个 CTA 在窄屏会横向溢出 */
    gap: 0.6rem;
  }
  .hero-tagline {
    font-size: 0.95rem;
    margin-bottom: 1.2rem;
  }

  /* 档案袋：单列 + 整体纵向滚动，比左右两页各自滚动好用得多 */
  .folder-spread-opened {
    grid-template-columns: 1fr;
    height: auto;
    max-height: 72vh;
    overflow-y: auto;
    overflow-x: hidden;
  }
  .spread-page {
    overflow: visible;        /* FIX: 单列时两页各自滚动会互相压住 */
    padding: 1.4rem 1.2rem;
  }
  .folder-spine-crease {
    display: none;
  }
  .folder-index-tab,
  .spread-index-tab {
    display: none;            /* FIX: 右侧索引标签在窄屏会被切掉 */
  }
  .dossier-stage {
    top: 50%;
  }

  .projects-grid {
    grid-template-columns: 1fr;
  }
  .principles-grid {
    grid-template-columns: 1fr;   /* FIX: 三列在窄屏挤成一团 */
  }

}

/* --- 手机 --- */
@media (max-width: 560px) {
  /* 顶部栏：原布局在 390px 就把 Lo-Fi 按钮整块挤出屏幕（实测 x=388..471） */
  .site-header {
    padding: 0 0.55rem;
    gap: 0.35rem;
  }
  .header-left {
    flex: 0 0 auto;
  }
  .header-nav {
    flex: 1 1 auto;
    min-width: 0;
    justify-content: center;
    gap: 0.1rem;
    padding: 0.15rem 0.25rem;
  }
  .nav-item {
    padding: 0.28rem 0.4rem;
    min-width: 0;
  }
  .nav-item .nav-zh {
    font-size: 0.76rem;
    letter-spacing: 0.02em;
  }
  .nav-item .nav-en {
    display: none;
  }
  .header-actions {
    flex: 0 0 auto;
    gap: 0.3rem;
  }
  .action-btn {
    padding: 0.4rem 0.55rem;
  }
  .action-btn .audio-label {
    display: none;
  }

  .hero-headline {
    font-size: 2.05rem;
  }
  .hero-domains {
    font-size: 1rem;
  }
  .hero-tagline {
    font-size: 0.88rem;
  }
  .editorial-btn {
    padding: 0.65rem 1.1rem;
    font-size: 0.8rem;
  }

  /* 档案袋封面：420px 固定宽在 360px 屏上左右各被切掉 30px */
  .folder-cover-closed {
    width: min(420px, 88vw);
    height: min(560px, 58vh);
    min-height: 360px;
  }
  .folder-cover-paper {
    padding: 2rem 1.4rem 1.6rem;
  }
  .paper-label-main {
    padding: 1.2rem 1rem;
  }
  .label-header {
    font-size: 1.2rem;
  }
  .label-subtitle {
    font-size: 0.72rem;
    margin-bottom: 0.6rem;
  }
  .label-meta {
    font-size: 0.68rem;
  }
  .red-stamp-box {
    font-size: 0.85rem;
    padding: 0.45rem 1rem;
    margin: 0.9rem auto;
  }
  /* 注：这一档原本还有 .open-instruction-tag 的缩字号规则，以及为它让位而加的
     .folder-cover-paper { padding-bottom }。封面提示条按用户要求去掉后，两条一并删除。 */
  .paperclip-note {
    bottom: 0.7rem;
    left: 0.9rem;
  }
  .dossier-stage {
    top: 50%;
  }

  /* 联系面板：AIR MAIL 邮戳压住标题；两列表单让邮箱输入框跑出屏幕 */
  .panel-inner {
    padding: 1.7rem 1.25rem;
  }
  .panel-stamp {
    position: static;
    display: inline-block;
    transform: rotate(-2deg);
    margin-bottom: 0.7rem;
  }
  .panel-title {
    padding-right: 0;
    font-size: 1.5rem;
  }
  .panel-subtitle {
    font-size: 0.82rem;
  }
  .form-row {
    grid-template-columns: 1fr;
    gap: 0.85rem;
  }
  .channel-chip {
    max-width: 100%;
  }
  .chip-text {
    overflow-wrap: anywhere;
  }
  .copy-tag {
    white-space: nowrap;
    flex-shrink: 0;
  }
  .qr-pattern {
    width: 140px;
    height: 140px;
  }

  /* 注：这里原本有一组针对「视口百分比定位」的热区覆盖
     （.hotspot-laptop / .lamp-light-cone / .cottage-hotspot），
     作用是「窄屏下别让热区越出右边界」。现在这些热区全部改成按插画像素百分比定位、
     跟着插画走，再覆盖 left/right/width 就会把它们从插画上拽走 ——
     于是整组删掉。窄屏要靠「推镜头」（见下面的 .home-art-layer / .coffee-art）解决。 */
  /* 小屋在窄屏贴近右边缘，195px 宽的 nowrap 提示会越出屏幕 18px。
     但改成换行后，行数随视口宽度变化、高度不再固定，用固定 top 偏移就会
     把提示条底部压进热区内部的 🏠 图标（实测 390px 重叠 28%、360px 48%）。
     这里改成锚定在热区上边缘之上：无论提示条折成几行，都不会碰到热区内部。
     另：left:50% 会让可用宽度只剩容器的右半（128-64=64px），shrink-to-fit
     于是把提示条压成 64px 的竖柱、文字逐字换行；width:max-content 取自然宽度，
     再由 max-width 收口。 */
  .cottage-tooltip {
    top: auto;
    bottom: calc(100% + 6px);
    width: max-content;
    max-width: 150px;
    padding: 5px 9px;
  }
  .tooltip-main,
  .tooltip-sub {
    white-space: normal;
    text-align: center;
  }
  .tooltip-main {
    font-size: 0.72rem;
  }
  .tooltip-sub {
    font-size: 0.6rem;
    line-height: 1.35;
  }

  .modal-header {
    padding: 1.1rem 1.2rem;
  }
  .modal-title {
    font-size: 1.15rem;
  }
  .projects-grid {
    padding: 1rem;
    gap: 1rem;
  }
  .card-cover-media {
    height: 118px;
  }
  .notebook-content-body {
    padding: 1.2rem;
  }
  .sketch-paper {
    padding: 1.2rem 1.1rem;
  }
  .photo-gallery-body {
    padding: 1rem;
  }
  .editorial-toast {
    right: 1rem;
    bottom: 1rem;
    font-size: 0.74rem;
    padding: 0.6rem 1rem;
  }
  .contact-editorial-panel {
    width: 94vw;
  }
}

/* --- 小手机 --- */
@media (max-width: 400px) {
  .hero-headline {
    font-size: 1.85rem;
  }
  .nav-item .nav-zh {
    font-size: 0.72rem;
  }
  .nav-item {
    padding: 0.25rem 0.32rem;
  }
  .folder-spread-opened {
    max-height: 70vh;
  }
}

/* ================= REDUCED MOTION ================= */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* ================= 内容覆盖层（后台可改文字）=================
   index.html 的 <head> 里有一行内联脚本会给 <html> 打上 data-tsync，
   这里就把所有可编辑文字先藏起来，等 content.js 拿到后台改过的内容再放出来。
   不做这一步的话，回访者会先看到 HTML 原文、再跳成改过的文 —— 一次明显的闪字。

   ★ 兜底那行 animation 很关键：万一 content.js 加载失败或被拦，
     1.1 秒后文字也会自己显示出来，不会让整站空着。 */
[data-t] {
  transition: opacity 0.28s ease;
}
html[data-tsync] [data-t] {
  opacity: 0;
  animation: tsync-show 0s linear 1.1s forwards;
}
@keyframes tsync-show {
  to { opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  [data-t] { transition: none; }
}
