/* ============================================================
 * 日常小镇 · 样式
 * 移动优先；暖色系延续站内体系，但整体更明亮柔和
 * ============================================================ */

:root {
  --lt-grass:    #a8d98a;
  --lt-grass-2:  #9acf7b;
  --lt-grass-3:  #8ec46f;
  --lt-road:     #efe0bd;
  --lt-road-2:   #d9c39a;
  --lt-wall:     #fdf1e0;
  --lt-wood:     #c9955f;
  --lt-ink:      #4a4033;
  --lt-ink-2:    #6b5d4a;
  --lt-sky:      #cfe8f5;
  --lt-panel:    #fffaf2;
  --lt-panel-2:  #f6ead8;
  --lt-line:     #e2d3bd;
  --lt-accent:   #e07a5f;
  --lt-accent-2: #7fb069;
  --lt-gold:     #f2b134;
  --lt-text:     #4a4033;
  --lt-muted:    #8a7a66;
  --lt-shadow:   0 10px 26px rgba(74, 64, 51, .16);
  --lt-radius:   16px;
  --lt-radius-sm:10px;
  --lt-top-h:    54px;
  --lt-dock-h:   62px;
  --lt-font: -apple-system, BlinkMacSystemFont, "PingFang SC", "Noto Sans CJK SC",
             "Microsoft YaHei", system-ui, sans-serif;
  /* 数字/拉丁专用（对齐站点主 style.css 的 --font-display） */
  --lt-num: "Poppins", -apple-system, BlinkMacSystemFont, "PingFang SC", system-ui, sans-serif;
}

/* ---------- 字体（2026-10-19：接回站点既有的字体体系） ----------
 * 站点主 style.css 已定义 --font-display: Poppins / --font-body: Lora，
 * 字体文件在 assets/fonts/。这里只引 600 与 700 两档 —— 800 是站点 hero 专用，
 * 游戏里没有用到（浏览器实测没加载），但静态闭包会把 @font-face 的 url() 算进首屏，
 * 留着就是白担 7.8KB。
 * ⚠️ 它们的 unicode-range 只覆盖拉丁字符（U+0000-00FF 等），【不覆盖中文】——
 * 所以这里只用来做【数字与拉丁文】的排版（金币数、0/2、百分比、日期），
 * 中文继续走系统字体（PingFang / 微软雅黑），这是正确的分工不是妥协：
 * 中文点阵字重更全、也更省字节。
 * 相对路径：games/lifetown/css/ -> ../../../assets/fonts/ */
@font-face {
  font-family: 'Poppins'; font-style: normal; font-weight: 600; font-display: swap;
  src: url('../../../assets/fonts/poppins-600.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Poppins'; font-style: normal; font-weight: 700; font-display: swap;
  src: url('../../../assets/fonts/poppins-700.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
* { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }

/* 排版分层
   —— 站内传统是零外部资源（不引 CDN、不引字体文件），所以 display 层不靠换字体，
      而靠字重 + 字距 + 字号做出明确对比：display 更重更紧，正文更松更轻。 */
.lt-brand b, .lt-sheet-head h2, .lt-stat b, .lt-empty b, .lt-timer .lt-clock {
  font-weight: 800;
  letter-spacing: -0.015em;
}
.lt-task-title, .lt-house-label, .lt-dock-btn b { letter-spacing: -0.005em; }
body { letter-spacing: 0.01em; }
.lt-stat b, .lt-timer .lt-clock, .lt-pill b, .lt-badge, .lt-prog b {
  font-family: var(--lt-num);
  font-variant-numeric: tabular-nums;
}

html, body {
  margin: 0; padding: 0; height: 100%;
  overflow: hidden;
  background: var(--lt-sky);
  color: var(--lt-text);
  font-family: var(--lt-font);
  font-size: 15px;
  line-height: 1.6;
  overscroll-behavior: none;
}

body { display: flex; flex-direction: column; }

button { font: inherit; color: inherit; cursor: pointer; border: 0; background: none; }

/* ---------------- 顶部栏 ---------------- */
.lt-top {
  flex: 0 0 auto;
  height: var(--lt-top-h);
  display: flex; align-items: center; gap: 10px;
  padding: 0 12px;
  /* 🔴 界面外壳升级（2026-10-19 用户反馈"界面过于简陋"）：
     顶栏原来是一块纯平色 + 一条 2px 边线，像工具条不像游戏。
     改成"暖纸渐变 + 底部柔和投影 + 内高光"，
     让它像一块立起来的木质告示牌，和游戏世界的手绘风一致。 */
  background: linear-gradient(180deg, #fffdf7 0%, #f7efdd 78%, #efe3c9 100%);
  border-bottom: 2px solid var(--lt-line);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,.85),
    0 2px 6px -2px rgba(74,64,51,.22),
    0 6px 14px -8px rgba(74,64,51,.14);
  position: relative; z-index: 30;
}
/* 顶栏底部加一条"木沿"高光，弱化硬边线的工具感 */
.lt-top::after {
  content: ''; position: absolute; left: 0; right: 0; bottom: -2px; height: 2px;
  background: linear-gradient(90deg,
    rgba(255,255,255,0), rgba(255,255,255,.55) 18%, rgba(255,255,255,.55) 82%, rgba(255,255,255,0));
  pointer-events: none;
}
.lt-brand { display: flex; align-items: center; gap: 7px; font-size: 16px; white-space: nowrap; }
.lt-brand b { letter-spacing: .02em; }
.lt-logo { display: flex; }

.lt-stats { display: flex; gap: 6px; margin-left: auto; }
.lt-pill {
  display: inline-flex; align-items: center; gap: 3px;
  background: linear-gradient(180deg, #ffffff, #f6efdf);
  border: 1.5px solid var(--lt-line);
  border-radius: 999px;
  padding: 3px 9px;
  font-size: 12.5px;
  white-space: nowrap;
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,.9),
    0 1.5px 3px -1px rgba(74,64,51,.18);
}
.lt-pill i { font-style: normal; font-size: 12px; }
.lt-pill b { font-variant-numeric: tabular-nums; }
.lt-pill em { font-style: normal; color: var(--lt-muted); font-size: 11px; }

.lt-tools { display: flex; gap: 4px; }
/* 🔴 2026-10-19 界面优化：这四个图标原来是【裸灰线】
   （border 透明 + 无背景），读起来像"装饰符号"而不是"可以点的按钮"。
   给它们和 pill 一致的底衬（微渐变 + 描边 + 内高光 + 落影），
   用户一眼就知道"这四个能点"。 */
.lt-ico {
  width: 34px; height: 34px; border-radius: 10px;
  display: grid; place-items: center;
  color: var(--lt-ink-2);
  background: linear-gradient(180deg, rgba(255,255,255,.9), rgba(246,239,223,.75));
  border: 1.5px solid var(--lt-line);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,.85),
    0 1.5px 3px -1px rgba(74,64,51,.16);
  transition: background .15s, border-color .15s, transform .12s, box-shadow .15s;
}
.lt-ico:active {
  transform: translateY(1px);
  box-shadow: inset 0 1.5px 3px rgba(74,64,51,.16);
}
/* ⚠️ 2026-10-19 记录：我一度想把它放大到 40x40 解决"点不准"，
   但 460px 以下的媒体查询早已用 ::after 把热区撑到 44x44
   （见下方 @media 块）——视觉尺寸不是触摸尺寸。
   改 base 反而会让宽屏变成 40px 视觉 + 40px 热区、窄屏仍是 30px，
   两套不一致。所以恢复原样，热区方案已足够。 */
.lt-ico:hover, .lt-ico:focus-visible { background: var(--lt-panel-2); border-color: var(--lt-line); }
.lt-ico:active { transform: scale(.94); }

/* 窄屏：顶栏必须能完整放下三个按钮 + 三个数据胶囊 */
@media (max-width: 430px) {
  .lt-top { gap: 5px; padding: 0 8px; }
  .lt-brand b { font-size: 15px; }
  .lt-pill { padding: 2px 6px; font-size: 12px; gap: 2px; }
  .lt-pill i svg { width: 13px; height: 13px; }
  .lt-pill em { display: none; }        /* 顶栏加语音按钮后，为它让出空间 */
  /* 🔴 2026-10-19 手感修复：460px 以下的手机把顶栏图标压到 28x28 ——
     拇指根本点不准（实测 7 个图标全是 28px）。
     视觉仍保持 30px（窄屏本来就该紧凑），但用 ::after 把热区撑到 44px。
     ⚠️ 不能用负 margin（会把相邻图标挤进来压住热区，之前踩过）——
        用伪元素绝对定位即可，它不参与布局。 */
  .lt-ico { width: 30px; height: 30px; position: relative; }
  .lt-ico::after {
    content: ''; position: absolute; left: 50%; top: 50%;
    width: 44px; height: 44px; transform: translate(-50%, -50%);
  }
  .lt-tools { gap: 1px; }
}
@media (max-width: 375px) {
  .lt-brand b { display: none; }
}

/* ---------------- 小镇视图 ---------------- */
.lt-world-wrap {
  flex: 1 1 auto;
  position: relative;
  overflow: hidden;
  touch-action: manipulation;
  /* 🔴 2026-10-19：这里原来是一块纯色 var(--lt-grass)。世界本体是 820x620，
     竖屏视口高 728 ⇒ 世界之外会露出一条纯色底；而新地面是"天光渐变"，
     两者接起来就是一条硬分界（实测截图里非常明显）。
     现在 wrap 用【与地面同源的渐变】打底：天光从偏上打下来、向下渐冷，
     世界边缘自然融进去，不再有缝。
     ⚠️ 世界比视口小时才有缝；但横屏/大屏也会遇到，所以按同源处理而不是特例补丁。 */
  /* 2.5D：世界之外是"虚空"（天与水），不再是草地绿。
     用与 island 虚空同源的浅蓝渐变 —— 否则视口边缘会露出一块绿色，
     看起来像"岛屿没铺满"的 bug（截图实证过左上角那块绿）。 */
  background: linear-gradient(180deg, #d6ecf7 0%, #c2e2f2 55%, #a9d4ea 100%);
}
.lt-world {
  position: absolute; top: 0; left: 0;
  transform-origin: 0 0;
  will-change: transform;
}
.lt-world.lt-cam-anim { transition: transform .55s cubic-bezier(.4, .1, .2, 1); }

/* ============================================================================
 * 世界的"生命"（2026-10-19 加，去 demo 味的关键）
 * ----------------------------------------------------------------------------
 * 用户："别有 demo 味道"。原先整个世界是【完全静止】的 —— 树不摇、没有鸟、
 * 没有蝴蝶、烟囱不冒烟，只有小人在动。静止的世界是最强的"demo 感"信号：
 * 它说明这是张摆好的图，不是个活着的地方。
 * 这里加四样，全部是 CSS 动画（零 JS 开销、零新增网络资源）：
 *   ① 树/灌木随风轻摆（每棵相位不同，不然整齐摆动反而更假）
 *   ② 蝴蝶：绕着固定轨迹飘，带翅膀开合
 *   ③ 飞鸟：远处横穿天空
 *   ④ 炊烟：从烟囱升起淡出
 * ⚠️ prefers-reduced-motion 已在文件末统一关掉全部动画（那一节覆盖这里）。
 * ========================================================================== */

/* ① 风摆：树与灌木。transform-origin 在根部（50% 100%）才不会整棵平移 */
.lt-tree, .lt-bush {
  transform-origin: 50% 100%;
  animation: lt-sway 5.6s ease-in-out infinite;
}
.lt-bush { animation-duration: 4.4s; animation-direction: alternate-reverse; }
@keyframes lt-sway {
  0%, 100% { transform: rotate(-1.6deg); }
  50%      { transform: rotate(1.8deg); }
}

/* ② 蝴蝶：一条 8 字形小轨迹 + 翅膀开合 */
.lt-butterfly { position: absolute; width: 14px; height: 12px; pointer-events: none; }
.lt-butterfly .lt-bf-wing {
  position: absolute; top: 0; width: 7px; height: 11px;
  border-radius: 60% 40% 50% 50%;
  background: linear-gradient(160deg, #ffe9a8, #ffb84d);
  border: 1.5px solid var(--lt-ink);
  transform-origin: bottom center;
}
.lt-butterfly .lt-bf-wing.l { left: 0; animation: lt-flap-l .32s ease-in-out infinite alternate; }
.lt-butterfly .lt-bf-wing.r { right: 0; animation: lt-flap-r .32s ease-in-out infinite alternate; }
@keyframes lt-flap-l { from { transform: rotateY(0deg) skewY(-6deg); } to { transform: rotateY(62deg) skewY(-14deg); } }
@keyframes lt-flap-r { from { transform: rotateY(0deg) skewY(6deg); }  to { transform: rotateY(-62deg) skewY(14deg); } }
.lt-butterfly { animation: lt-flit 9s ease-in-out infinite; }
@keyframes lt-flit {
  0%   { transform: translate(0, 0) rotate(0deg); }
  25%  { transform: translate(22px, -14px) rotate(8deg); }
  50%  { transform: translate(44px, 3px) rotate(-6deg); }
  75%  { transform: translate(20px, 16px) rotate(10deg); }
  100% { transform: translate(0, 0) rotate(0deg); }
}

/* ③ 飞鸟：远处横穿，只画剪影（远 = 无细节，这也是纵深线索） */
.lt-bird { position: absolute; pointer-events: none; animation: lt-fly 26s linear infinite; }
.lt-bird svg { display: block; }
@keyframes lt-fly {
  0%   { transform: translate(-60px, 0); opacity: 0; }
  8%   { opacity: .55; }
  88%  { opacity: .55; }
  100% { transform: translate(120px, -22px); opacity: 0; }
}

/* ④ 炊烟：从烟囱升起、边升边淡、微微右飘 */
.lt-smoke { position: absolute; pointer-events: none; }
.lt-smoke i {
  position: absolute; left: 0; bottom: 0;
  width: 7px; height: 7px; border-radius: 50%;
  background: rgba(255,255,255,.85);
  border: 1.5px solid rgba(59,48,37,.16);
  animation: lt-puff 4.2s ease-out infinite;
}
.lt-smoke i:nth-child(2) { animation-delay: 1.4s; }
.lt-smoke i:nth-child(3) { animation-delay: 2.8s; }
@keyframes lt-puff {
  0%   { transform: translate(0, 0) scale(.5); opacity: 0; }
  18%  { opacity: .75; }
  100% { transform: translate(13px, -40px) scale(1.9); opacity: 0; }
}

/* ---------- 地面（2026-10-19 重做：去棋盘格，改天光渐变 + 草叶笔触 + 暗角） ----------
   旧版是 40px 两色棋盘格铺满，逐像素实测两种绿占 68.7% 画面、
   明度 90.3% 落亮部 —— 读起来"平、廉价"。现在地面是一整张 SVG（3 个节点），
   分层：天光径向渐变定光向 → 线性渐变分受光/背光 → 草叶笔触 → 四周暗角。 */
/* ⚠️ 不能用 inset:0 —— 地面要画到【世界之外】才能盖住视口（见 town.js 的 PAD 注释），
   所以由 JS 给 left/top/width/height，CSS 只负责定位原点。 */
.lt-ground { position: absolute; left: 0; top: 0; }
.lt-ground-svg { position: absolute; inset: 0; display: block; }
.lt-tile { position: absolute; }   /* 兼容旧标记（现已不用） */

/* 环境光遮蔽（AO）：所有落地物的脚下柔影。
   🔴 旧版只有 .lt-house 的 drop-shadow(0 3px 0 rgba(...,.10)) —— 那是一层硬边偏移，
   读起来像"贴纸"而不是"站在地上"。AO 用两层：贴地窄影（接触阴影）+ 外扩柔影（环境遮挡）。 */
/* 🔴 2026-10-19 第二轮（用户："尽可能优化到质感无敌"）：
   旧版 AO 只有 bottom:-2px、宽 62%、单层渐变 —— 实测在 3x 截图里几乎看不见，
   所有东西都像"贴纸"，没有"站在地上"的重量感。
   现在改成【两层 AO】，这是写实/卡通都能立刻提质感的标准做法：
     ::before = 接触阴影（contact shadow）：贴着物体底边的一道窄而深的影子，
                负责"东西和地面接触上了"这个信息
     ::after  = 环境遮挡（ambient occlusion）：更宽、更淡的外扩椭圆，
                负责"这一块地面被物体压暗了"的整体感
   两层叠加才读得出"体积落在平面上"，单层永远像贴纸。 */
.lt-obj::before,
.lt-obj::after {
  content: '';
  position: absolute; left: 50%;
  border-radius: 50%;
  pointer-events: none;
  transform: translateX(-50%);
}
.lt-obj::before {          /* 接触阴影：窄、深、贴底 */
  bottom: 0;
  width: 52%; height: 7px;
  background: radial-gradient(ellipse at center,
              rgba(30,46,28,.42) 0%, rgba(30,46,28,.22) 55%, rgba(30,46,28,0) 78%);
}
.lt-obj::after {           /* 环境遮挡：宽、淡、外扩 */
  bottom: -5px;
  width: 86%; height: 20px;
  background: radial-gradient(ellipse at center,
              rgba(30,46,28,.20) 0%, rgba(30,46,28,.11) 48%, rgba(30,46,28,0) 72%);
}

/* 房屋 / 工作台 / 装饰 / 道具：统一用绝对定位 */
.lt-obj { position: absolute; transform-origin: 50% 100%; }
.lt-house { filter: drop-shadow(0 2px 1px rgba(40,58,40,.16)) drop-shadow(0 8px 14px rgba(40,58,40,.10)); transition: transform .18s; }
.lt-house-hit {
  position: absolute; cursor: pointer;
  border-radius: 12px;
  transition: transform .18s, filter .18s;
}
.lt-house-hit:active { transform: scale(.97); }
.lt-house-hit.lt-locked { cursor: not-allowed; filter: grayscale(.75) brightness(1.04); opacity: .72; }
.lt-house-label {
  position: absolute; left: 50%; transform: translateX(-50%);
  background: rgba(255, 250, 242, .94);
  border: 1.5px solid var(--lt-line);
  border-radius: 999px;
  padding: 1px 9px; font-size: 11.5px; white-space: nowrap;
  color: var(--lt-ink-2);
  pointer-events: none;
}
.lt-house-label b { color: var(--lt-text); }
.lt-pct { color: var(--lt-accent); font-weight: 700; font-size: 10.5px; }
.lt-lock-badge {
  position: absolute; left: 50%; top: -6px; transform: translateX(-50%);
  background: var(--lt-ink); color: #fff;
  font-size: 10.5px; padding: 2px 8px; border-radius: 999px; white-space: nowrap;
}

/* 建设度气泡（房子右上角） */
.lt-prog {
  position: absolute; left: 50%; transform: translateX(-50%);
  width: 62px; height: 7px; border-radius: 999px;
  background: rgba(255, 255, 255, .72);
  border: 1.5px solid var(--lt-ink);
  overflow: hidden; pointer-events: none;
}
.lt-prog i { display: block; height: 100%; background: var(--lt-accent); transition: width .5s cubic-bezier(.3, 1.2, .5, 1); }

/* 道路 */
.lt-roads { position: absolute; inset: 0; pointer-events: none; }
/* 装饰层：不吃点击，避免挡住房屋热区 */
.lt-decor { position: absolute; inset: 0; pointer-events: none; }
/* 生命层：同上。单独成层是为了让 CSS 动画不被 decor 重绘打断 */
.lt-life { position: absolute; inset: 0; pointer-events: none; }

/* 小人 */
.lt-char-wrap {
  position: absolute; transform-origin: 50% 100%;
  transition: transform .1s linear;
  pointer-events: none;
  /* ⚠️ z-index 由 JS 按 depthOf(脚点) 动态设置（见 town.js 的 placeChar）。
     不要在这里写死 —— 玩家现在和房子/树同处 .lt-objs 里，
     写死一个常数会让它永远压在所有房子之上（或之下）。 */
}
.lt-char-wrap svg { display: block; width: 38px; height: auto; }
/* 🔴 2026-10-19 修：.lt-char-wrap 本身是 pointer-events:none，
   但里面的 SVG（class="lt-char"）默认还是 auto ⇒
   玩家站到房子前面时会把房屋热区挡住（实测 school 热区被 lt-char 挡）。
   玩家是"自己在动"的对象，永远不该抢点击。 */
/* ⚠️ 只作用于【玩家】的 .lt-char-wrap 内部。
   我第一版写成裸 `.lt-char` —— 而 NPC 的 SVG 也带 class="lt-char"，
   于是一起被设成 none，32 个 NPC 全部点不动（实测 svgPE=none）。
   必须用后代选择器限定在玩家容器里。 */
.lt-char-wrap, .lt-char-wrap svg, .lt-char-wrap .lt-char { pointer-events: none !important; }
.lt-char-shadow { position: absolute; left: 50%; bottom: -3px; transform: translateX(-50%); }

/* 交互提示气泡 */
/* 🔴 界面外壳升级：气泡原来是"白底+黑边"的平面框。
   改成暖纸渐变 + 内高光 + 双层落影（近影锐、远影柔），
   读起来像一张立在小镇前面的说明卡。 */
.lt-bubble {
  position: absolute; left: 50%; transform: translateX(-50%);
  bottom: calc(var(--lt-dock-h) + 14px);
  max-width: min(86vw, 420px);
  background: linear-gradient(180deg, #fffdf6, #faf2e0);
  border: 2px solid var(--lt-ink);
  border-radius: 16px;
  padding: 10px 15px;
  font-size: 13.5px; line-height: 1.55;
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,.9),
    0 3px 6px -2px rgba(74,64,51,.28),
    0 10px 22px -10px rgba(74,64,51,.22);
  z-index: 25;
  animation: lt-pop .3s cubic-bezier(.3, 1.4, .5, 1);
}
.lt-bubble::after {
  content: ''; position: absolute; left: 50%; bottom: -8px; transform: translateX(-50%) rotate(45deg);
  width: 12px; height: 12px; background: rgba(255, 250, 242, .97);
  border-right: 2px solid var(--lt-ink); border-bottom: 2px solid var(--lt-ink);
}
.lt-bubble b { color: var(--lt-accent); }

.lt-hint {
  position: absolute; left: 50%; bottom: 12px; transform: translateX(-50%);
  background: rgba(74, 64, 51, .74); color: #fff;
  padding: 5px 13px; border-radius: 999px; font-size: 12px;
  white-space: nowrap; pointer-events: none;
  transition: opacity .4s;
  max-width: 92vw; overflow: hidden; text-overflow: ellipsis;
}
/* 🔴 修【重叠】：气泡和提示条都占底部（bubble: dock+14px，hint: 12px），
   气泡一出现就压住提示条，用户看到两行字叠在一起（截图实证）。
   ⇒ 有气泡时提示条自动让位淡出（气泡是当前对话，优先级更高）；
     气泡消失后由 JS 移除 lt-away，提示条回来。
   ⚠️ 用相邻兄弟选择器的前提是 DOM 里 hint 在 bubble 后面 ——
     实测 index.html 中 #ltHint 在 .lt-world-wrap 外、bubble 在内，
     所以不用兄弟选择器，改成由 showBubble/hideBubble 配合加类。 */
.lt-hint.lt-away { opacity: 0; transform: translateX(-50%) translateY(6px); }
.lt-hint.lt-gone { opacity: 0; }

/* ---------------- 底部快捷栏 ---------------- */
.lt-dock {
  flex: 0 0 auto;
  height: var(--lt-dock-h);
  display: flex; align-items: center; gap: 8px;
  padding: 0 10px;
  /* 🔴 界面外壳升级：底栏原来是纯平色块，和顶栏一样"工具条"。
     改成暖纸渐变 + 顶部内高光 + 上方柔和投影，
     读起来像一块托住小镇的木托盘。 */
  background: linear-gradient(180deg, #f4ead2 0%, #fbf5e6 30%, #fffdf7 100%);
  border-top: 2px solid var(--lt-line);
  box-shadow:
    inset 0 1.5px 0 rgba(255,255,255,.75),
    0 -2px 8px -3px rgba(74,64,51,.18);
  overflow-x: auto; overflow-y: hidden;
  scrollbar-width: none;
  position: relative; z-index: 30;
}
.lt-dock::-webkit-scrollbar { display: none; }
.lt-dock-btn {
  /* 🔴 2026-10-19 UI（用户："这个 UI 太丑陋了"）：
     原来 min-width:62px + flex:0 0 auto ⇒ 五个按钮宽度【各不相等】
     （"家"字少就窄、'图书馆'字多就宽），一排看过去参差不齐。
     改成 flex:1 1 0 + 相同 min-width ⇒ 五格等宽，像真正的导航栏。 */
  flex: 1 1 0;
  display: flex; flex-direction: column; align-items: center; gap: 1px;
  min-width: 0; padding: 6px 2px;
  border-radius: 12px;
  border: 1.5px solid var(--lt-line);
  /* 按钮加"微微抬起"的立体：上亮下暗 + 落影，按下去会塌下去 */
  background: linear-gradient(180deg, #ffffff, #f3ead6);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,.85),
    0 2px 4px -1.5px rgba(74,64,51,.22);
  font-size: 11.5px; line-height: 1.2;
  transition: transform .14s, background .14s, border-color .14s, box-shadow .14s;
}
/* 名字统一字号并单行截断（'图书馆'在等宽格子里要能放下） */
.lt-dock-btn b {
  font-size: 12.5px; line-height: 1.15;
  max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.lt-dock-btn:active {
  transform: scale(.94) translateY(1px);
  box-shadow: inset 0 1.5px 3px rgba(74,64,51,.18), 0 0 0 rgba(0,0,0,0);
}
/* 选中态：绿色高光 + 抬得更高（原来是只换底色，区分度太弱） */
.lt-dock-btn.lt-on {
  background: linear-gradient(180deg, #f2f9e8, #e3f2d2);
  border-color: #7fb069;
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,.9),
    0 3px 7px -2px rgba(127,176,105,.45),
    0 0 0 1px rgba(127,176,105,.25);
}
.lt-dock-btn.lt-on { background: #eaf6df; border-color: var(--lt-accent-2); }
.lt-dock-btn.lt-locked { opacity: .55; }
/* 🔴 2026-10-19 UI：图标尺寸原来靠 SVG 自带 width/height，
   五个图标大小不一（'家'的 SVG 偏大）⇒ 挤得文字位置和字号都不一致。
   这里强制统一，让五格的"图标-名字-百分比"三行完全对齐。 */
.lt-dock-btn .lt-dot,
.lt-dock-btn svg { font-size: 15px; line-height: 1; }
.lt-dock-btn > svg,
.lt-dock-btn .lt-dot > svg { width: 22px; height: 22px; display: block; }
/* 图标行统一高度：不管 SVG 原来多高，占位一致 */
.lt-dock-btn .lt-dot { height: 22px; display: grid; place-items: center; }
/* 建设度百分比：比"还没有任务"有信息量，用弱强调色不抢徽章的风头 */
/* 百分比：原来 #8a7a5c 和名字同色系，读起来糊成一片。
   改成偏绿的强调色 + 小一号 + 加字距，让它读起来是"数据"而不是"副标题"。 */
.lt-dock-btn .lt-dock-pct {
  color: #6f8f5a; font-weight: 800; font-size: 11px;
  letter-spacing: .02em; font-variant-numeric: tabular-nums;
}
.lt-dock-btn .lt-badge {
  position: absolute; margin-top: -34px; margin-left: 26px;
  background: var(--lt-accent); color: #fff;
  font-size: 10px; padding: 0 5px; border-radius: 999px; min-width: 16px; text-align: center;
}

/* ---------------- 浮层面板 ---------------- */
.lt-sheet { position: fixed; inset: 0; z-index: 100; display: flex; align-items: flex-end; }
.lt-sheet[hidden] { display: none; }
.lt-sheet-backdrop { position: absolute; inset: 0; background: rgba(40, 34, 26, .42); animation: lt-fade .2s; }
.lt-sheet-body {
  position: relative; width: 100%;
  max-height: 88vh; overflow-y: auto;
  background: var(--lt-panel);
  border-radius: 22px 22px 0 0;
  border-top: 2.5px solid var(--lt-ink);
  /* 顶部内高光：模拟"纸从桌面抬起"的受光边，比纯平面多一层体积 */
  box-shadow: inset 0 1px 0 rgba(255,255,255,.9), 0 -8px 28px -12px rgba(74,64,51,.18);
  padding: 0 0 calc(20px + env(safe-area-inset-bottom));
  animation: lt-slide .28s cubic-bezier(.25, .9, .3, 1);
  -webkit-overflow-scrolling: touch;
}

.lt-sheet-head {
  position: sticky; top: 0; z-index: 2;
  display: flex; align-items: center; gap: 10px;
  padding: 14px 16px 12px;
  background: var(--lt-panel);
  border-bottom: 2px solid var(--lt-line);
}
.lt-sheet-head h2 { margin: 0; font-size: 17px; }
.lt-sheet-head .lt-sub { font-size: 12px; color: var(--lt-muted); }
.lt-sheet-head .lt-spacer { flex: 1; }
.lt-x {
  position: relative;
  width: 32px; height: 32px; border-radius: 9px; display: grid; place-items: center;
  background: var(--lt-panel-2); border: 1.5px solid var(--lt-line); font-size: 17px; line-height: 1;
  transition: background .15s, transform .12s;
}
/* 同 .lt-check：视觉 32px、热区 44px（实测这类按钮是第二大的"点不准"来源） */
.lt-x::before {
  content: ''; position: absolute; left: 50%; top: 50%;
  width: 44px; height: 44px; transform: translate(-50%, -50%);
}
.lt-x:active { transform: scale(.9); background: var(--lt-line); }

.lt-sheet-body > *:not(.lt-sheet-head) { margin-left: 16px; margin-right: 16px; }
.lt-sec { margin-top: 16px; }
.lt-sec-title {
  display: flex; align-items: center; gap: 8px;
  font-size: 12.5px; color: var(--lt-muted); font-weight: 700; letter-spacing: .06em;
  margin: 18px 16px 8px;
}
.lt-sec-title::after { content: ''; flex: 1; height: 1px; background: var(--lt-line); }

/* 建设度条 */
/* 🔴 2026-10-19 界面优化：建设度是这个游戏最核心的指标，
   但 0% 时进度条里是一片空白 —— 看不出"它有刻度、有目标"。
   加【升档刻度】：20/40/60/80% 各一条细线，正对应房子长高的四个节点。
   这样即使是空的，也一眼能看出"要走到那几个点"。 */
.lt-bar {
  position: relative;
  height: 14px; border-radius: 999px; overflow: hidden;
  background: linear-gradient(180deg, #f0e8d6, #faf5ea);
  border: 2px solid var(--lt-ink);
  margin: 10px 0 4px;
  box-shadow: inset 0 1.5px 3px rgba(74,64,51,.14);
}
/* 刻度线：用重复渐变实现，不新增 DOM 节点 */
.lt-bar::after {
  content: ''; position: absolute; inset: 0;
  pointer-events: none;
  background-image: repeating-linear-gradient(90deg,
    transparent 0, transparent calc(20% - 1px),
    rgba(74,64,51,.22) calc(20% - 1px), rgba(74,64,51,.22) 20%);
}
.lt-bar i {
  display: block; height: 100%;
  background: linear-gradient(90deg, var(--lt-accent-2), #a8d98a);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.5);
  transition: width .6s cubic-bezier(.3, 1.1, .4, 1);
}
.lt-bar-note { font-size: 12px; color: var(--lt-muted); }
.lt-bar-note b { color: var(--lt-text); font-variant-numeric: tabular-nums; }

/* ---------------- 按钮 ---------------- */
.lt-btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  padding: 10px 16px; border-radius: 12px;
  border: 2px solid var(--lt-ink);
  background: var(--lt-panel);
  font-size: 14px; font-weight: 600;
  box-shadow: 0 2px 0 var(--lt-ink);
  transition: transform .12s, box-shadow .12s, background .12s;
}
.lt-btn:active { transform: translateY(2px); box-shadow: 0 0 0 var(--lt-ink); }
.lt-btn.lt-primary { background: var(--lt-accent-2); color: #fff; }
.lt-btn.lt-warm { background: var(--lt-accent); color: #fff; }
.lt-btn.lt-ghost { box-shadow: none; border-width: 1.5px; border-color: var(--lt-line); background: var(--lt-panel-2); font-weight: 500; }
.lt-btn.lt-ghost:active { transform: scale(.97); }
/* 禁用态：透明度从 .5 提到 .62（配合外层不再叠乘），并加一个浅底
   —— 让"暂时买不起"读起来是"等你有钱"，而不是"按钮坏了" */
.lt-btn:disabled {
  opacity: .62;
  box-shadow: none; transform: none;
  background: var(--lt-panel-2);
  color: var(--lt-muted);
}
.lt-btn.lt-block { width: 100%; }
/* 🔴 2026-10-19 手感修复：lt-sm 原来 padding 6px → 实际高度只有 34px，
   拇指点不准（实测"购买"按钮 97x34、"子项删除"更小）。
   加 min-height 44px，但把纵向 padding 收一点，视觉高度只多 2~3px，
   不会显得笨重 —— "热区够大 + 观感不变"两者可以同时要。 */
.lt-btn.lt-sm { padding: 5px 12px; min-height: 44px; font-size: 12.5px; border-width: 1.5px; box-shadow: 0 1.5px 0 var(--lt-ink); }

/* ---------------- 任务卡片 ---------------- */
.lt-task {
  position: relative;
  border: 1.5px solid var(--lt-line); border-radius: var(--lt-radius);
  background: #fff; padding: 12px 13px; margin-bottom: 10px;
  /* 2026-10-19：原来只有一层 2px 边框，卡片读起来"贴在纸上"没有层次。
     改成"极淡描边 + 双层阴影"：近处 1px 接触影定边界，远处 12px 柔影给浮起感。
     这是站点主站的阴影语言（--shadow / --shadow-lift）缩到卡片尺度。 */
  box-shadow: 0 1px 2px rgba(74,64,51,.06), 0 6px 16px -6px rgba(74,64,51,.10);
  transition: border-color .2s, background .2s, box-shadow .2s;
}
/* 完成态：左侧一条 3px 色条（比整块变色更克制，也让"已完成"在长列表里一眼可扫） */
.lt-task.lt-done { background: #f7fbf2; border-color: #cfe3c2; }
.lt-task.lt-done::before {
  content: ''; position: absolute; left: 0; top: 10px; bottom: 10px;
  width: 3px; border-radius: 0 3px 3px 0; background: var(--lt-accent-2);
}
.lt-task.lt-running { border-color: #e8c98a; background: #fffdf4;
  box-shadow: 0 1px 2px rgba(74,64,51,.06), 0 8px 20px -8px rgba(242,177,52,.28); }
.lt-task-top { display: flex; align-items: center; gap: 8px; }
/* 🔴 2026-10-19 手感修复（用户："项目太挫了"）：
   实测 22 个可点区域 < 44px，其中复选框只有 21x21、任务"更多"按钮 32x32 ——
   拇指根本点不准（iOS HIG / Material 都要求 ≥44px）。
   做法：视觉尺寸保持不变（26px 的方框仍然好看），但用 ::before 撑开【热区】到 44px。
   这样列表看起来还是紧凑的，手指却能点到。 */
.lt-check {
  position: relative;
  flex: 0 0 auto;
  /* 🔴 手感修复：热区必须【物理】是 44px。
     不能用"伪元素撑热区"——伪元素不参与布局，被兄弟元素压住时
     elementFromPoint 会先命中兄弟（实测子任务那个复选框下方 14px 点不到）。
     所以：盒子真的做成 44px，再用 ::after 画【26px 的视觉方框】。
     布局高度用 margin 收回去，列表看起来仍然是紧凑的。 */
  width: 44px; height: 44px;
  /* ⚠️ 不要用负 margin 把占位收回去 —— 那会让相邻元素挤进来压住热区
     （实测：子任务复选框下方 14px 点不到，命中的是兄弟元素）。
     44px 就让它真实占位，列表间距由父级的 gap 控制。 */
  margin: 0;
  border: 0; background: none;
  display: grid; place-items: center;
  font-size: 15px; color: #fff;
  -webkit-tap-highlight-color: transparent;
}
/* 视觉方框（26px）—— 真正被看见的那个圆角方块 */
.lt-check::after {
  content: '';
  position: absolute; left: 50%; top: 50%;
  width: 26px; height: 26px; border-radius: 9px;
  transform: translate(-50%, -50%) scale(1);
  border: 2px solid var(--lt-ink); background: #fff;
  transition: background .18s, transform .12s, border-color .18s;
  /* ⚠️ 不要用 z-index:-1 —— 它会跑到 .lt-task 的背景后面，方块整个消失
     （截图实证：复选框全空了）。正常层叠顺序即可：::after 先画、span 后画。 */
  z-index: 0;
}
.lt-task.lt-done .lt-check::after { background: var(--lt-accent-2); border-color: var(--lt-accent-2); }
.lt-check span { position: relative; z-index: 1; display: block; line-height: 1; }
.lt-check:active { transform: scale(.88); }
/* 勾上时给一个"弹一下"的反馈 —— 纯视觉，零成本 */
.lt-task.lt-done .lt-check { animation: lt-check-pop .32s cubic-bezier(.3, 1.6, .5, 1); }
@keyframes lt-check-pop {
  0% { transform: scale(.86); }
  55% { transform: scale(1.14); }
  100% { transform: scale(1); }
}
.lt-task.lt-done .lt-check::after { background: var(--lt-accent-2); border-color: var(--lt-accent-2); }
.lt-task-main { flex: 1; min-width: 0; }
.lt-task-title { font-weight: 600; font-size: 15px; word-break: break-word; }
.lt-task.lt-done .lt-task-title { text-decoration: line-through; color: var(--lt-muted); }
.lt-task-meta { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 5px; font-size: 11.5px; color: var(--lt-muted); }
.lt-chip {
  display: inline-flex; align-items: center; gap: 3px;
  background: var(--lt-panel-2); border: 1px solid #e6d7c1;
  border-radius: 999px; padding: 1px 8px; font-size: 11px;
  font-variant-numeric: tabular-nums;
}
.lt-chip.lt-fire { background: #fff0e6; border-color: #f0b48f; color: #b4592f; }
.lt-task-acts { display: flex; gap: 6px; align-items: center; }

/* 子项 */
.lt-subs { margin: 10px 0 2px; padding-left: 4px; border-left: 2px dashed var(--lt-line); }
.lt-sub { display: flex; align-items: center; gap: 9px; padding: 5px 0 5px 10px; font-size: 13.5px; }
/* 🔴 2026-10-19 手感修复：这条原来把子任务复选框压到 21x21（拇指根本点不准）。
   现在热区保持 44px（与主任务一致），只把【视觉方框】缩成 21px：
   通过 ::after 的宽高覆盖实现，热区不受影响。
   ⚠️ 这正是我刚才没找到的"覆盖源" —— 我改了 .lt-check 的 44px，
      但被这条更具体的规则（.lt-sub .lt-check）压回了 21px。 */
.lt-sub .lt-check { width: 44px; height: 44px; font-size: 12px; }
.lt-sub .lt-check::after { width: 21px; height: 21px; border-radius: 7px; }
.lt-sub.lt-done { color: var(--lt-muted); text-decoration: line-through; }
.lt-sub-name { flex: 1; min-width: 0; word-break: break-word; }

/* 计时器 */
.lt-timer {
  display: flex; align-items: center; gap: 10px;
  margin-top: 10px; padding: 8px 10px;
  background: #fff8e8; border: 1.5px dashed var(--lt-gold); border-radius: 12px;
}
.lt-timer .lt-clock { font-variant-numeric: tabular-nums; font-size: 20px; font-weight: 700; letter-spacing: .03em; }
.lt-timer .lt-tmode { font-size: 11px; color: var(--lt-muted); }
.lt-timer .lt-spacer { flex: 1; }

/* ---------------- 空状态 ---------------- */
.lt-empty { text-align: center; padding: 26px 16px; color: var(--lt-muted); }
.lt-empty .lt-face { font-size: 40px; line-height: 1; margin-bottom: 10px; }
.lt-empty b { display: block; color: var(--lt-text); font-size: 15px; margin-bottom: 4px; }

/* ---------------- 表单 ---------------- */
.lt-field { margin: 12px 0; }
.lt-field label { display: block; font-size: 12.5px; color: var(--lt-muted); margin-bottom: 5px; font-weight: 600; }
.lt-input, .lt-select, .lt-area {
  width: 100%; padding: 11px 12px;
  border: 2px solid var(--lt-line); border-radius: 11px;
  background: #fff; font: inherit; font-size: 14.5px; color: var(--lt-text);
}
.lt-area { min-height: 74px; resize: vertical; }
.lt-input:focus, .lt-select:focus, .lt-area:focus { outline: none; border-color: var(--lt-accent-2); }
.lt-row { display: flex; gap: 8px; flex-wrap: wrap; }
.lt-row > * { flex: 1; min-width: 110px; }

/* 选项按钮组 */
.lt-opts { display: flex; gap: 7px; flex-wrap: wrap; }
.lt-opt {
  padding: 7px 13px; border-radius: 999px;
  border: 2px solid var(--lt-line); background: #fff; font-size: 13px;
}
.lt-opt.lt-on { background: var(--lt-accent-2); border-color: var(--lt-ink); color: #fff; font-weight: 600; }

/* ---------------- 捏人 ---------------- */
.lt-avatar-stage {
  display: flex; flex-direction: column; align-items: center;
  background: linear-gradient(180deg, #dff0cd, #bde0a2);
  border: 2px solid var(--lt-ink); border-radius: var(--lt-radius);
  padding: 16px 0 10px; margin-top: 14px;
}
.lt-avatar-stage svg { width: 118px; height: auto; }
.lt-avatar-name { font-size: 12px; color: var(--lt-ink-2); }
.lt-swatches { display: flex; gap: 8px; flex-wrap: wrap; }
.lt-sw {
  width: 34px; height: 34px; border-radius: 10px;
  border: 2px solid var(--lt-line);
}
.lt-sw.lt-on { border-color: var(--lt-ink); box-shadow: 0 0 0 2px #fff inset, 0 0 0 3px var(--lt-accent-2); }

/* ---------------- 日历热力图 ---------------- */
.lt-cal-head, .lt-cal-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 5px; }
.lt-cal-head div { text-align: center; font-size: 11px; color: var(--lt-muted); }
.lt-cal-cell {
  aspect-ratio: 1 / 1; border-radius: 7px;
  background: var(--lt-panel-2); border: 1.5px solid var(--lt-line);
  display: grid; place-items: center; font-size: 10.5px; color: var(--lt-muted);
}
.lt-cal-cell.lt-out { opacity: .35; }
.lt-cal-cell.lt-future { opacity: .45; border-style: dashed; }
.lt-cal-cell.lt-l1 { background: #dcefcb; border-color: #b7d9a0; color: #4a6b3a; }
.lt-cal-cell.lt-l2 { background: #b7dda0; border-color: #8fc072; color: #33512a; }
.lt-cal-cell.lt-l3 { background: #8fc072; border-color: #6aa353; color: #fff; }
.lt-cal-cell.lt-l4 { background: #5f9a48; border-color: #47772f; color: #fff; }

/* ---------------- 统计格 ---------------- */
.lt-stats-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 9px; }
.lt-stat {
  background: var(--lt-panel-2); border: 2px solid var(--lt-line);
  border-radius: 13px; padding: 12px 6px; text-align: center;
}
.lt-stat b { display: block; font-size: 21px; line-height: 1.2; font-variant-numeric: tabular-nums; }
.lt-stat span { font-size: 11px; color: var(--lt-muted); }

/* ---------------- 仓库 / 商店 ---------------- */
.lt-grid2 { display: grid; grid-template-columns: repeat(auto-fill, minmax(96px, 1fr)); gap: 10px; }
.lt-item {
  background: #fff; border: 2px solid var(--lt-line); border-radius: 13px;
  padding: 8px 6px 6px; text-align: center; position: relative;
}
.lt-item svg { display: block; margin: 0 auto; max-height: 56px; width: auto; }
.lt-item .lt-iname { font-size: 11.5px; margin-top: 3px; }
.lt-item .lt-n {
  position: absolute; top: 5px; right: 5px;
  background: var(--lt-ink); color: #fff; font-size: 10.5px;
  border-radius: 999px; padding: 0 6px;
}
.lt-item .lt-price { font-size: 11px; color: var(--lt-gold); font-weight: 700; }
/* 🔴 2026-10-19 UI 修复【买不起的道具像"按钮坏了"】：
   原来 .lt-item.lt-cant 整块 opacity .5，而里面的 .lt-btn:disabled
   又有 opacity .5 ⇒ 两层相乘 = 0.25，按钮文字几乎看不见。
   ⇒ 外层不再压透明度，只用"整体降饱和"表达"买不起"；
     按钮本身保持 .5 的禁用态即可读。 */
.lt-item.lt-cant { filter: saturate(.55); }
.lt-item button { width: 100%; }

/* 摆放模式 */
.lt-placing .lt-world-wrap { cursor: crosshair; }
.lt-placing .lt-house-hit { pointer-events: none; }
.lt-placing-bar {
  position: absolute; left: 50%; transform: translateX(-50%);
  bottom: calc(var(--lt-dock-h) + 12px);
  background: var(--lt-ink); color: #fff;
  padding: 7px 14px; border-radius: 999px; font-size: 12.5px;
  z-index: 26; white-space: nowrap;
}

/* ---------------- Toast / 粒子 ---------------- */
.lt-toast {
  position: fixed; left: 50%;
  /* 🔴 抬高到气泡上方：bubble 在 dock+14px、高度约 80~110px，
     toast 原来在 dock+24px —— 只差 10px，两者同时出现必然叠在一起。
     现在放到 dock+130px，即使同时出现也不会压住气泡。 */
  bottom: calc(var(--lt-dock-h) + 130px);
  transform: translateX(-50%);
  background: var(--lt-ink); color: #fff;
  padding: 9px 16px; border-radius: 999px; font-size: 13.5px;
  z-index: 200; max-width: 88vw; text-align: center;
  animation: lt-pop .25s cubic-bezier(.3, 1.4, .5, 1);
}
.lt-toast[hidden] { display: none; }

.lt-fx { position: fixed; inset: 0; pointer-events: none; z-index: 150; }
.lt-fx span {
  position: absolute; font-size: 20px;
  animation: lt-float 1.05s ease-out forwards;
}
/* ---------- 民居（2026-10-19） ----------
   16 栋背景民居现在可以点：小人物走去应门，冒出住户信息。
   热区和主建筑一样铺满整栋，但视觉上不抢戏（不加底色/描边），
   只用 cursor + 轻微抬起表示"这块能点"。 */
/* 🔴 2026-10-19 性能（用户："首先很卡顿"）：
   27 个民居热区【每个都常驻 transition】—— 浏览器每帧都要为这 27 个
   元素维护动画属性，而它们只在"按下"那一瞬需要过渡。
   ⇒ 把 transition 挪到 :active 里（按下时才生效），平时零开销。
   同类问题：lt-npc-name（32 个）见下面。 */
.lt-villa-hit {
  position: absolute; cursor: pointer;
  border-radius: 10px;
}
.lt-villa-hit:active {
  transform: scale(.97); filter: brightness(1.06);
  transition: transform .16s, filter .16s;
}
.lt-villa-hit:focus-visible { outline: 2px solid var(--lt-accent); outline-offset: 2px; }

/* ---------- 室内场景（2026-10-19） ----------
   进屋时地图淡出、等距小房间淡入 —— 像镜头推近。
   room2d.js 负责画（一张 320x240 的 SVG），这里只做挂载与过渡。 */
/* ---------- 任务重要性标签（2026-10-19） ----------
   ×1 普通弱化、×2 重要强调、×3 关键金底 —— 三档一眼分得开。
   之前只有 ×2/×3 才显示，玩家看不到"普通 ×1"这个基准，也就不会去调整。 */
.lt-chip-w1 { opacity: .62; }
.lt-chip-w2 { background: #fff2d8; border-color: #e8c887; color: #8a6220; font-weight: 700; }
.lt-chip-w3 {
  background: linear-gradient(180deg, #ffe8a8, #ffd166);
  border-color: #d9a520; color: #6b4a00; font-weight: 800;
  box-shadow: 0 1px 2px rgba(180, 130, 20, .25);
}

/* ---------- 跨区提示（2026-10-19） ----------
   走进新的区域时，屏幕顶部短暂浮出一条区名 ——
   让"我在哪一片"这个信息被感知到，而不是只有静态路牌。
   pointer-events: none，绝不挡操作。 */
.lt-zone-tip {
  position: absolute; left: 50%; top: 82px;
  transform: translate(-50%, -8px);
  z-index: 9500;
  pointer-events: none;
  padding: 6px 16px;
  border-radius: 999px;
  background: rgba(61, 48, 33, .82);
  color: #fff6e2;
  font-size: 13px; font-weight: 700; letter-spacing: .06em;
  opacity: 0;
  transition: opacity .26s ease, transform .34s cubic-bezier(.22, 1.2, .36, 1);
  box-shadow: 0 3px 10px rgba(40, 30, 15, .28);
  white-space: nowrap;
}
.lt-zone-tip.lt-zone-tip-on {
  opacity: 1;
  transform: translate(-50%, 0);
}

/* ---------- 区域路牌（2026-10-19） ----------
   立在每条分区线旁边，告诉玩家"过去是哪一片"。
   木牌 + 白字，与小镇的木色体系一致。 */
/* 路牌层：在 worldEl 内（跟随镜头），但层级高于所有落地物。
   路牌是导航标识，不能被树/房子盖住（试过放在 lifeEl 与 objEl 都不行）。 */
.lt-sign-layer {
  position: absolute; inset: 0;
  pointer-events: none;
  z-index: 9000;
}
.lt-zonesign {
  position: absolute; width: 44px;
  pointer-events: none;
  display: flex; justify-content: center;
}
.lt-sign-layer .lt-zonesign { z-index: 9002 !important; }
/* ⚠️ 第一版 font-size 9.5px + padding 2/7 ⇒ 实测整块只有 23x11，
   在这个缩放（0.51）下文字完全看不清。放大到 12px / padding 3/10。 */
.lt-zonesign span {
  display: inline-block;
  padding: 3px 10px;
  border-radius: 6px;
  background: linear-gradient(180deg, #d8a869, #b98647);
  border: 1.8px solid #7d5322;
  color: #fffdf6;
  font-size: 12px; font-weight: 700; letter-spacing: .02em;
  white-space: nowrap;
  box-shadow: 0 2px 3px rgba(80,50,20,.3);
}
/* 藏在牌子下面的两根小支柱，让它"立"在地上而不是浮着 */
.lt-zonesign::after {
  content: '';
  position: absolute; left: 50%; top: 100%;
  width: 2px; height: 6px; margin-left: -1px;
  background: #8a5f33;
}

/* ---------- 夜行的萤火虫 / 夜空星星（2026-10-19） ----------
   同样是这个小镇，白天和晚上是两幅画：
     白天：蝴蝶绕飞
     夜里：萤火虫贴着草丛飘、天上有点点星星
   两者都常驻 DOM，只用 CSS 控制显隐 —— 切换时段零重绘零延迟。 */
.lt-firefly { display: none; }
.lt-night .lt-firefly { display: block; position: relative; width: 16px; height: 16px; }
.lt-night .lt-butterfly { display: none; }     /* 夜里蝴蝶收工 */

.lt-firefly i {
  position: absolute; left: 50%; top: 50%;
  width: 5px; height: 5px; margin: -2.5px 0 0 -2.5px;
  border-radius: 50%;
  background: radial-gradient(circle, #fff8c4 0%, #ffe066 45%, rgba(255,214,64,0) 72%);
  box-shadow: 0 0 6px 2px rgba(255, 224, 102, .55);
  animation: lt-firefly-drift 5.2s ease-in-out infinite, lt-firefly-blink 2.4s ease-in-out infinite;
}
/* 每一只的节奏错开，看起来才不是"同步闪烁" */
.lt-firefly-obj:nth-child(3n) i    { animation-duration: 6.1s, 2.9s; animation-delay: -1.3s, -0.7s; }
.lt-firefly-obj:nth-child(3n+1) i  { animation-duration: 4.6s, 2.1s; animation-delay: -2.4s, -1.5s; }

@keyframes lt-firefly-drift {
  0%   { transform: translate(0, 0); }
  25%  { transform: translate(6px, -5px); }
  50%  { transform: translate(-2px, -9px); }
  75%  { transform: translate(-7px, -3px); }
  100% { transform: translate(0, 0); }
}
@keyframes lt-firefly-blink {
  0%, 100% { opacity: .25; }
  45%      { opacity: 1; }
  70%      { opacity: .45; }
}

/* 夜空星星 */
.lt-stars {
  position: absolute; inset: 0;
  pointer-events: none;
  z-index: 2;                /* 盖在天空之上、地面之下 */
  opacity: 0;
  transition: opacity .9s ease;
}
.lt-night .lt-stars { opacity: 1; }
.lt-star {
  position: absolute;
  background: #fffdf0;
  border-radius: 50%;
  box-shadow: 0 0 3px rgba(255, 253, 240, .9);
  animation: lt-star-twinkle 3.6s ease-in-out infinite;
}
@keyframes lt-star-twinkle {
  0%, 100% { opacity: .35; transform: scale(.85); }
  50%      { opacity: 1;   transform: scale(1.1); }
}

/* ---------- 夜晚的灯（2026-10-19） ----------
   之前夜里只有一层冷蓝色膜，窗户还是白天的淡蓝色 ——
   "夜里没有任何一盏灯亮"，小镇看着是死的。
   现在：夜间把窗玻璃染成暖黄，并给灯泡加光晕。
   ⚠️ 用属性选择器命中 fill="#bcdcf0" 的窗玻璃（三处绘制共用这个色），
      零重绘、零新增节点；白天完全不影响（只在 .lt-night 下生效）。 */
.lt-night .lt-house rect[fill="#bcdcf0"],
.lt-night .lt-villa rect[fill="#bcdcf0"],
.lt-night .lt-house path[fill="#bcdcf0"] {
  fill: #ffd98a;
}
/* 窗玻璃上叠一层暖光（用 drop-shadow 做光晕，比加滤镜便宜） */
.lt-night .lt-house rect[fill="#bcdcf0"],
.lt-night .lt-villa rect[fill="#bcdcf0"] {
  filter: drop-shadow(0 0 3px rgba(255, 214, 130, .85));
}
/* 路灯 / 门前挂灯：夜间亮起来。
   ⚠️ 灯的灯泡在 prop/lamp 与 doorTop 处都用 #ffe9a8，统一加光晕。 */
.lt-night .lt-ambient svg,
.lt-night .lt-obj svg {
  /* 不给整栋房子加 filter（会很贵），只对"夜里该亮的"元素加。 */
}
.lt-night svg rect[fill="#ffe9a8"],
.lt-night svg path[fill="#ffe9a8"] {
  filter: drop-shadow(0 0 4px rgba(255, 214, 110, .95));
}
/* 黄昏：只给窗户一点暖，不加光晕（天还没黑透） */
.lt-dusk .lt-house rect[fill="#bcdcf0"],
.lt-dusk .lt-villa rect[fill="#bcdcf0"],
.lt-dusk .lt-house path[fill="#bcdcf0"] {
  fill: #dbe8d8;
}

/* ---------- 修通路的小车（2026-10-19） ----------
   路修通时沿路面弧线跑一趟，把「这条路由你的坚持铺成」演出来。
   位置由 JS 逐帧算（世界坐标 → 屏幕），这里只管显隐与过渡。 */
.lt-cart {
  position: absolute; left: 0; top: 0;
  pointer-events: none;
  opacity: 0;
  transition: opacity .25s;
  filter: drop-shadow(0 2px 2px rgba(40,50,30,.35));
}
.lt-cart.lt-cart-on { opacity: 1; }

/* ---------- 里程碑达成：房子脉冲（2026-10-19） ----------
   完成一件让房子升档的事时，房子自己"弹一下 + 亮一下"。
   与 .lt-house-enter（开门进屋）区分：那个是缩放 + 亮度，这个是
   "向上顶一格再回落"，读起来像"又长高了一层"。 */
.lt-house-up {
  animation: lt-house-up 1.05s cubic-bezier(.22, 1.3, .36, 1);
  transform-origin: 50% 100%;      /* 以脚点为轴，像"从地面长高" */
}
@keyframes lt-house-up {
  0%   { transform: translateY(0) scale(1);    filter: brightness(1); }
  22%  { transform: translateY(-6px) scale(1.045); filter: brightness(1.18) saturate(1.12); }
  55%  { transform: translateY(-2px) scale(1.015); filter: brightness(1.08); }
  100% { transform: translateY(0) scale(1);    filter: brightness(1); }
}

/* ---------- 进度预告（2026-10-19） ----------
   地点面板里列出"两条成长线各自的下一个里程碑 + 达成后的具体效果"。
   目标感来自"知道下一步会得到什么"。 */
.lt-milestone {
  display: flex; flex-direction: column; gap: 2px;
  margin: 6px 0; padding: 9px 12px;
  border-radius: 11px;
  background: linear-gradient(180deg, #fffdf6, #fff5df);
  border: 1px solid #e8d9b8;
  border-left: 3px solid var(--lt-accent);
}
.lt-milestone b { font-size: 13px; font-weight: 700; color: var(--lt-text); }
.lt-milestone span { font-size: 12.5px; color: #7a6234; }
.lt-milestone i { font-style: normal; font-size: 11px; color: var(--lt-muted); }
.lt-milestone-done { background: var(--lt-panel-2); border-color: var(--lt-line); border-left-color: var(--lt-accent-2); }

/* ---------- 小镇的变化（2026-10-19） ----------
   把隐性反馈（熟悉度 / 房子长高 / 门口痕迹）显性列出来 ——
   玩家不必一个个去点 NPC 才能发现"我坚持了 14 天"。 */
.lt-change-list { display: flex; flex-direction: column; gap: 6px; margin-top: 8px; }
.lt-change-item {
  display: flex; align-items: baseline; gap: 8px;
  padding: 8px 11px; border-radius: 11px;
  background: var(--lt-panel-2); border: 1px solid var(--lt-line);
  font-size: 12.5px;
}
.lt-change-item b { flex: 0 0 auto; font-weight: 700; color: var(--lt-text); min-width: 3.2em; }
.lt-change-item span { flex: 1; min-width: 0; color: var(--lt-muted); font-size: 12px; line-height: 1.45; }
.lt-change-item i { flex: 0 0 auto; font-style: normal; font-size: 11px; color: var(--lt-accent-2); font-weight: 600; }

/* ---------- 拾取物（2026-10-19 玩法深度） ----------
   散落在小镇里的小东西：贝壳 / 蘑菇 / 四叶草 / 小木箱。
   走过去自动收集 —— "闲逛也有收益"的最小闭环。
   视觉上要【一眼能看见但不抢戏】：轻微上下浮动 + 一圈很淡的光晕。 */
.lt-pickup {
  pointer-events: none;      /* 不用点，走过去就捡 */
  animation: lt-pick-float 1.9s ease-in-out infinite;
}
.lt-pickup svg { display: block; overflow: visible; }
/* ⚠️ 24x24 在 1280 宽的世界 + 0.638 缩放下只剩 ~15px，实测几乎看不见。
   放大到 34px（≈22 屏幕像素），并给一点投影让它"立"在地面上。 */
.lt-pickup { width: 34px; height: 34px; filter: drop-shadow(0 2px 2px rgba(40,50,30,.30)); }
.lt-pickup svg { width: 34px; height: 34px; }
.lt-pickup::after {
  content: '';
  position: absolute; left: 50%; top: 50%;
  width: 34px; height: 34px; transform: translate(-50%, -50%);
  border-radius: 50%;
  background: radial-gradient(circle, rgba(255,240,180,.55) 0%, rgba(255,240,180,0) 70%);
  animation: lt-pick-glow 1.9s ease-in-out infinite;
  z-index: -1;
}
@keyframes lt-pick-float {
  0%, 100% { transform: translateY(0); }
  50%      { transform: translateY(-4px); }
}
@keyframes lt-pick-glow {
  0%, 100% { opacity: .45; transform: translate(-50%, -50%) scale(.9); }
  50%      { opacity: .95; transform: translate(-50%, -50%) scale(1.15); }
}

/* ---------- 时段氛围（2026-10-19） ----------
   按时段叠一层极淡的色膜：清晨暖橙 / 黄昏橙红 / 夜晚冷蓝 / 白天无。
   浓度 ≤ .17 —— 这是氛围不是滤镜（实测 .30 会把草地变灰绿、小人染蓝）。
   pointer-events:none 保证不挡任何操作。 */
.lt-time-tint {
  position: absolute; inset: 0;
  pointer-events: none;
  z-index: 45;                  /* 盖在场景之上、UI 之下 */
  transition: background .8s ease, opacity .8s ease;
  mix-blend-mode: multiply;
}
.lt-room-scene {
  position: absolute; inset: 0; z-index: 2147483100;   /* 必须高于站点级返回按钮（2147483000）*/
  display: flex; align-items: center; justify-content: center;
  background: rgba(30, 44, 52, .42);
  opacity: 0; transition: opacity .3s ease;
  pointer-events: none;
}
.lt-room-scene.lt-room-on { opacity: 1; pointer-events: auto; }
.lt-room-stage {
  width: min(94%, 380px); aspect-ratio: 268 / 220;
  transform: scale(.94); transition: transform .34s cubic-bezier(.22, 1.2, .36, 1);
  filter: drop-shadow(0 18px 34px rgba(20, 30, 26, .38));
  background: #eef6ff;
  border-radius: 18px;
  overflow: hidden;
}
.lt-room-scene.lt-room-on .lt-room-stage { transform: scale(1); }
.lt-room-svg { display: block; width: 100%; height: 100%; }
/* 民居室内的标题（"郑家 · 孩子刚上小学"）—— 原来没有样式，标题不显示 */
.lt-room-title {
  position: absolute; left: 50%; top: 16px; transform: translateX(-50%);
  max-width: 86vw; padding: 6px 16px;
  border-radius: 999px;
  background: rgba(255,252,246,.94);
  border: 1.5px solid var(--lt-line);
  font-size: 12.5px; font-weight: 600; color: var(--lt-ink);
  box-shadow: 0 3px 12px rgba(40,32,20,.16);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  z-index: 3;
}
.lt-room-close {
  position: absolute; left: 50%; bottom: 22px; transform: translateX(-50%);
  padding: 10px 26px; min-height: 44px;
  border-radius: 999px; border: 2px solid var(--lt-ink);
  background: var(--lt-panel); color: var(--lt-ink);
  font-size: 14px; font-weight: 700;
  box-shadow: 0 3px 0 var(--lt-ink);
}
.lt-room-close:active { transform: translateX(-50%) translateY(2px); box-shadow: 0 0 0 var(--lt-ink); }

/* ---------- 小镇居民 NPC（2026-10-19） ----------
   每个地点住着一位居民，站在自家工作台旁做该地点的事。
   名牌默认半透明（常显会太吵），按下时清晰。 */
.lt-npcs {
  position: absolute; inset: 0;
  /* ⚠️ 容器本身不能吃点击（NPC 的 SVG 自己会开 auto）；
     z-index 保持 auto，让 .lt-npc 的 z-index 与 objEl 内其它 .lt-obj
     【参与同一套排序】—— 若这里写死一个 z-index，容器会形成独立层叠上下文，
     子元素的 z 就与房子无关了（那正是 NPC 被房子热区挡住的原因）。 */
  pointer-events: none;
}
.lt-npc {
  position: absolute; width: 38px; height: 52px;
  /* 🔴 2026-10-19 修：原来是 pointer-events:auto（整个 38x52 矩形都吃点击）——
     实测民居住户 NPC 正好压在学校的热区上方，把"点房子进屋"整个拦掉了
     （elementsFromPoint 命中 lt-villa-npc 而不是 lt-house-hit）。
     改成 none + 只让 SVG 自己接收（SVG 的命中检测按【实际图形】而不是外框，
     所以人形轮廓之外的点会穿透到下层，房子又能正常点了）。 */
  pointer-events: none; cursor: pointer;
  /* 2026-10-19 性能：NPC 位置改由 transform 驱动（见 tickNPCs），
     提前建合成层 + 限制布局范围，32 个同时走动时不重排整棵树。
     ⚠️ will-change 不能滥用 —— 只给这种"长期在动"的元素，
        给静态元素加反而会占显存。 */
  will-change: transform;
  contain: layout style;
}
.lt-npc svg {
  display: block; width: 38px; height: auto;
  /* ⚠️ 只让 SVG 自己接收点击，但 SVG 的【外框】仍会被命中检测算进去。
     实测：民居热区（约 50x70）被站在门口的 NPC 挡掉 2/3 的采样点。
     用 clip-path 把命中区收成【人形轮廓附近的一个小矩形】——
     头顶 + 身体中段仍然能点，但左右/下缘的空白不再吞点击。 */
  pointer-events: auto;
  clip-path: inset(12% 22% 18% 22%);
}
.lt-npc-name {
  position: absolute; left: 50%; bottom: -15px;
  transform: translateX(-50%);
  font-size: 10px; line-height: 1.4; white-space: nowrap;
  padding: 1px 7px; border-radius: 999px;
  background: rgba(255,250,242,.9);
  border: 1px solid var(--lt-line);
  color: var(--lt-ink-2);
  /* 2026-10-19：原来 opacity .55 太淡，5 个名字几乎看不见（截图实证）。
     提到 .82 —— 既能读清，又不至于喧宾夺主；按下时到 1。 */
  opacity: .82;
  z-index: 3;
  pointer-events: none;   /* 名牌只是标签，不该拦住房子/NPC 的点击 */
  /* 🔴 2026-10-19 性能：32 个名牌原来常驻 transition，
     浏览器每帧都要维护它们。transition 只在悬停/按下才需要 ⇒ 挪到触发侧。 */
}
.lt-npc:hover .lt-npc-name,
.lt-npc:active .lt-npc-name {
  opacity: 1; transform: translateX(-50%) translateY(-2px);
  transition: opacity .18s, transform .18s;
}
/* 民居住户：默认完全隐藏名牌（21 个名字会糊成一片），悬停/按下才出现 */
.lt-npc-name-mini { opacity: 0; }
.lt-npc:active svg { transform: scale(.96); }

/* ---------- 今日回顾（2026-10-19） ---------- */
   记录面板原来只有汇总数字 + 日历 + 一句周小结 —— 数字是抽象的，
   玩家看不到"今天具体做掉了哪几件事"。这里把清单列出来。 */
.lt-review-list { display: flex; flex-direction: column; gap: 6px; margin-top: 8px; }
.lt-review-item {
  display: flex; align-items: center; gap: 8px;
  padding: 8px 11px; border-radius: 11px;
  background: var(--lt-panel-2); border: 1px solid var(--lt-line);
  font-size: 13.5px;
}
.lt-review-tick {
  flex: 0 0 auto; width: 20px; height: 20px; border-radius: 6px;
  display: grid; place-items: center; font-size: 12px;
  background: var(--lt-accent-2); color: #fff;
}
.lt-review-item b { flex: 1; min-width: 0; font-weight: 600; color: var(--lt-text); }
.lt-review-item i { font-style: normal; font-size: 11px; color: var(--lt-muted); flex: 0 0 auto; }

/* ============================================================================
 * 成就系统（2026-10-19）
 * ----------------------------------------------------------------------------
 * ① 解锁浮层 .lt-ach-pop：从屏幕上方滑入 → 停留 2.6s → 淡出
 *    用独立浮层而不是 toast —— toast 会被下一条顶掉，
 *    而"解锁了什么成就"是玩家要看清的信息。
 * ② 成就墙 .lt-ach-grid：已解锁亮着、未解锁灰着但仍显示
 *    （全藏起来就失去"还有什么可做"的引导作用了）。
 * ========================================================================== */
.lt-ach-pop {
  position: fixed; left: 50%; top: 76px; z-index: 160;
  transform: translateX(-50%) translateY(-16px);
  display: flex; align-items: center; gap: 10px;
  padding: 10px 16px 10px 12px;
  background: linear-gradient(180deg, #fffdf4, #ffeec4);
  border: 2px solid #b8860b;
  border-radius: 16px;
  box-shadow: 0 10px 28px rgba(184,134,11,.35), 0 2px 0 rgba(184,134,11,.5);
  opacity: 0;
  animation: lt-ach-in .42s cubic-bezier(.25, 1.3, .4, 1) forwards;
  pointer-events: none;
  max-width: 88vw;
}
.lt-ach-pop.lt-ach-out { animation: lt-ach-outward .62s ease-in forwards; }
@keyframes lt-ach-in {
  0%   { opacity: 0; transform: translateX(-50%) translateY(-26px) scale(.9); }
  100% { opacity: 1; transform: translateX(-50%) translateY(0) scale(1); }
}
@keyframes lt-ach-outward {
  0%   { opacity: 1; transform: translateX(-50%) translateY(0); }
  100% { opacity: 0; transform: translateX(-50%) translateY(-14px); }
}
.lt-ach-ic {
  flex: 0 0 auto; width: 34px; height: 34px; border-radius: 10px;
  display: grid; place-items: center; font-size: 17px;
  background: #fff; border: 2px solid #b8860b; color: #7a5410;
}
.lt-ach-tx { display: flex; flex-direction: column; line-height: 1.25; min-width: 0; }
.lt-ach-tx b { font-size: 14px; font-weight: 700; color: #7a5410; }
.lt-ach-tx i { font-style: normal; font-size: 11.5px; color: #a07c3a; }

.lt-ach-cnt { font-size: 11.5px; color: var(--lt-muted); font-weight: 500; margin-left: 6px; }
.lt-ach-grid {
  display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px;
  margin-top: 8px;
}
.lt-ach {
  display: flex; flex-direction: column; align-items: center; gap: 4px;
  padding: 10px 4px; border-radius: 12px;
  background: var(--lt-panel-2); border: 1.5px solid var(--lt-line);
  opacity: .45; filter: grayscale(.9);
  transition: opacity .2s, filter .2s, border-color .2s;
}
.lt-ach.lt-ach-on {
  opacity: 1; filter: none;
  background: linear-gradient(180deg, #fffdf4, #fff4d6);
  border-color: #d8b25a;
}
.lt-ach-i {
  width: 30px; height: 30px; border-radius: 9px;
  display: grid; place-items: center; font-size: 15px;
  background: #fff; border: 1.5px solid var(--lt-line);
}
.lt-ach.lt-ach-on .lt-ach-i { border-color: #d8b25a; }
.lt-ach b { font-size: 11px; font-weight: 600; text-align: center; line-height: 1.2; color: var(--lt-ink-2); }
.lt-ach.lt-ach-on b { color: #7a5410; }

/* ---------- 进屋的视觉交代（2026-10-19） ----------
   点房子时：走到门口 → 敲门 → 房子亮一下（模拟门开了），再打开面板。
   这一下高亮很小（0.62s），但能明确传达"他进屋了"，而不是"站在门外看着你"。 */
.lt-house-enter {
  animation: lt-house-enter .62s cubic-bezier(.3, 1.2, .4, 1);
}
@keyframes lt-house-enter {
  0%   { filter: brightness(1); transform: scale(1); }
  28%  { filter: brightness(1.16) saturate(1.1); transform: scale(1.035); }
  100% { filter: brightness(1); transform: scale(1); }
}

/* ---------- 新建任务的模板选择器（2026-10-19） ----------
   原来点"＋ 新建任务"直接塞一个空壳「新任务」，用户还要自己改名/设重复/配计时。
   现在先给 6 个常用模板 + 一个"空白任务"，一键就有合理默认值。 */
.lt-tpl-grid {
  display: grid; grid-template-columns: 1fr 1fr; gap: 10px;
  margin: 14px 16px 20px;
}
.lt-tpl {
  display: flex; flex-direction: column; gap: 3px;
  text-align: left; padding: 13px 12px;
  border: 1.5px solid var(--lt-line); border-radius: 14px;
  background: #fff;
  box-shadow: 0 1px 2px rgba(74,64,51,.05), 0 4px 12px -6px rgba(74,64,51,.08);
  transition: transform .12s, box-shadow .18s, border-color .18s;
}
.lt-tpl:active { transform: scale(.97); border-color: var(--lt-accent-2); }
.lt-tpl b { font-size: 14px; font-weight: 600; color: var(--lt-text); letter-spacing: -.01em; }
.lt-tpl span { font-size: 11.5px; color: var(--lt-muted); }
.lt-tpl-blank { border-style: dashed; background: var(--lt-panel-2); }
.lt-tpl-blank b { color: var(--lt-muted); }

/* 金币飘字：金底白字的小胶囊，向上飘 + 淡出。
   比裸文字更"有奖励感"，而且不会和 toast 抢位置。 */
.lt-fx span.lt-fx-coin {
  font-family: var(--lt-num);
  font-size: 15px; font-weight: 700;
  color: #7a5410;
  background: linear-gradient(180deg, #ffe9a8, #f7c948);
  border: 2px solid #b8860b;
  border-radius: 999px;
  padding: 3px 11px;
  box-shadow: 0 4px 12px rgba(184,134,11,.35);
  animation: lt-coin-rise 1.15s cubic-bezier(.2,.8,.3,1) forwards;
  white-space: nowrap;
}
@keyframes lt-coin-rise {
  0%   { transform: translate(-50%, 6px) scale(.6); opacity: 0; }
  18%  { transform: translate(-50%, -4px) scale(1.06); opacity: 1; }
  40%  { transform: translate(-50%, -16px) scale(1); opacity: 1; }
  100% { transform: translate(-50%, -52px) scale(1); opacity: 0; }
}
/* 刚完成的卡片：整卡闪一下暖光（"这件事真的完成了"的直接反馈） */
.lt-task.lt-task-just-done {
  animation: lt-task-flash .7s cubic-bezier(.3,.9,.4,1);
}
@keyframes lt-task-flash {
  0%   { box-shadow: 0 0 0 0 rgba(247,201,72,.9), 0 1px 2px rgba(74,64,51,.06); }
  35%  { box-shadow: 0 0 0 5px rgba(247,201,72,.45), 0 8px 22px -8px rgba(247,201,72,.6); }
  100% { box-shadow: 0 1px 2px rgba(74,64,51,.06), 0 6px 16px -6px rgba(74,64,51,.10); }
}

/* 任务类型标签（2026-10-19）：once / daily 原来没有标识，
   玩家看不出"做完就没了"和"明天还会回来"。 */
.lt-chip-once { background: #f3eefb; border-color: #cbb9e0; color: #5f4a7a; }
.lt-chip-daily { background: #eef7ee; border-color: #bcd9bc; color: #3d6b3d; }

/* 累计型任务的"本月 X/Y"进度标签（2026-10-19） */
.lt-chip-month {
  background: #eef4ff; border-color: #b9cde8; color: #3d5f8a; font-weight: 700;
}
.lt-chip-month.lt-chip-month-done {
  background: linear-gradient(180deg, #d8f0c8, #b8e09a);
  border-color: #7fb06a; color: #2f5a1c;
}

/* 节奏预测（2026-10-19）：把"还差多少点"翻译成"大概几天" */
.lt-pace {
  margin-top: 3px;
  color: #8a6b3a;
  font-size: 11.5px;
}

/* 金币到账时计数器跳一下（2026-10-19）——
   配合"飘字飞进计数器"的第二段动画，让"到账"有明确的落点。 */
#ltCoins.lt-coins-bump {
  animation: lt-coins-bump .42s cubic-bezier(.3, 1.4, .4, 1);
  display: inline-block;
}
@keyframes lt-coins-bump {
  0%   { transform: scale(1); }
  38%  { transform: scale(1.22); color: #b8860b; }
  100% { transform: scale(1); }
}

/* ---------- 雨天（2026-10-19） ----------
   用【一个】重复线性渐变画雨丝，靠 background-position 动画让它下落。
   ⚠️ 绝不能"一颗雨滴一个 div" —— 这个项目在节点数上踩过坑，
      而雨滴动辄几百个。一个渐变 + 一次动画，成本几乎为零。 */
.lt-world-wrap.lt-rain::after {
  content: '';
  position: absolute; inset: 0;
  pointer-events: none;
  z-index: 9400;
  /* ⚠️ 第一版间距只有 6~8px、不透明度 .38 —— 截图里读起来是"斜条纹背景"
     而不是雨。拉开到 22~26px、降到 .22，并让"线段"只占周期的 1/12，
     才有"稀疏的雨丝"的感觉。 */
  background-image:
    repeating-linear-gradient(
      78deg,
      rgba(218, 232, 245, 0) 0px,
      rgba(218, 232, 245, 0) 22px,
      rgba(220, 234, 246, .22) 24px,
      rgba(218, 232, 245, 0) 26px
    );
  background-size: 100% 120px;
  animation: lt-rain-fall .75s linear infinite;
  /* 整体再压一层冷灰，让"下雨天"读得出来 */
  box-shadow: inset 0 0 120px rgba(60, 78, 100, .30);
}
@keyframes lt-rain-fall {
  from { background-position: 0 0; }
  to   { background-position: 0 120px; }
}
/* 雨天稍微压暗、降饱和 —— 但不盖过时段色膜 */
.lt-world-wrap.lt-rain .lt-world {
  filter: brightness(.956) saturate(.92);
}

/* ---------- 夜里街上人变少（2026-10-19） ----------
   32 位居民原来不管几点都在外面走 —— 夜里也满街人，时间感是错的。
   夜里让一部分人回家（约 55%，按 NPC id 稳定哈希）。
   ⚠️ 仍然用 opacity + visibility（不用 display，避免触发布局）。

   🔴 修【叠加过度】：
     第一版让"夜里隐藏"和"雨雪避雨"各自独立生效，
     实测雨天夜间 32 人里隐藏 23 个（72%）、街上只剩 9 人 ——
     小镇冷清得像被遗弃，这不是"氛围"而是"空"。
     ⇒ 夜间优先：夜里【只按 night 规则】隐藏，
       并且【放开】那些被雨天隐藏、但 night 不隐藏的人（回家避雨 vs 夜里睡觉
       是同一件事的两面，不该叠加扣两次人）。
     做法：.lt-night 下先统一放人，再按 night 规则收。 */
/* 夜里：先【无条件放开】所有被雨雪隐藏的人（用更具体的选择器压过雨雪规则），
   再单独按 night 规则收人。
   ⚠️ 第一版只写 `.lt-night .lt-npc[data-rain-hide="1"]`，
      特异性低于雨雪那条 `.lt-rain .lt-npc[data-rain-hide="1"]`（同级但后者在后），
      实测 rain=1 night=0 那 5 个人【仍然被隐藏】。
      ⇒ 加 `.lt-npc` 双写提高特异性，确保夜里只按 night 一套规则走。 */
.lt-world-wrap.lt-night .lt-npc.lt-npc[data-rain-hide="1"] {
  opacity: 1;
  visibility: visible;
}
.lt-world-wrap.lt-night .lt-npc.lt-npc[data-night-hide="1"] {
  opacity: 0;
  visibility: hidden;
  transition: opacity 1.2s ease, visibility 0s 1.2s;
}
/* 🔴 2026-10-19 修【NPC 集体平移入场】的真正元凶：
   这条规则【没有 .lt-night 前缀】⇒ 白天也生效，
   给 18 个 NPC 常挂 1.2s 的 transition。
   配合初始渲染时 transform 从"无"到"目标位置"，浏览器把这一跳做成了动画 ——
   32 个人同时从左上角滑到位，就是用户看到的"NPC 平移入场"。
   （前两轮我改 tickNPCs 和 pauseUntil 都没治到根，因为病根在 CSS。）
   ⇒ 必须限定在 .lt-night 下，白天零 transition。 */
.lt-world-wrap.lt-night .lt-npc[data-night-hide="1"] {
  transition: opacity 1.2s ease, visibility 0s 0s;
}

/* ---------- 雪天（2026-10-19） ----------
   和雨【做法不同】：雨是"一整个斜向渐变 + 快速度移动"，
   雪是"慢、飘、有大小"，一个渐变做不出来那种飘忽感。
   但也绝不用几百个 DOM 节点（这个项目踩过节点数的坑）。
   折中：3 层不同大小/速度的重复径向渐变，叠在一起就有层次。
   · 慢的那层最小最密（远景雪）
   · 快的那层最大最疏（近景雪片） */
.lt-world-wrap.lt-snow::after {
  content: '';
  position: absolute; inset: 0;
  pointer-events: none;
  z-index: 9400;
  /* ⚠️ 第一版每层 90/150/230px 一格、半径 1.2~2.6px ——
     截图里只能看到零星几个白点，读不出"下雪"。
     缩到 55/95/150px 一格（密度约翻三倍）、半径提到 1.6~3.4px，
     才有"雪在落"的感觉。 */
  background-image:
    radial-gradient(circle, rgba(255,255,255,.92) 1.6px, transparent 2.1px),
    radial-gradient(circle, rgba(255,255,255,.72) 2.4px, transparent 3.0px),
    radial-gradient(circle, rgba(255,255,255,.85) 3.4px, transparent 4.2px);
  background-size: 55px 55px, 95px 95px, 150px 150px;
  background-position: 0 0, 30px 45px, 80px 20px;
  animation: lt-snow-fall 12s linear infinite;
}
/* 三层用不同速度需要三个动画源 —— 用一个元素只能统一速度，
   所以再做一层 ::before 承载"更慢更大的远景雪"。 */
.lt-world-wrap.lt-snow::before {
  content: '';
  position: absolute; inset: 0;
  pointer-events: none;
  z-index: 9399;
  background-image: radial-gradient(circle, rgba(255,255,255,.6) 1.4px, transparent 1.9px);
  background-size: 110px 110px;
  animation: lt-snow-fall-slow 15s linear infinite;
}
@keyframes lt-snow-fall {
  from { background-position: 0 0, 40px 60px, 120px 30px; }
  to   { background-position: 14px 55px, 46px 140px, 62px 170px; }
}
@keyframes lt-snow-fall-slow {
  from { background-position: 0 0; }
  to   { background-position: -30px 160px; }
}

/* 雪天：整体偏冷、略提亮（雪地反光），并给地面/屋顶压一层冷白 */
.lt-world-wrap.lt-snow .lt-world {
  filter: brightness(1.02) saturate(.82) hue-rotate(-6deg);
}
.lt-world-wrap.lt-snow .lt-ground {
  filter: brightness(1.05) saturate(.6) hue-rotate(-10deg);
}
.lt-world-wrap.lt-snow .lt-roads {
  filter: brightness(1.02) saturate(.55) contrast(.94);
}
/* 屋顶积雪：用一层半透明白压在房子顶部 ——
   ⚠️ 不能改 SVG（那要重绘），用 ::after 覆盖整栋房子的上 1/3 */
.lt-world-wrap.lt-snow .lt-place-obj,
.lt-world-wrap.lt-snow .lt-villa {
  filter: brightness(1.03) saturate(.72);
}
/* 雪天居民也少出门（同雨天，但更多：下雪更冷） */
.lt-world-wrap.lt-snow .lt-npc[data-rain-hide="1"] {
  opacity: 0;
  visibility: hidden;
}
.lt-world-wrap.lt-snow .lt-npc:nth-child(3n) {
  opacity: 0;
  visibility: hidden;
}

/* ---------- 节日标记与预告（2026-10-19） ---------- */
/* 日历格子上的小圆点：一眼看出"这天是节日" */
.lt-cal-cell { position: relative; }
.lt-cal-dot {
  position: absolute; left: 50%; bottom: 3px;
  width: 4px; height: 4px; margin-left: -2px;
  border-radius: 50%;
  background: #e0473f;
  box-shadow: 0 0 0 1.5px rgba(224, 71, 63, .22);
}
/* 节日格子整体加一圈淡红描边，比只有小圆点更醒目 */
.lt-cal-cell.lt-cal-fest {
  box-shadow: inset 0 0 0 1.5px rgba(224, 71, 63, .35);
}
/* 顶部"下一个节日"提示条 */
.lt-note-fest {
  margin: 6px 0 2px; padding: 8px 11px;
  border-radius: 10px;
  font-size: 12px; line-height: 1.55;
  color: #7a3a34;
  background: linear-gradient(180deg, #fff5f2, #ffeae4);
  border: 1px solid #f0cdc4;
}
.lt-note-fest b { color: #c0392b; }

/* ---------- 云备份：存档码展示（2026-10-24） ----------
   存档码是用户要"记住/抄走"的东西，所以要做得像一张券：
   等宽字体、字距拉开、虚线边框 —— 一眼看出"这是一串要保留的码"。 */
.lt-code-view {
  margin: 8px 0; padding: 11px 13px;
  border-radius: 11px;
  background: linear-gradient(180deg, #f7fbff, #eef5fc);
  border: 1.5px dashed #8ab4d8;
  display: flex; flex-direction: column; gap: 3px;
}
.lt-code-label { font-size: 11px; color: var(--lt-muted); letter-spacing: .04em; }
.lt-code-val {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 15.5px; font-weight: 800; letter-spacing: .08em;
  color: #2b5b86;
  user-select: all;          /* 点一下全选，方便复制 */
  -webkit-user-select: all;
}

/* ---------- 本周挑战（2026-10-19） ---------- */
.lt-chal {
  margin: 7px 0; padding: 9px 12px;
  border-radius: 11px;
  background: linear-gradient(180deg, #fdf8ff, #f6f0fb);
  border: 1px solid #ddd0ea;
}
.lt-chal-done { opacity: .65; background: var(--lt-panel-2); border-color: var(--lt-line); }
.lt-chal-h { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; }
.lt-chal-h b { font-size: 12.5px; font-weight: 700; color: var(--lt-text); }
.lt-chal-h i { font-style: normal; font-size: 11px; font-weight: 800; color: #6f4a9a; font-variant-numeric: tabular-nums; }
.lt-chal-bar {
  height: 5px; margin: 6px 0 5px;
  border-radius: 3px; overflow: hidden;
  background: rgba(140, 120, 170, .16);
}
.lt-chal-bar i {
  display: block; height: 100%;
  border-radius: 3px;
  background: linear-gradient(90deg, #b794e0, #8a5fc4);
  transition: width .4s ease;
}
.lt-chal-f { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.lt-chal-f span { font-size: 11px; color: var(--lt-muted); }
.lt-chal-need { color: #8a6b3a; font-weight: 600; }

/* ---------- 今日连击进度提示（2026-10-19） ----------
   差 1 件时给一个呼吸感的金色描边 —— "再做一件就有奖励"。
   ⚠️ 只改 border/box-shadow，不动 layout（顶栏是固定高度，改尺寸会抖）。 */
.lt-pill.lt-combo-near {
  border-color: #e0a020;
  box-shadow: 0 0 0 2px rgba(247, 201, 72, .35);
  animation: lt-combo-pulse 1.8s ease-in-out infinite;
}
.lt-pill.lt-combo-hit {
  border-color: #7fb069;
  box-shadow: 0 0 0 1px rgba(127, 176, 105, .3);
}
@keyframes lt-combo-pulse {
  0%, 100% { box-shadow: 0 0 0 2px rgba(247, 201, 72, .35); }
  50%      { box-shadow: 0 0 0 4px rgba(247, 201, 72, .14); }
}

/* ---------- 喷泉水花（2026-10-19） ----------
   喷泉原来是纯静态图形 —— 一个"在喷水的池子"静止不动，读起来像海报。
   ⚠️ 动画只能作用于【水柱/水珠那一组】，整块缩放会变成"呼吸"而不是"水"。
   ⚠️ 用 transform: scaleY + translateY 的组合做"水柱伸缩"，
      不改路径（改 d 属性要重建 SVG，而这个项目对重绘很敏感）。
   0.9s 一个循环、幅度很小（±4%）—— 水动起来但不能抽筋。 */
/* ⚠️ 不能按 stroke 色值选（water2 实际是 #3aa8d6，我第一版写错了两个色号）。
   改为：只给【没有 fill 的 path】（也就是水柱那些描边线）加动画 ——
   池底和水面是有 fill 的，它们不该伸缩。 */
.lt-fx-water path:not([fill]):not([fill="none"]),
.lt-fx-water path[fill="none"] {
  transform-box: fill-box;
  transform-origin: 50% 100%;
  animation: lt-water-pulse 1.5s ease-in-out infinite;
}
/* 水珠轻微上下浮（错开相位，看起来才不呆板） */
.lt-fx-water circle {
  transform-box: fill-box;
  transform-origin: 50% 50%;
  animation: lt-water-float 1.8s ease-in-out infinite;
}
.lt-fx-water circle:last-child { animation-delay: -.6s; }
.lt-fx-water circle:nth-last-child(2) { animation-delay: -.3s; }

@keyframes lt-water-pulse {
  0%, 100% { transform: scaleY(1); }
  45%      { transform: scaleY(1.045); }
  70%      { transform: scaleY(.98); }
}
@keyframes lt-water-float {
  0%, 100% { transform: translateY(0); opacity: .85; }
  50%      { transform: translateY(-1.6px); opacity: 1; }
}

/* ---------- 地标级道具标签（2026-10-19） ---------- */
.lt-landmark {
  display: inline-block;
  margin-top: 2px;
  padding: 1px 7px;
  border-radius: 999px;
  font-size: 10px; font-weight: 800; letter-spacing: .03em;
  background: linear-gradient(180deg, #ffe9a8, #f7c948);
  border: 1.5px solid #b8860b;
  color: #6b4a00;
}

/* ---------- 存款利息说明（2026-10-19） ---------- */
.lt-note-save {
  margin: 6px 0 2px; padding: 8px 11px;
  border-radius: 10px;
  font-size: 12px; line-height: 1.55;
  color: var(--lt-muted);
  background: var(--lt-panel-2);
  border: 1px solid var(--lt-line);
}
.lt-note-save b { color: var(--lt-text); font-variant-numeric: tabular-nums; }
.lt-note-save-on {
  background: linear-gradient(180deg, #f2f9ec, #e6f2dc);
  border-color: #cfe0c0;
  color: #4a6b3a;
}

/* ---------- 金币扩建（2026-10-19） ----------
   给金币一个长期目标：花金币给房子加楼层。 */
.lt-upg {
  margin: 8px 0 4px; padding: 10px 12px;
  border-radius: 12px;
  background: linear-gradient(180deg, #fffaf0, #fdf3e0);
  border: 1px solid #e8d9b8;
  display: flex; flex-direction: column; gap: 4px;
}
.lt-upg-h { display: flex; align-items: baseline; justify-content: space-between; }
.lt-upg-h b { font-size: 13px; font-weight: 700; color: var(--lt-text); }
.lt-upg-h i { font-style: normal; font-size: 11px; font-weight: 800; color: #b8860b; }
.lt-upg span { font-size: 12px; color: var(--lt-muted); line-height: 1.5; }
.lt-upg .lt-btn { margin-top: 4px; align-self: flex-start; }
.lt-upg-full { background: var(--lt-panel-2); border-color: var(--lt-line); }
.lt-upg-full b { color: var(--lt-muted); }

/* ---------- 雨天也要"湿"，不只是"下"（2026-10-19） ----------
   第一版只有雨丝和整体压暗 —— 看着像"有人在前面洒水"，
   地面和屋顶都是干的，缺了"被淋湿"这件事。
   补三样，全部走 CSS（零重绘、零新节点）：
     ① 路面变深、微微反光（湿沥青/湿土）
     ② 屋顶和树冠压低饱和度、加一点点高光（雨水覆膜的观感）
     ③ 整体对比度略降（雨天的空气里有水汽） */
.lt-world-wrap.lt-rain .lt-roads {
  filter: brightness(.86) saturate(.9) contrast(.96);
}
.lt-world-wrap.lt-rain .lt-place-obj svg,
.lt-world-wrap.lt-rain .lt-villa svg {
  filter: brightness(.97) saturate(.9);
}
/* 地面湿光：一条自上而下的极淡高光，压在岛的上半部 */
.lt-world-wrap.lt-rain .lt-ground {
  filter: brightness(.95) saturate(.94);
}
/* 雨天：一部分居民回屋避雨（约 40%，按 NPC id 稳定哈希决定）——
   这样雨天的小镇会有"人变少了"的感觉，而不是完全没人。
   ⚠️ 用 opacity + visibility 而不是 display:none：
      display 会触发布局，而这些 NPC 每帧都有 transform 动画。 */
.lt-world-wrap.lt-rain .lt-npc[data-rain-hide="1"] {
  opacity: 0;
  visibility: hidden;
  transition: opacity .6s ease, visibility 0s .6s;
}
/* 🔴 2026-10-19 修【第二处同类 bug】：
   这条和上面的夜间那条犯了同一个错 —— 没有 .lt-rain 前缀 ⇒
   晴天也给 14 个 NPC 常挂 0.6s 的 transition。
   配合初始渲染的 transform 跳变，造成"NPC 平移入场"。
   ⇒ 必须限定在雨/雪态下。晴天零 transition，入场就是硬落位。 */
.lt-world-wrap.lt-rain .lt-npc[data-rain-hide="1"],
.lt-world-wrap.lt-snow .lt-npc[data-rain-hide="1"] {
  transition: opacity .6s ease, visibility 0s 0s;
}

.lt-world-wrap.lt-rain .lt-char-wrap svg {
  /* 小人也会被淋：略暗一点点，不至于显脏 */
  filter: brightness(.97);
}

/* ---------- NPC 被点击的即时回应（2026-10-19 手感） ----------
   点 NPC 后要等小人走过去（1~2 秒）才说话，中间【毫无反馈】。
   点下去立刻让 NPC 弹一下 + 冒感叹号，确认"点到了"。
   ⚠️ 只动 scale 不动位置 —— 位置由 transform:translate3d 驱动，
      这里再改 translate 会把它顶飞（NPC 的位置动画在 tickNPCs 里）。 */
.lt-npc-nudge svg {
  animation: lt-npc-nudge .6s cubic-bezier(.3, 1.5, .4, 1);
  transform-origin: 50% 100%;
}
@keyframes lt-npc-nudge {
  0%   { transform: scale(1); }
  35%  { transform: scale(1.18) rotate(-4deg); }
  65%  { transform: scale(.96) rotate(2deg); }
  100% { transform: scale(1); }
}
/* ⚠️ 第一版 top:-6px、15px 直径 —— 实测被上方的区域路牌/房子挡住，
   截图里几乎看不见。改到 top:-20px、20px 直径，并加白色描边增加分离度。 */
.lt-npc-bang {
  position: absolute; left: 50%; top: -20px;
  transform: translateX(-50%);
  width: 20px; height: 20px;
  border-radius: 50%;
  background: linear-gradient(180deg, #ffe9a8, #f7c948);
  border: 2.5px solid #b8860b;
  box-shadow: 0 0 0 2px rgba(255, 252, 240, .9), 0 2px 6px rgba(120, 80, 10, .35);
  color: #6b4a00;
  font-size: 14px; font-weight: 900; line-height: 15px;
  text-align: center;
  pointer-events: none;
  animation: lt-npc-bang .75s cubic-bezier(.2, 1.4, .4, 1) forwards;
}
@keyframes lt-npc-bang {
  0%   { opacity: 0; transform: translateX(-50%) translateY(4px) scale(.4); }
  30%  { opacity: 1; transform: translateX(-50%) translateY(-4px) scale(1.1); }
  100% { opacity: 0; transform: translateX(-50%) translateY(-16px) scale(1); }
}

/* ---------- 落点标记（2026-10-19 手感） ----------
   点地面时在落点画一个快速收缩的圈 —— "我收到了"。
   650ms 内消失，不干扰视线。 */
/* ⚠️ 第一版用纯白半透明，在浅绿草地上几乎看不见（截图对照才发现）。
   改成【深色描边 + 暖白内芯】：深色在草绿上对比足够，暖白保证它不像"脏点"。 */
.lt-tap-mark {
  position: absolute;
  width: 38px; height: 38px;
  margin: -19px 0 0 -19px;
  border-radius: 50%;
  border: 3px solid rgba(94, 72, 44, .78);
  background: radial-gradient(circle, rgba(255, 246, 220, .92) 0%, rgba(255, 240, 200, .30) 46%, rgba(255,255,255,0) 70%);
  box-shadow: 0 0 0 2px rgba(255, 252, 240, .55), 0 2px 6px rgba(40, 50, 30, .25);
  pointer-events: none;
  animation: lt-tap-mark .6s cubic-bezier(.2, .8, .3, 1) forwards;
}
@keyframes lt-tap-mark {
  0%   { transform: scale(1.35); opacity: 0; }
  22%  { transform: scale(1);    opacity: 1; }
  100% { transform: scale(.42);  opacity: 0; }
}

/* ---------- 开场镜头（2026-10-19 首屏第一印象） ----------
   进入游戏的最初约 1.9 秒：小镇从透明淡入，镜头从稍远处缓推到位。
   ⚠️ 只加在 .lt-boot 上 —— 动画跑完 JS 会移除这个类，
      之后玩家拖地图/走动都不受影响。 */
.lt-boot .lt-world {
  transition: transform 1.5s cubic-bezier(.22, .9, .3, 1);
}
.lt-boot .lt-world-wrap > * {
  opacity: 0;
  transition: opacity 1.1s ease;
}
.lt-boot.lt-boot-in .lt-world-wrap > * { opacity: 1; }
.lt-boot .lt-top,
.lt-boot .lt-dock,
.lt-boot .lt-hint {
  animation: lt-boot-ui 1.4s ease .25s both;
}
@keyframes lt-boot-ui {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* ---------- 小镇编年史（2026-10-19） ----------
   按月分组的里程碑时间线。左侧一条竖线串起所有事件。 */
.lt-tl-month {
  font-size: 11.5px; font-weight: 800; letter-spacing: .04em;
  color: var(--lt-muted); margin: 12px 0 6px;
  padding-left: 2px;
}
.lt-tl-item {
  display: flex; align-items: baseline; gap: 8px;
  position: relative;
  padding: 7px 10px; margin-left: 10px;
  border-left: 2px solid rgba(190, 165, 120, .45);
  font-size: 12.5px;
}
.lt-tl-ic { flex: 0 0 auto; font-size: 13px; }
.lt-tl-d { flex: 0 0 auto; font-size: 11px; color: var(--lt-muted); font-variant-numeric: tabular-nums; min-width: 2.6em; }
.lt-tl-item b { flex: 1; min-width: 0; font-weight: 600; color: var(--lt-text); }
/* 不同 kind 给左侧竖线不同颜色 —— 一眼看出"这条路/这栋房/这个成就" */
.lt-tl-road   { border-left-color: #d8b46a; }
.lt-tl-build  { border-left-color: #7fb06a; }
.lt-tl-fam    { border-left-color: #8fc4a8; }
.lt-tl-streak { border-left-color: #e08b6a; }
.lt-tl-challenge { border-left-color: #a883d8; }

/* ---------- 底栏状态副标题（2026-10-19） ----------
   三态：有待办(徽章) / 今天已完成 / 还没有任务。 */
.lt-dock-sub { font-size: 10px; color: var(--lt-muted); }
.lt-dock-sub.lt-dock-empty {
  color: #b08a5a;
  font-weight: 600;
}

/* ---------- 已摆放道具的两个按钮（2026-10-19） ----------
   「移动」和「收回」并排，都在卡片底部。 */
.lt-item-btns { display: flex; gap: 6px; margin-top: 6px; }
.lt-item-btns .lt-btn { flex: 1; margin-top: 0 !important; }

/* ---------- 最近 4 周趋势（2026-10-19） ----------
   四根对比条 + 一句走势判断。让"我在变好还是变差"变得可见。 */
.lt-trend {
  display: flex; align-items: flex-end; gap: 10px;
  height: 92px; margin: 6px 0 2px;
  padding: 0 2px;
}
.lt-trend-col {
  flex: 1; display: flex; flex-direction: column;
  align-items: center; justify-content: flex-end;
  height: 100%; gap: 3px;
}
.lt-trend-bar {
  flex: 1; width: 100%; min-height: 6px;
  display: flex; align-items: flex-end;
  border-radius: 6px 6px 3px 3px;
  background: rgba(150, 130, 100, .10);
  overflow: hidden;
}
.lt-trend-bar i {
  display: block; width: 100%;
  border-radius: 6px 6px 3px 3px;
  background: linear-gradient(180deg, #ffd166, #f0a93b);
  transition: height .5s cubic-bezier(.3, 1.1, .4, 1);
}
/* 本周那根用强调色，与"过去的周"区分 */
.lt-trend-col:last-child .lt-trend-bar i {
  background: linear-gradient(180deg, #a8e06a, #6fae33);
}
.lt-trend-col b { font-size: 12px; font-weight: 800; color: var(--lt-text); }
.lt-trend-col span { font-size: 10px; color: var(--lt-muted); white-space: nowrap; }

/* ---------- 日回顾（2026-10-19） ----------
   日历格子可点，点开看那天完成了什么。 */
.lt-cal-cell.lt-cal-click { cursor: pointer; }
.lt-cal-cell.lt-cal-click:active { transform: scale(.93); }

.lt-day-item {
  display: flex; align-items: center; gap: 8px;
  padding: 8px 11px; margin: 5px 0;
  border-radius: 10px;
  background: var(--lt-panel-2); border: 1px solid var(--lt-line);
  font-size: 13px;
}
.lt-day-ok {
  flex: 0 0 auto; display: inline-flex;
  width: 18px; height: 18px; border-radius: 50%;
  align-items: center; justify-content: center;
  background: #dfF0cf; color: #4a7a2e;
}
.lt-day-item b { flex: 1; min-width: 0; font-weight: 600; color: var(--lt-text); }
.lt-day-item i { flex: 0 0 auto; font-style: normal; font-size: 11px; font-weight: 800; color: #8a6220; }
.lt-day-item em { flex: 0 0 auto; font-style: normal; font-size: 11px; color: var(--lt-muted); }

/* ---------- 全镇庆祝（2026-10-19） ----------
   连续成就（一周不断 / 一个月）达成时，所有房子依次亮一下。
   由 JS 按深度阶梯加 .lt-town-glow，这里只定义"亮"长什么样。
   ⚠️ 只做 brightness/saturate，不做 transform —— 房子同时缩放会显得乱。 */
.lt-town-glow {
  animation: lt-town-glow 1.1s cubic-bezier(.3, .1, .4, 1);
}
@keyframes lt-town-glow {
  0%   { filter: brightness(1) saturate(1); }
  28%  { filter: brightness(1.32) saturate(1.25) drop-shadow(0 0 6px rgba(255, 214, 110, .85)); }
  70%  { filter: brightness(1.12) saturate(1.08) drop-shadow(0 0 3px rgba(255, 214, 110, .45)); }
  100% { filter: brightness(1) saturate(1); }
}

/* 关键任务（×3）完成：闪光更强、范围更大、卡片轻微放大
   —— 光靠"+9 金币"的数字不够"重"，观感上要和普通任务区分开。 */
.lt-task.lt-task-just-done.lt-task-just-big {
  animation: lt-task-flash-big 1s cubic-bezier(.3,.9,.4,1);
}
@keyframes lt-task-flash-big {
  0%   { box-shadow: 0 0 0 0 rgba(247,201,72,.95), 0 1px 2px rgba(74,64,51,.06); transform: scale(1); }
  25%  { box-shadow: 0 0 0 9px rgba(247,201,72,.55), 0 10px 30px -6px rgba(247,201,72,.85); transform: scale(1.035); }
  60%  { box-shadow: 0 0 0 4px rgba(247,201,72,.28), 0 8px 22px -8px rgba(247,201,72,.5); transform: scale(1.012); }
  100% { box-shadow: 0 1px 2px rgba(74,64,51,.06), 0 6px 16px -6px rgba(74,64,51,.10); transform: scale(1); }
}

/* ---------------- 动画 ---------------- */
@keyframes lt-pop { from { transform: translateX(-50%) scale(.9); opacity: 0; } to { transform: translateX(-50%) scale(1); opacity: 1; } }
@keyframes lt-fade { from { opacity: 0; } to { opacity: 1; } }
@keyframes lt-slide { from { transform: translateY(24px); opacity: .6; } to { transform: translateY(0); opacity: 1; } }
@keyframes lt-float {
  0%   { transform: translate(-50%, 0) scale(.7); opacity: 0; }
  25%  { opacity: 1; }
  100% { transform: translate(-50%, -70px) scale(1.15); opacity: 0; }
}
@keyframes lt-hop {
  0%, 100% { transform: translateY(0); }
  35%      { transform: translateY(-7px); }
  70%      { transform: translateY(0); }
}
.lt-char-wrap.lt-hop { animation: lt-hop .5s ease-out; }

/* ---------------- 桌面适配 ---------------- */
@media (min-width: 640px) {
  body { max-width: 100%; }
  .lt-sheet { align-items: center; justify-content: center; }
  .lt-sheet-body {
    width: min(560px, 94vw);
    border-radius: 22px;
    border: 2.5px solid var(--lt-ink);
    max-height: 84vh;
    padding-bottom: 22px;
  }
  .lt-bubble { bottom: 20px; }
}

/* ---------------- 无障碍 ---------------- */
.lt-visually-hidden {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap;
}
:focus-visible { outline: 3px solid var(--lt-accent-2); outline-offset: 2px; border-radius: 6px; }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
  }
}
