/* ============================================================
   css/veggie.css — 「蔬菜拆拆乐：医院拆除行动」
   ------------------------------------------------------------
   命名一律 `vg-` 前缀（含美术层的 `va-`），避免和餐厅/农场/钓鱼撞车。
   本文件只管「游戏界面」的排版与动效；素材本身全部来自 GB.VArt（内联 SVG）。

   ★ 两条本项目踩过的规矩（写在这里当地雷标记）：
     ① SVG 元素绝不写 CSS transform —— 会覆盖 transform 属性（瞬移）。
        动效只作用于**内层 <g>** 或 HTML 元素；要给 SVG 加定位，
        用 CSS 变量 + transform 属性层面的写法（见 .vg-sl-pouch）。
     ② position:static 会让 <span> 退回行内 → width/max-width 失效。
        凡「把绝对定位元素改成 static 交给父级定位」的 override，
        必须显式补 display:inline-block / block。
   ============================================================ */

/* ============================================================
   一、工地场景（城市边缘里的「拆迁工地」区域）
   ============================================================ */
.vg-yard {
    position: fixed;
    inset: 0;
    z-index: 60;
    display: none;
    /* 底部工具栏高度（站台算「脚底线」要用它：脚底线不能低于「工具栏顶 + 名牌高」）。
       站台是工具栏的**兄弟**节点，所以变量必须挂在共同祖先 .vg-yard 上 ——
       写在 .vg-yard-tools 上站台读不到。
       实测按钮高 39px（一行）/ 87px（窄屏 2×2 两行，见文件末尾的媒体查询）；
       这里宽屏故意取 46px 留余量：偏大只会把物件抬得更高，不会压住。 */
    --vg-yard-toolbar: 46px;
}
.vg-yard.active { display: block; }

/* ⚠️ 行内 svg 兜底。
   `GB.VArt.i()` 已经把 width/height 烧进标签里（17px，跟字号走），这条只是第二道防线：
   万一将来哪个内联 svg 漏带尺寸，会退回 SVG 默认 300×150，
   把所在行盒撑到 ~180px 高 —— 表现是「文字容器突然很高、中间一片空白」。
   只在「确实没有 width 属性」时才介入，不去覆盖已写好的尺寸。 */
svg.va-i:not([width]) { width: 1.05em; height: 1.05em; vertical-align: -0.18em; }

.vg-yard-sky {
    position: absolute;
    inset: 0;
    background: linear-gradient(180deg, #a8d8ef 0%, #d9eef7 58%, #f3f0e2 100%);
}
.vg-yard-ground {
    position: absolute;
    left: 0; right: 0; bottom: 0;
    height: 34%;
    background: linear-gradient(180deg, #c8a878 0%, #b2905f 20%, #8a6c44 100%);
}
/* 中景建筑带（医院背景挂在这一层）
   ★ 这一层原来挂着 background-repeat / background-size 却**从来没有背景图**
     —— 「今日施工」的副标题一直是「医院拆除行动」，可工地本身没有医院。
     现在由 GB.VArt.yardHospitalSvg() 注入内联 SVG（按拆迁进度分 full/half/gone 三档），
     `slice` 裁切 + 100%/100% 铺满：宽屏看到整院，窄屏自动推近到主楼。 */
.vg-yard-skyline {
    position: absolute;
    left: 0; right: 0; bottom: 34%;
    height: 38%;
    opacity: 0.96;
}
/* 窄屏：背景整体压小一档。
   `slice` 的缩放由「容器高 / 300」决定，减小高度 = 医院跟着变小、
   同时露出更多楼体（不会被推得太近）。不这么做的话竖屏上医院会占满
   中部、和关卡地图/实验室那排道具糊在一起。
   ⚠️ 别再往下压（26% 试过、已回退）：skyline 的**底边**钉在地平线(34%)上，
   高度越小 ⇒ 顶边越低 ⇒ 院名牌整体**往下走**，反而更早被站台顶到。
   实测 360×640 上 29% 时院名牌 y≈340（站台顶 327，压住一截）、
   26% 时掉到 348（压得更多）。想让牌子露出得**加大**高度，不是减小。 */
@media (max-width: 640px) {
    .vg-yard-skyline { height: 29%; }
}
/* 矮屏（横屏手机 H≤460）反过来 —— 医院带要**加大**到 35%。
   这一档是审计 N1 逼出来的：640×360 上站台（弹弓 124 缩到 94，仍占 134px 高）
   的顶边正好落在 136px，而 29% 的医院带只到 133px —— **医院整个被站台压平**，
   横屏玩的时候「医院拆除行动」的背景等于不存在。加大带高让顶边上移，
   同时又更接近 viewBox 的宽高比（1600:300 ≈ 5.33），slice 裁掉的楼体反而更少。
   实测 640×360 / 844×390 均露出 >= 20px，N1 全绿。 */
@media (max-height: 460px) {
    .vg-yard-skyline { height: 35%; }
}
.vg-yard-skyline > .vg-yard-hosp {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    display: block;
}
/* ★ 只有「画到东西的地方」能点：整条天空带都吃掉点击的话，
   玩家点天上的云也会听见狗宝讲医院。cursor 可继承，写在根上即可。 */
.vg-yard-hosp { pointer-events: none; cursor: pointer; }
.vg-yard-hosp .va-yard-hosp { pointer-events: visiblePainted; }

/* ============================================================
   二·五、工地站台 —— 所有地面物件共用**同一条脚底线**
   ------------------------------------------------------------
   ★★ 这里是「几个按钮图标无序悬浮在空中」的根因，写清楚免得以后改回去：

   旧写法 `.vg-prop { position:absolute; transform:translate(-50%,-100%) }`
   里，`bottom:X%` 先把**元素盒的底边**放到离屏底 X% 的位置，
   紧接着 `translateY(-100%)` 又把它按**自身高度**往上顶了一次 ——
   于是「物件脚底离屏底 = X% + 自身高度」。
   物件高度从 88px(蔬菜箱) 到 145px(弹弓) 不等，同一排的脚底就差出 50 多 px。
   实测（1024×768）：弹弓飘在地平线上方 106px、关卡地图牌 92px，
   而礼盒反而沉到地平线**下方** 81px；430×900 上礼盒更是压住底部工具栏
   3136px²。百分比机位（P/P2 两套）就是在这种偏差下反复手调的，
   换个视口必然再散一次。

   现在的模型只有两条：
     ① `.vg-prop` 的 transform **只留 translateX(-50%)**（横向着色点仍居中），
        物件是站台的 flex item，由 `align-items:flex-end` 把底边对齐 ——
        bottom 从此就是「脚底离线」，所有物件必然同高。
     ② 站台的 bottom = 那条脚底线，纯 CSS 推导（见下），不靠 JS 手调机位。

   脚底线怎么算：
     常规取「地平线下沉一点」= 34%（地面层高度）− 标签高度 − 一个小下沉量
     （下沉量随屏高走）→ 物件站在医院基座**前面**，有纵深。
     再与「工具栏顶 + 余量」取 max：矮屏/横屏时优先保证不被底部 HUD 压住，
     宁可让它们贴着楼前站。`--vg-yard-toolbar` 与 `--sa-bottom` 都定义在
     容器 .vg-yard 上（站台是工具栏的**兄弟**节点，变量必须挂在共同祖先上）。
   ============================================================ */
.vg-yard-stage {
    position: absolute;
    left: 0;
    right: 0;
    bottom: max(
        calc(var(--vg-yard-toolbar, 46px) + var(--sa-bottom, 0px) + 10px),
        calc(34% - 24px - clamp(2px, 2.4vh, 22px))
    );
    width: 100%;
    max-width: 1000px;
    margin: 0 auto;
    box-sizing: border-box;
    display: flex;
    align-items: flex-end;              /* ★ 底边对齐 = 站在同一条脚底线上 */
    /* ★ 居中而**不是** space-between：物件横向间隙从此由 column-gap 决定，
       和屏幕多宽无关。理由（这是「手机有的看不全」的一半病因）：
       space-between 会把仅剩的间隙按剩余空间放大 —— 物件少的时候被推得
       稀稀拉拉、边上一件紧贴 padding 边（实测 640×360 上关卡地图牌贴到 x=19），
       物件多的时候又反过来把每一格挤到美术以下。居中 + 固定 gap 之后，
       「一格有多宽」只由 gap / padding / 美术设计宽决定，可算、可审计。 */
    justify-content: center;
    column-gap: clamp(6px, 2.4vw, 30px);
    padding: 0 clamp(6px, 3vw, 36px);
    pointer-events: none;                /* 站台本体不吃点击，只有物件吃 */
    /* ★★ 两个**正交**的缩放系数 —— 千万别再合并成一个变量。
       合并后「窄屏」和「矮屏」两个媒体查询会互相覆盖（谁写在后面谁生效），
       常见结果是「横屏小机 = 0.76 生效、窄屏 0.9 被吃掉」或者反过来。
       拆成两个名字、在 .vg-prop 里相乘，两档就能同时生效、自动取到更小的那个：
         --vg-fit        由**宽**决定（现在是恒 1，留作将来按宽度分档）
         --vg-art-scale  由**高**决定（横屏矮屏 0.76 / 矮竖屏 0.88）
       svg 宽度 = 设计宽 × --vg-fit × --vg-art-scale。 */
    --vg-fit: 1;
}
/* 矮屏：整体把美术缩一档，否则最高的弹弓会顶到顶部公告栏。
   两档 + 一档中间值，覆盖「横屏手机(≤460)」「矮竖屏(≤560)」两类；
   三条互斥（min/max-height 分段），不会有谁覆盖谁的问题。 */
@media (max-height: 460px) {
    .vg-yard-stage { --vg-art-scale: 0.76; }
}
@media (min-height: 461px) and (max-height: 560px) {
    .vg-yard-stage { --vg-art-scale: 0.88; }
}
/* 窄屏：一排排不下 6 格，收起两件「与底部工具栏功能重复」的物件：
     · 关卡地图 → 工具栏有 #vg-yard-mapbtn
     · 蔬菜实验室 → 工具栏有 #vg-yard-shopbtn
   留下的 4 格：蔬菜图鉴 · 狗宝 · 弹弓(主入口) · 拆迁成就 —— 顺序与宽屏一致，
   只是把最外侧两件收掉，所以两种形态看起来是同一个场景的远近关系。

   ★★ 阈值 639 → 799 的由来（别凭手感改回去）：
   6 格的美术设计宽合计 = 106(地图) + 104(图鉴) + 92(狗宝) + 124(弹弓) + 104(成就) + 104(实验室)
   = 634px；再加 5 个 column-gap(clamp(6px,2.4vw,30px)) 与两侧 padding(clamp(6px,3vw,36px))。
   代入后「6 格不缩水」需要宽度 ≈ 775px。
     · 639~799 这一段（640×360 横屏手机、667×375、768 竖屏平板）以前是**六格硬挤**：
       实测 640×360 上每件美术只剩 70~94px、名牌字号 10.88px —— 能点但很难看清，
       正是玩家说的「看不全」。
     · 换成 4 格后同尺寸下美术直接回到 79~124px（≈ 设计值的 0.76~1.0 倍），
       而且被收起的两件在底部工具栏里**都有等价入口**，功能一个没少。 */
@media (max-width: 799px) {
    .vg-yard-stage > .vg-prop[data-tier="extra"] { display: none; }
}
/* ★ 例外：**宽 ≥700 且不算矮（高 ≥561）**时把两件放回来。
   这类屏幕（768×1024 竖屏平板是典型）横排其实放得下六格 ——
   实测 768 宽下总需求 772px vs 可用 768px，只压掉 0.5%（美术 105.5px），
   肉眼与满尺寸无异。为这点像素牺牲两个入口不值得。
   而 740×360 / 640×360 这种「宽够但矮」的横屏手机仍然走上一档（4 格）：
   高度只有 360px 时，六格被 --vg-art-scale 缩完只剩 70~94px，看不清。 */
@media (min-width: 700px) and (min-height: 561px) {
    .vg-yard-stage > .vg-prop[data-tier="extra"] { display: flex; }
}

/* 工地上的可点物件（弹弓/蔬菜箱/关卡牌/实验室/成就）
   ★ 美术尺寸一律用 px 变量给（`--vg-art`），不能靠 SVG 自然尺寸：
     否则不同视口下实际像素高度差极大（手机 103~138px，桌面因为
     SVG 默认 300×150 会窜到几百 px）。这里用变量 + 安全网 max-width，
     矮屏再由 `.vg-yard-stage` 的 --vg-art-scale 统一缩档。 */
.vg-prop {
    position: relative;
    flex: 0 1 auto;
    min-width: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    cursor: pointer;
    transition: transform 0.16s ease;
    /* 从**底边**放大：物件是底边对齐的，用默认的中心原点放大时
       下半截会临时沉到脚底线以下（悬停时脚陷进地里）。 */
    transform-origin: bottom center;
    filter: drop-shadow(0 4px 6px rgba(90, 62, 34, 0.22));
    pointer-events: auto;
}
.vg-prop:hover { transform: scale(1.06); }
.vg-prop:active { transform: scale(0.96); }
.vg-prop svg {
    display: block;
    /* ★ 两个系数相乘（--vg-fit 管宽、--vg-art-scale 管高）—— 详见 .vg-yard-stage 里的说明。
       max-width:100% 只是安全网：正常情况下宽度由这里算好，不该轮到 flex 去压。 */
    width: calc(var(--vg-art, 104px) * var(--vg-fit, 1) * var(--vg-art-scale, 1));
    height: auto;
    max-width: 100%;
}
/* 主入口（弹弓）比其它物件大一点，方便点 */
#vg-yard-sling { --vg-art: 124px; }
/* 关卡地图牌矮一点，避免顶到顶部公告栏 */
#vg-yard-map { --vg-art: 106px; }
/* 名牌：
   ★ 旧版是 `position:absolute`，所以它**不参与物件宽度** ——
     物件宽度只由美术决定，标签宽了就横向溢出、压到隔壁物件的脸上。
     现在它是流内子元素：物件宽度 = max(美术宽, 标签宽)，
     「相邻物件不重叠」这件事由布局本身保证，不靠手调百分比。
     `width:max-content` 让药丸贴合文字（不是撑满物件宽），
     `max-width:100%` 是安全网（真挤不下时宁可省略号也不溢出画外）。 */
.vg-prop .vg-prop-tag,
.vg-yard-dog .vg-yard-dog-pad {
    display: block;
    width: max-content;
    max-width: 100%;
    margin-top: 2px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    font-size: clamp(9.5px, 1.7vw, 12px);
    /* ★ 行高必须显式给：名牌里有彩色 emoji，`line-height:normal` 会按
       「装了 emoji 的那个字体」算行盒 —— 于是只放一个空格当占位块的
       .vg-yard-dog-pad 会比真名牌矮 4px，狗宝的脚就比物件低 4px。
       写死行高后行盒高度只由 font-size 决定，两者必然等高。 */
    line-height: 1.35;
    font-weight: 700;
    color: #5c4030;
    background: rgba(253, 243, 224, 0.95);
    border: 2px solid #6b4a2f;
    border-radius: 999px;
    padding: 2px clamp(5px, 1.4vw, 10px);
    box-sizing: border-box;
    box-shadow: 0 2px 0 rgba(107, 74, 47, 0.25);
}
/* 名牌的长 / 短两版（由 JS 同时写进 DOM，CSS 只负责显隐）。
   ★ 为什么非得有短版，而不是一路缩字号：
     340px 以下一行 4 格，每格只剩 ~60~70px；而「🛒 蔬菜实验室」即使缩到 10px
     仍要 ~85px、「📖 蔬菜图鉴」要 ~69px —— 名牌一旦比美术还宽就顶不住、
     被 ellipsis 截肢（实测 280×653 截断 7px、320×568 压线 0px）。
     短版把它压到 ~50px，名牌从此不再是决定格宽的那一项。
   ★ 阈值取 339px 而不是 379px：360/375px 上美美术宽（78~95px）本来就压得住
     长名牌，没必要牺牲信息量；只有 340px 以下才真的需要换短版。 */
.vg-tag-short { display: none; }
@media (max-width: 339px) {
    .vg-tag-long { display: none; }
    .vg-tag-short { display: inline; }
}
/* 狗宝的名牌占位块：只占高度、不显示。
   ★ 它必须自己撑出一个**行盒**：
     · 空的 block 什么行盒都没有 → 高度只剩 padding+border = 8px；
     · 加一个不换行空格的伪元素后有行盒了，但只按普通字体算 → 18px；
     · 真名牌里是彩色 emoji，`line-height:normal` 会按 emoji 字体的度量算 → 22px。
   三档差出 14px，狗宝的脚就会比物件低一截。所以上面把 line-height 写死，
   这里再补一个空格伪元素 —— 两者高度此后只由 font-size 决定，必然相等。 */
.vg-yard-dog .vg-yard-dog-pad { visibility: hidden; }
.vg-yard-dog .vg-yard-dog-pad::before { content: "\00a0"; }
.vg-prop.is-badge::after {
    content: '';
    position: absolute;
    top: -4px; right: -4px;
    width: 12px; height: 12px;
    border-radius: 50%;
    background: #e05a52;
    border: 2px solid #fff;
    animation: vg-pulse 1.5s ease-in-out infinite;
}
@keyframes vg-pulse {
    0%, 100% { transform: scale(1); opacity: 1; }
    50% { transform: scale(1.35); opacity: 0.75; }
}

/* 场景顶部公告牌：今日施工
   ★ 高度必须可控：主行 + 副行各自 nowrap、各占一行，
     整块高度 = 行数 × 行高 + padding，不再随视口高度或折行乱涨。
     实测（430×900 / 1024×768）均稳定在 56px 上下。 */
.vg-yard-board {
    position: absolute;
    left: 50%;
    top: calc(12px + var(--sa-top, 0px));
    transform: translateX(-50%);
    background: #fdf3e0;
    border: 3px solid #6b4a2f;
    border-radius: 12px;
    padding: 6px 18px;
    font-size: 14px;
    font-weight: 700;
    color: #5c4030;
    box-shadow: 0 4px 0 rgba(107, 74, 47, 0.3);
    text-align: center;
    max-width: min(92vw, 460px);
    box-sizing: border-box;
}
/* 主行：一行放不下就省略，绝不折行（折行 = 高度翻倍压住物件） */
.vg-yard-board-h {
    display: block;
    font-size: 14px;
    line-height: 1.35;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.vg-yard-board small {
    display: block;
    font-size: 11px;
    line-height: 1.35;
    font-weight: 400;
    color: #8a6640;
    margin-top: 1px;
    white-space: nowrap;   /* ★ 不加这句，窄屏上「今日施工…」会折成两行、牌子涨到 249px 高压住物件 */
    overflow: hidden;
    text-overflow: ellipsis;
}

/* 狗宝 NPC（工地里站着那只）
   ★ 它现在是站台的 flex item（不再是 absolute + translateX 居中），
     这样它的脚也落在同一条脚底线上。
     但它没有名牌，而名牌是**流内**元素、会把美术底边垫高约 22px ——
     所以给它一个「与名牌同尺寸的隐形占位块」(.vg-yard-dog-pad)。
     占位块的样式与 .vg-prop-tag 写在同一条规则里（同一份 font/padding/border），
     不会各写一套然后慢慢漂移。 */
.vg-yard-dog {
    position: relative;              /* 对话气泡以它为定位基准 */
    flex: 0 1 auto;
    min-width: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    cursor: pointer;
    pointer-events: auto;
    animation: vg-dog-bob 2.6s ease-in-out infinite;
}
.vg-yard-dog .vg-yard-dog-art {
    width: calc(92px * var(--vg-fit, 1) * var(--vg-art-scale, 1));
    max-width: 100%;
    aspect-ratio: 1 / 1;      /* 窄屏被 flex 压缩时，宽高一起缩（不然会拉成竖条） */
}
.vg-yard-dog .vg-yard-dog-art > .dog-holder { width: 100%; height: 100%; }
@keyframes vg-dog-bob {
    0%, 100% { transform: translateY(0); }
    50% { transform: translateY(-5px); }
}
/* 长按/点击狗宝冒的对话气泡
   ★ 宽度必须真的跟着屏宽缩，不能给死值：
     实测「狗宝头顶这条横带」的空档宽度约为 126px(390屏) / 201px(430屏) / 294px(560屏)。
     给死 186px 在 390px 屏上必然压住旁边的弹弓（踩过）。
     用 clamp(right, 46vw, 230px)：
       390px → 179px… 仍偏大，所以下界给 34vw 兜住最窄屏；
     综合取 44vw 且上不超 230px、下不低于 150px，交给 fitBubble 再做一次实测横移。 */
.vg-yard-bubble {
    position: absolute;
    left: 50%;
    bottom: 106%;
    transform: translateX(-50%);
    background: #fff;
    border: 2.5px solid #6b4a2f;
    border-radius: 12px;
    padding: 6px 10px;
    font-size: 12.5px;
    line-height: 1.4;
    color: #5c4030;
    width: max-content;
    max-width: clamp(150px, 42vw, 230px);
    box-shadow: 0 3px 0 rgba(107, 74, 47, 0.22);
    /* ★ 会说话的覆盖层**绝不吃点击**：
       气泡挂在狗宝身上、宽度可到 230px，一旦飘到某个物件上面，
       那个物件就点不动了（玩家报「点不动」时最容易被误判成布局问题）。
       它是纯展示层，一律让点击穿过去。 */
    pointer-events: none;
    animation: vg-bubble-in 0.24s ease-out;
}
.vg-yard-bubble::after {
    content: '';
    position: absolute;
    left: 50%;
    bottom: -9px;
    transform: translateX(-50%);
    border: 8px solid transparent;
    border-top-color: #6b4a2f;
    border-bottom: 0;
}
@keyframes vg-bubble-in {
    from { opacity: 0; transform: translateX(-50%) translateY(6px); }
    to { opacity: 1; transform: translateX(-50%) translateY(0); }
}

/* ============================================================
   二、关卡地图（36 关选关面板）
   ============================================================ */
.vg-map { display: flex; flex-direction: column; gap: 14px; }

.vg-map-summ {
    display: flex;
    align-items: center;
    gap: 12px;
    flex-wrap: wrap;
    background: linear-gradient(135deg, #fdf3e0, #f6e6cc);
    border: 2.5px solid #6b4a2f;
    border-radius: 14px;
    padding: 10px 14px;
}
.vg-map-summ .vg-sum-item {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: 13px;
    font-weight: 700;
    color: #5c4030;
}
.vg-map-summ .vg-sum-item b { font-size: 16px; color: #c0563c; }
.vg-map-summ svg { flex: 0 0 auto; }
.vg-map-summ .vg-sum-prog {
    flex: 1 1 140px;
    min-width: 120px;
    height: 9px;
    border-radius: 999px;
    background: #e6d6bc;
    overflow: hidden;
    border: 1.5px solid #b99f7c;
}
.vg-map-summ .vg-sum-prog i {
    display: block;
    height: 100%;
    border-radius: 999px;
    background: linear-gradient(90deg, #f0c05a, #e0904a);
    transition: width 0.5s cubic-bezier(0.34, 1.3, 0.64, 1);
}

.vg-chapter {
    border: 2.5px solid #6b4a2f;
    border-radius: 14px;
    background: #fffaf0;
    overflow: hidden;
}
.vg-chapter-head {
    display: flex;
    align-items: center;
    gap: 9px;
    padding: 9px 14px;
    background: linear-gradient(135deg, #f0c37a, #e0a75a);
    border-bottom: 2px solid #6b4a2f;
    cursor: pointer;
    user-select: none;
}
.vg-chapter-head .vg-ch-ico {
    font-size: 19px;
    line-height: 1;
}
.vg-chapter-head .vg-ch-name {
    font-size: 14px;
    font-weight: 700;
    color: #5c4030;
}
.vg-chapter-head .vg-ch-sub {
    font-size: 11px;
    color: #7a5a3c;
    font-style: italic;
}
.vg-chapter-head .vg-ch-stat {
    margin-left: auto;
    font-size: 12px;
    font-weight: 700;
    color: #5c4030;
    background: rgba(255, 255, 255, 0.6);
    border-radius: 999px;
    padding: 2px 10px;
    white-space: nowrap;
}
.vg-chapter-head .vg-ch-caret {
    font-size: 12px;
    color: #7a5a3c;
    transition: transform 0.2s ease;
}
.vg-chapter.collapsed .vg-ch-caret { transform: rotate(-90deg); }
.vg-chapter.collapsed .vg-ch-grid { display: none; }

.vg-ch-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(96px, 1fr));
    gap: 9px;
    padding: 12px;
}

.vg-lv {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 3px;
    padding: 9px 6px 7px;
    border: 2.5px solid #6b4a2f;
    border-radius: 12px;
    background: linear-gradient(180deg, #fffdf7, #f6ecd8);
    cursor: pointer;
    transition: transform 0.14s ease, box-shadow 0.14s ease;
    box-shadow: 0 3px 0 rgba(107, 74, 47, 0.22);
    text-align: center;
}
.vg-lv:hover { transform: translateY(-3px); box-shadow: 0 6px 0 rgba(107, 74, 47, 0.22); }
.vg-lv:active { transform: translateY(0); box-shadow: 0 2px 0 rgba(107, 74, 47, 0.22); }
.vg-lv .vg-lv-no {
    font-size: 10px;
    font-weight: 700;
    color: #a08a6c;
    letter-spacing: 0.5px;
}
.vg-lv .vg-lv-name {
    font-size: 12px;
    font-weight: 700;
    color: #5c4030;
    line-height: 1.25;
    word-break: break-all;
}
.vg-lv .vg-lv-stars {
    display: flex;
    gap: 1px;
    margin-top: 1px;
}
.vg-lv .vg-lv-diff {
    position: absolute;
    top: 5px; right: 6px;
    font-size: 9px;
    font-weight: 700;
    color: #fff;
    background: #c0563c;
    border-radius: 999px;
    padding: 1px 5px;
    line-height: 1.4;
}
.vg-lv.is-done {
    background: linear-gradient(180deg, #fffbe9, #f6ebc4);
    border-color: #c9a44a;
}
.vg-lv.is-locked {
    filter: grayscale(0.75);
    opacity: 0.62;
    cursor: not-allowed;
}
.vg-lv.is-next {
    border-color: #e0904a;
    box-shadow: 0 3px 0 rgba(224, 144, 74, 0.45), 0 0 0 3px rgba(240, 192, 90, 0.35);
    animation: vg-next-glow 1.8s ease-in-out infinite;
}
@keyframes vg-next-glow {
    0%, 100% { box-shadow: 0 3px 0 rgba(224, 144, 74, 0.45), 0 0 0 3px rgba(240, 192, 90, 0.35); }
    50% { box-shadow: 0 3px 0 rgba(224, 144, 74, 0.45), 0 0 0 8px rgba(240, 192, 90, 0.1); }
}
.vg-lv .vg-lv-lock {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    pointer-events: none;
}
.vg-lv .vg-lv-lock svg { width: 30px; height: 30px; opacity: 0.85; }

/* ============================================================
   三、对局舞台
   ============================================================ */
.vg-stagewrap {
    position: relative;
    width: 100%;
    border-radius: 12px;
    overflow: hidden;
    background: #b9d6e8;
    border: 2.5px solid #6b4a2f;
    /* 世界 900×520 ≈ 1.73:1 */
    aspect-ratio: 900 / 520;
    touch-action: none;   /* 拖动弹弓时不要触发浏览器滚动/缩放 */
    user-select: none;
}
.vg-stage {
    position: absolute;
    inset: 0;
    display: block;
    width: 100%;
    height: 100%;
}
.vg-stage svg { display: block; width: 100%; height: 100%; }

/* 舞台顶部 HUD：剩余蔬菜 / 目标进度 / 破坏率
   ★ 高度必须可预测：单行不换行 + 省略，绝不允许折成两行
     （折行会让 HUD 从 27px 涨到 62px，直接盖住上半屏可玩区 —— 窄屏实测踩过）。
     做法：flex-wrap: nowrap + 各 chip 自身 nowrap + 溢出隐藏。
     次要信息（连锁）不进 HUD，改由 fx 层飘字表达。 */
.vg-hud {
    position: absolute;
    left: 8px;
    right: 8px;
    top: 6px;
    display: flex;
    align-items: center;
    gap: 6px;
    flex-wrap: nowrap;
    overflow: hidden;
    pointer-events: none;
}
.vg-hud-chip {
    display: flex;
    align-items: center;
    gap: 4px;
    flex: 0 1 auto;
    min-width: 0;
    /* ★ 不透明度 0.94 → 0.97：HUD 会盖在各种装饰（医院招牌等）之上，
       半透明底会让背后的字透出来形成「两行字叠在一起」的脏观感。 */
    background: rgba(253, 243, 224, 0.97);
    border: 2px solid #6b4a2f;
    border-radius: 999px;
    padding: 2px 9px;
    font-size: 11.5px;
    font-weight: 700;
    color: #5c4030;
    box-shadow: 0 2px 0 rgba(107, 74, 47, 0.25);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.vg-hud-chip svg { width: 15px; height: 15px; flex: 0 0 auto; }
.vg-hud-chip.ok { border-color: #4f9c46; color: #37702f; background: rgba(232, 250, 228, 0.95); }
.vg-hud-chip.warn { border-color: #d99b2a; color: #8a5c14; background: rgba(255, 246, 218, 0.96); }
.vg-hud-chip.bad { border-color: #c0563c; color: #a8402c; background: rgba(255, 236, 232, 0.96); }

/* ★ 剩余发射胶囊：必须能真正收缩，否则窄屏会被裁掉尾部（实测 390/360px 裁切）。
   做法：flex-shrink 允许收缩 + min-width:0（flex 项默认 min-width:auto，会拒绝收缩到内容宽度以下）
   + 内部 span 也允许收缩。仅靠 overflow:hidden 只会「藏起来」，检测层仍判为裁切。 */
.vg-hud-shots {
    margin-left: auto;
    display: flex;
    gap: 2px;
    align-items: center;
    flex: 0 1 auto;
    min-width: 0;
    background: rgba(253, 243, 224, 0.94);
    border: 2px solid #6b4a2f;
    border-radius: 999px;
    padding: 2px 8px;
    box-shadow: 0 2px 0 rgba(107, 74, 47, 0.25);
    max-width: 52%;
    overflow: hidden;
}
.vg-hud-shots > span { flex: 0 1 auto; min-width: 0; display: block; }
.vg-hud-shots svg { width: 13px; height: 13px; flex: 0 0 auto; display: block; }
.vg-hud-shots .spent { opacity: 0.24; filter: grayscale(1); }

/* ============================================================
   三·五、舞台下方的「运行条」：目标清单 + 蔬菜选择器 + 说明
   ------------------------------------------------------------
   ★★ 为什么不放在舞台里：
     旧版把 .vg-goals（左中）和 .vg-picker（底部中央）都 absolute 贴在
     .vg-stagewrap 内部。宽屏（stage ~305px 高）勉强能用，
     窄屏（stage 只剩 ~173px 高）这两条面板直接盖掉半个可玩区域，
     表现就是用户说的「所选的蔬菜标识会在部分分辨率下挡住画面」。
     移到舞台上方的独立条后，任何分辨率都不会与画面重叠，
     并且还顺手解决了「选菜时要抬头看画面、挑菜要低头找按钮」的来回切换。
   ============================================================ */
.vg-runbar {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
    margin-top: 8px;
    /* ★★ 必须钉死 100% 宽 + min-width:0：
       .gb-modal-body 是 flex column + align-items:center，给子元素的宽度是「收缩到内容宽」。
       在 ≤620px 我们又把 .vg-runbar 改成 flex column + align-items:stretch，
       此时「容器宽度」由**最宽那个子项的 min-content** 决定 ——
       而 .vg-picker 里有 8 个 38px 圆 + 间距 ≈ 364px，
       于是 runbar 被撑到 368px，比 360px 视口还宽，左右各溢出 4px（实测踩过）。
       钉死 width:100% + min-width:0 后，runbar 服从父级 300px，
       再由子项自己（picker 横向滚动 / goal 折行）消化内容。 */
    width: 100%;
    max-width: 100%;
    min-width: 0;
    box-sizing: border-box;
}

/* 目标清单（runbar 内，左） */
.vg-goals {
    display: flex;
    flex-direction: column;
    gap: 3px;
    flex: 1 1 160px;
    min-width: 0;
    align-self: flex-start;
}
.vg-goal {
    display: flex;
    align-items: center;
    gap: 5px;
    background: rgba(253, 243, 224, 0.9);
    border: 2px solid #6b4a2f;
    border-radius: 9px;
    padding: 3px 9px;
    font-size: 11.5px;
    font-weight: 700;
    color: #5c4030;
    box-shadow: 0 2px 0 rgba(107, 74, 47, 0.2);
    line-height: 1.4;
    /* ★ 长目标文案（如 keep_intact 的长句）必须能折行，
         不能撑破 runbar；这里让它在容器内换行并允许收缩。 */
    min-width: 0;
    overflow-wrap: anywhere;
}
.vg-goal.done {
    border-color: #4f9c46;
    color: #37702f;
    background: rgba(232, 250, 228, 0.92);
}
.vg-goal.done::before { content: '✓ '; }

/* 蔬菜选择器（runbar 内，右） */
.vg-picker {
    display: flex;
    gap: 6px;
    padding: 5px 9px;
    background: rgba(253, 243, 224, 0.94);
    border: 2.5px solid #6b4a2f;
    border-radius: 999px;
    box-shadow: 0 3px 0 rgba(107, 74, 47, 0.28);
    flex: 0 1 auto;
    max-width: 100%;
    overflow-x: auto;
    scrollbar-width: none;
}
.vg-picker::-webkit-scrollbar { display: none; }
.vg-pick {
    position: relative;
    flex: 0 0 auto;
    width: 40px;
    height: 40px;
    border-radius: 50%;
    border: 2.5px solid #b99f7c;
    background: #fffdf7;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: transform 0.14s ease, border-color 0.14s ease;
}
.vg-pick:hover { transform: translateY(-3px); }
.vg-pick.sel {
    border-color: #e0904a;
    background: #fff4d8;
    box-shadow: 0 0 0 3px rgba(240, 192, 90, 0.4);
}
.vg-pick svg { width: 30px; height: 30px; }
.vg-pick .vg-pick-n {
    position: absolute;
    right: -2px;
    bottom: -2px;
    min-width: 15px;
    height: 15px;
    border-radius: 999px;
    background: #6b4a2f;
    color: #fff;
    font-size: 9.5px;
    font-weight: 700;
    line-height: 15px;
    text-align: center;
    padding: 0 3px;
}
.vg-pick.is-disabled { opacity: 0.4; cursor: not-allowed; }

/* 蔬菜提示条（runbar 内，占满剩余宽度） */
.vg-vegtip {
    flex: 1 1 190px;
    min-width: 0;
    background: rgba(255, 255, 255, 0.94);
    border: 2px solid #6b4a2f;
    border-radius: 10px;
    padding: 5px 9px;
    font-size: 11px;
    line-height: 1.45;
    color: #5c4030;
    box-shadow: 0 2px 0 rgba(107, 74, 47, 0.2);
    pointer-events: none;
    overflow-wrap: anywhere;
}
.vg-vegtip b { color: #c0563c; }

/* 关键提示（如「再点一下加速！」） */
.vg-flash {
    position: absolute;
    left: 50%;
    top: 46%;
    transform: translate(-50%, -50%);
    background: rgba(192, 86, 60, 0.94);
    color: #fff;
    font-size: 15px;
    font-weight: 700;
    padding: 8px 20px;
    border-radius: 999px;
    border: 2.5px solid #fff;
    box-shadow: 0 4px 12px rgba(60, 38, 20, 0.3);
    pointer-events: none;
    animation: vg-flash-in 0.3s ease-out;
    white-space: nowrap;
}
@keyframes vg-flash-in {
    from { opacity: 0; transform: translate(-50%, -50%) scale(0.82); }
    to { opacity: 1; transform: translate(-50%, -50%) scale(1); }
}

/* 关卡标题浮层（进场时短暂出现）
   ★ 位置从 38% 上移到 13%：原来正好压在**建筑结构**上
     （结构集中在画面中下部），进场那 2 秒玩家想先看「这关长什么样」
     却被标题条挡住 → 改成贴在顶部安全区，只占天空部分。 */
.vg-intro {
    position: absolute;
    left: 50%;
    top: 13%;
    transform: translate(-50%, -50%);
    text-align: center;
    pointer-events: none;
    padding: 10px 26px;
    border-radius: 14px;
    background: rgba(40, 28, 18, 0.42);
    backdrop-filter: blur(2px);
    animation: vg-intro-in 0.45s cubic-bezier(0.34, 1.3, 0.64, 1);
}
.vg-intro .vg-intro-no {
    font-size: 12px;
    font-weight: 700;
    letter-spacing: 2px;
    color: #fdf3e0;
    text-shadow: 0 2px 4px rgba(60, 38, 20, 0.5);
}
.vg-intro .vg-intro-name {
    font-size: 26px;
    font-weight: 700;
    color: #fff;
    text-shadow: 0 3px 0 #a8402c, 0 5px 12px rgba(60, 38, 20, 0.45);
}
.vg-intro .vg-intro-desc {
    font-size: 12.5px;
    color: #fff7e8;
    margin-top: 3px;
    text-shadow: 0 2px 4px rgba(60, 38, 20, 0.5);
}
@keyframes vg-intro-in {
    from { opacity: 0; transform: translate(-50%, -50%) scale(0.78); }
    to { opacity: 1; transform: translate(-50%, -50%) scale(1); }
}

/* ============================================================
   四、结算面板
   ============================================================ */
.vg-result {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 11px;
    padding: 8px 4px 14px;
    text-align: center;
}
.vg-result .vg-res-title {
    font-size: 22px;
    font-weight: 700;
    color: #5c4030;
}
.vg-result .vg-res-title.win { color: #37702f; }
.vg-result .vg-res-title.lose { color: #a8402c; }
.vg-result .vg-res-stars {
    display: flex;
    gap: 10px;
    align-items: center;
    min-height: 54px;
}
.vg-result .vg-res-stars svg {
    width: 54px;
    height: 54px;
    transform-origin: center;
}
.vg-result .vg-res-stars .on {
    animation: vg-star-pop 0.5s cubic-bezier(0.34, 1.6, 0.64, 1) backwards;
}
.vg-result .vg-res-stars .on:nth-child(2) { animation-delay: 0.18s; }
.vg-result .vg-res-stars .on:nth-child(3) { animation-delay: 0.36s; }
@keyframes vg-star-pop {
    0% { opacity: 0; transform: scale(0.3) rotate(-40deg); }
    70% { transform: scale(1.18) rotate(6deg); }
    100% { opacity: 1; transform: scale(1) rotate(0); }
}
.vg-res-lines {
    display: flex;
    flex-direction: column;
    gap: 5px;
    width: 100%;
    max-width: 380px;
}
.vg-res-line {
    display: flex;
    align-items: center;
    gap: 8px;
    background: #fffaf0;
    border: 2px solid #d8c6a6;
    border-radius: 10px;
    padding: 7px 12px;
    font-size: 13px;
    color: #5c4030;
    text-align: left;
}
.vg-res-line .vg-rl-ico { flex: 0 0 auto; }
.vg-res-line .vg-rl-ico svg { width: 22px; height: 22px; display: block; }
.vg-res-line .vg-rl-txt { flex: 1 1 auto; }
.vg-res-line .vg-rl-val { font-weight: 700; color: #c0563c; }
.vg-res-line.no { opacity: 0.55; }
.vg-res-line.no .vg-rl-val { color: #a08a6c; }
.vg-res-cond {
    display: flex;
    flex-direction: column;
    gap: 3px;
    width: 100%;
    max-width: 380px;
    font-size: 11.5px;
    color: #8a6640;
}
.vg-res-cond div { display: flex; gap: 6px; align-items: baseline; }
.vg-res-cond .yes { color: #37702f; font-weight: 700; }
.vg-res-cond .no { color: #b09070; }

.vg-res-btns {
    display: flex;
    gap: 9px;
    flex-wrap: wrap;
    justify-content: center;
    margin-top: 2px;
}

/* 剧情台词条 */
.vg-story {
    display: flex;
    align-items: flex-start;
    gap: 9px;
    background: linear-gradient(135deg, #fff6e2, #f6e6cc);
    border: 2px solid #c9a44a;
    border-radius: 11px;
    padding: 9px 13px;
    font-size: 13px;
    line-height: 1.6;
    color: #5c4030;
    width: 100%;
    max-width: 420px;
    text-align: left;
}
.vg-story::before {
    content: '🐾';
    flex: 0 0 auto;
    font-size: 15px;
}

/* ============================================================
   五、商店
   ============================================================ */
.vg-shop-head {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
    background: linear-gradient(135deg, #fdf3e0, #f0dcb8);
    border: 2.5px solid #6b4a2f;
    border-radius: 13px;
    padding: 10px 14px;
    margin-bottom: 12px;
}
.vg-shop-head .vg-sh-bal {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: 16px;
    font-weight: 700;
    color: #8a5c14;
}
.vg-shop-head .vg-sh-bal svg { width: 26px; height: 26px; }
.vg-shop-head .vg-sh-note {
    margin-left: auto;
    font-size: 11.5px;
    color: #8a6640;
    text-align: right;
    line-height: 1.5;
}

.vg-shop-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(178px, 1fr));
    gap: 11px;
}
.vg-shop-item {
    display: flex;
    gap: 10px;
    align-items: flex-start;
    border: 2.5px solid #6b4a2f;
    border-radius: 13px;
    background: linear-gradient(180deg, #fffdf7, #f8eeda);
    padding: 11px;
    transition: transform 0.14s ease;
}
.vg-shop-item:hover { transform: translateY(-2px); }
.vg-shop-item .vg-si-ico {
    flex: 0 0 auto;
    width: 42px;
    height: 42px;
    display: flex;
    align-items: center;
    justify-content: center;
    background: #fff;
    border: 2px solid #d8c6a6;
    border-radius: 11px;
    font-size: 22px;
    line-height: 1;
}
.vg-shop-item .vg-si-body { flex: 1 1 auto; min-width: 0; }
.vg-shop-item .vg-si-name {
    font-size: 14px;
    font-weight: 700;
    color: #5c4030;
    margin-bottom: 2px;
}
.vg-shop-item .vg-si-desc {
    font-size: 11.5px;
    line-height: 1.5;
    color: #7a5a3c;
    margin-bottom: 7px;
}
.vg-shop-item .vg-si-foot {
    display: flex;
    align-items: center;
    gap: 7px;
    flex-wrap: wrap;
}
.vg-shop-item .vg-si-price {
    display: flex;
    align-items: center;
    gap: 4px;
    font-size: 13px;
    font-weight: 700;
    color: #8a5c14;
}
.vg-shop-item .vg-si-price svg { width: 18px; height: 18px; }
.vg-shop-item .vg-si-own {
    font-size: 11px;
    font-weight: 700;
    color: #37702f;
    background: #e8fae4;
    border: 1.5px solid #4f9c46;
    border-radius: 999px;
    padding: 1px 8px;
}
.vg-shop-item.is-owned { border-color: #4f9c46; background: linear-gradient(180deg, #f6fdf4, #e9f7e4); }
.vg-shop-item.is-maxed { opacity: 0.72; }

/* ============================================================
   六、蔬菜图鉴 / 关卡信息弹层
   ============================================================ */
.vg-vegdex {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
    gap: 10px;
}
.vg-vegdex-item {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 5px;
    border: 2.5px solid #6b4a2f;
    border-radius: 13px;
    background: linear-gradient(180deg, #fffdf7, #f8eeda);
    padding: 12px 9px;
    text-align: center;
}
.vg-vegdex-item svg { width: 52px; height: 52px; }
.vg-vegdex-item .vg-vd-name {
    font-size: 14px;
    font-weight: 700;
    color: #5c4030;
}
.vg-vegdex-item .vg-vd-kind {
    font-size: 10.5px;
    font-weight: 700;
    color: #fff;
    background: #e0904a;
    border-radius: 999px;
    padding: 1px 9px;
}
.vg-vegdex-item .vg-vd-desc {
    font-size: 11.5px;
    line-height: 1.5;
    color: #7a5a3c;
}
.vg-vegdex-item .vg-vd-ability {
    font-size: 11px;
    line-height: 1.5;
    color: #a8402c;
    background: #fff0e6;
    border: 1.5px dashed #e0a08a;
    border-radius: 9px;
    padding: 5px 8px;
    text-align: left;
}

/* 关卡信息：结构件图例 */
.vg-legend {
    display: flex;
    flex-wrap: wrap;
    gap: 7px 14px;
    margin-top: 8px;
}
.vg-legend-item {
    display: flex;
    align-items: center;
    gap: 5px;
    font-size: 12px;
    color: #5c4030;
}
.vg-legend-item svg { width: 22px; height: 22px; }

/* 关卡提示条 */
.vg-hint {
    display: flex;
    align-items: flex-start;
    gap: 8px;
    background: #eef7ff;
    border: 2px solid #8fc6dd;
    border-radius: 11px;
    padding: 9px 12px;
    font-size: 12.5px;
    line-height: 1.6;
    color: #3f5f70;
    margin-top: 10px;
}
.vg-hint svg { width: 24px; height: 24px; flex: 0 0 auto; }

/* ============================================================
   七、工地场景里的按钮条（回到地图 / 关卡地图 / 实验室 / 训练场）
   ------------------------------------------------------------
   ★ 旧写法是 `display:flex; flex-wrap:wrap`，而按钮里的文字默认会折行 ——
     于是「一行摆不下」不是把按钮挤小，而是**把文字拆成两行**：
     实测 430×900 上工具栏涨到 215×219（三行高）、1024×768 上也莫名折成两行。
     膨胀出来的高度正好压住地上的礼盒（重叠 3136px²），
     而且按钮条高度不确定 ⇒ 站台没法拿它算脚底线。
     现在改成确定性网格：宽屏一行四格、窄屏 2×2，按钮文字 nowrap。
   ============================================================ */
.vg-yard-tools {
    position: absolute;
    left: 50%;
    bottom: calc(16px + var(--sa-bottom, 0px));
    transform: translateX(-50%);
    display: grid;
    grid-template-columns: repeat(4, auto);
    justify-content: center;
    align-items: stretch;
    gap: 9px;
}
.vg-yard-tools .gb-btn {
    white-space: nowrap;    /* ★ 不加这句，窄屏上按钮文字会被拆行、工具栏涨成三行 */
    line-height: 1.25;      /* 高度必须可算：站台的脚底线依赖 --vg-yard-toolbar */
    padding: 8px 16px;
    font-size: 0.95rem;
}
/* 窄屏：四个按钮排不进一行（实测 430px 上约需 620px），
   改 2×2 等宽网格 —— 高度确定 = 2×39 + 9 = 87px。 */
@media (max-width: 639px) {
    .vg-yard { --vg-yard-toolbar: 87px; }
    .vg-yard-tools {
        grid-template-columns: repeat(2, minmax(0, 1fr));
        width: min(100% - 24px, 340px);
        box-sizing: border-box;
    }
    .vg-yard-tools .gb-btn { width: 100%; padding: 8px 10px; }
}
/* 极窄屏（折叠屏外屏 280~340px）：2×2 的每格只剩 ~124px，
   而「🛒 蔬菜实验室」在 0.95rem 下要 ~116px —— 压线。
   这里同时降字号 + 收紧列间距 + 把边距从 24px 收到 16px，
   把每格宽度让回 ~10px，文字从此不贴边。 */
@media (max-width: 359px) {
    .vg-yard-tools { gap: 6px; width: min(100% - 16px, 340px); }
    .vg-yard-tools .gb-btn { font-size: 0.85rem; padding: 8px 6px; }
}

/* ============================================================
   八、SVG 内部动效（只作用于内层 <g>，绝不碰 SVG 元素的 transform 属性）
   ============================================================ */
.va-target { animation: vg-target-glow 1.6s ease-in-out infinite; }
@keyframes vg-target-glow {
    0%, 100% { opacity: 0.85; }
    50% { opacity: 1; }
}
.va-cross { animation: vg-cross-bob 1.9s ease-in-out infinite; }
@keyframes vg-cross-bob {
    0%, 100% { transform: translateY(-2px); }
    50% { transform: translateY(2px); }
}
/* 命中反馈：整个舞台闪一下
   ★ 三档震屏：轻（撞击）/中/重（击毁·爆炸）。
     重档幅度/时长都更大，让「拆掉一个东西」有身体感受。
     注意：只动 .vg-stage 的 transform，不动 SVG 内部元素的属性。 */
.vg-stagewrap.hit .vg-stage { animation: vg-shake 0.22s ease-out; }
@keyframes vg-shake {
    0%, 100% { transform: translate(0, 0); }
    25% { transform: translate(-3px, 2px); }
    50% { transform: translate(3px, -1px); }
    75% { transform: translate(-2px, -2px); }
}
.vg-stagewrap.hit-2 .vg-stage { animation: vg-shake-2 0.24s ease-out; }
@keyframes vg-shake-2 {
    0%, 100% { transform: translate(0, 0); }
    20% { transform: translate(-5px, 3px) rotate(-0.25deg); }
    45% { transform: translate(5px, -2px) rotate(0.25deg); }
    70% { transform: translate(-3px, -3px); }
}
.vg-stagewrap.hit-3 .vg-stage { animation: vg-shake-3 0.34s ease-out; }
@keyframes vg-shake-3 {
    0%, 100% { transform: translate(0, 0) scale(1); }
    14% { transform: translate(-8px, 5px) rotate(-0.5deg) scale(1.014); }
    34% { transform: translate(8px, -4px) rotate(0.5deg) scale(1.012); }
    56% { transform: translate(-5px, -4px) rotate(-0.3deg); }
    78% { transform: translate(3px, 2px) rotate(0.15deg); }
}

/* 击毁残骸：整体略淡 + 略微下沉，和「还活着的件」明确区分 */
.va-wreck { opacity: 0.92; }
/* 残骸不做脉冲动画（那是「目标件还活着」的信号） */

/* 「可拆」件的贴地投影：让前景结构从平整背景里「浮」出来。
   ★ 这是区分「背景 / 可击毁部分」的主力手段 —— 背景是平的，
     有投影的物件天然有实体感。比加角标更安静、也更有效。 */
.va-shadow { transition: opacity 0.3s ease; }

/* 背景压暗/降饱和：让前景可拆件跳出来
   ★ 用 <g> 上的 opacity + 叠雾纱实现，不依赖 CSS filter
     （filter 作用在 SVG <g> 上各浏览器差异大）。 */
.va-bg-dim { opacity: 0.68; }
/* 爆破闪光 */
.vg-stagewrap.blast::after {
    content: '';
    position: absolute;
    inset: 0;
    background: radial-gradient(circle at var(--bx, 60%) var(--by, 60%), rgba(255, 243, 196, 0.75), transparent 62%);
    pointer-events: none;
    animation: vg-blast-fade 0.42s ease-out forwards;
}
@keyframes vg-blast-fade {
    from { opacity: 1; }
    to { opacity: 0; }
}

/* 关卡地图里的小星星不需要 hover 效果 */
.vg-lv-stars svg { width: 13px; height: 13px; }

/* 提示：不会消耗农场库存 */
.vg-no-consume {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    font-size: 11.5px;
    color: #37702f;
    background: #e8fae4;
    border: 1.5px solid #8fce86;
    border-radius: 999px;
    padding: 2px 10px;
}

/* ============================================================
   九、窄屏适配
   ============================================================ */
@media (max-width: 620px) {
    .vg-ch-grid { grid-template-columns: repeat(auto-fill, minmax(80px, 1fr)); gap: 7px; padding: 9px; }
    .vg-lv { padding: 7px 4px 5px; }
    .vg-lv .vg-lv-name { font-size: 11px; }
    .vg-shop-grid { grid-template-columns: 1fr; }
    .vg-vegdex { grid-template-columns: repeat(auto-fill, minmax(122px, 1fr)); }
    /* runbar 在窄屏改成竖排：目标清单一行、选择器一行、说明一行，
       各自独占宽度，避免「选择器被压成一条缝」或「说明被挤掉」。 */
    .vg-runbar { flex-direction: column; align-items: stretch; gap: 7px; width: 100%; max-width: 100%; min-width: 0; }
    .vg-goals { flex: 0 0 auto; align-self: stretch; min-width: 0; }
    .vg-goal { font-size: 11px; padding: 2px 8px; }
    /* ★ 选择器在窄屏会横向溢出（实测 360px 时 x=-4，左右各被推出视口 4px）。
       原因：.vg-picker 是 flex 行、8 个 38px 圆 + gap 天然 ~364px min-content，
       而它在 column 容器里 min-width:auto → 反过来把父级撑宽。
       做法：min-width:0 允许收缩到父级宽度，超出部分靠横向滚动（滚动条隐藏）。 */
    .vg-picker { flex: 0 1 auto; justify-content: center; width: 100%; max-width: 100%; min-width: 0; }
    .vg-vegtip { flex: 0 0 auto; min-width: 0; max-width: 100%; box-sizing: border-box; }
    .vg-pick { width: 38px; height: 38px; }
    .vg-pick svg { width: 28px; height: 28px; }
    .vg-hud-chip .vg-hud-label { display: none; }
    .vg-result .vg-res-stars svg { width: 44px; height: 44px; }
}

/* 结束本关按钮：达标时高亮成「过关结算」 */
.vg-end-btn.vg-end-ready {
    background: #2f9e54;
    color: #fff;
    border-color: #2f9e54;
    box-shadow: 0 0 0 3px rgba(47,158,84,.25);
    font-weight: 700;
    animation: vg-end-pulse 1.1s ease-in-out infinite;
}
@keyframes vg-end-pulse {
    0%, 100% { box-shadow: 0 0 0 3px rgba(47,158,84,.25); }
    50% { box-shadow: 0 0 0 6px rgba(47,158,84,.12); }
}
