/* 河豚打气(Puffer Pump)—— 夜光珊瑚礁 × 柠檬
 * 图片只有夜光珊瑚礁背景(public/img/puffer/bg.webp)和河豚六张(fish-1…4 越来越鼓、fish-pop 爆了、fish-win 收钱);
 * 大倍数、难度胶囊、「下一下」预告、注额、两颗大键、结果卡、往下掉的金币全是 CSS。版面照设计稿
 * (output/小游戏市场调研/河豚打气设计/B.png 打气中、out/S2-快爆了.png、S3-爆了.png、S4-收钱.png):
 * 顶栏(返回 / 河豚打气 / 余额)、上半屏珊瑚礁 + 河豚 + 柠檬发光大倍数、下半屏黑底面板。
 * 柠檬黄只给选中的难度、选中的快捷金额、主按钮「打气 / 开始 / 再来一局」和发光的倍数;数字一律白色粗体。
 * 动画(老板 10-26,设计可爱):河豚一直上下浮 + 果冻似的鼓缩、快爆了发抖 + 红色脉冲、打一下压扁弹大冒气泡、
 * 爆了猛鼓 → 白闪 → 彩纸气泡炸开 → 头晕下沉、收钱弹两下 + 小星星、背景气泡慢慢往上飘;气泡、彩纸、星星都是 CSS 画的。
 * 动的只有 transform / opacity;关了「减少动态效果」全关。
 * 10-26 老板:「应该要每按一次涨一下不是吗?只有画面在动鱼没有在动」—— 背景图不动,河豚是去背的影片(六支 <video>,见 .pf-clips)叠在上面,
 * 每按一下整只涨一点(.pf-fishwrap 照 --s 放大);影片没准备好 / 坏了就是整张图,同一个位置、同一个大小;结果卡贴底(下三分之一),河豚提上去,脸露在卡上面。
 */
.puffer {
  position: fixed; inset: 0; z-index: 10; overflow: hidden;
  display: flex; flex-direction: column;
  padding: var(--safe-t, 0px) 0 calc(var(--safe-b, 0px) + 10px);
  --bg: #0a0a0a; --surface: #151515; --surface-2: #1c1c1c; --line: #242424;
  --ink: #fff; --ink-2: #8a8a8a; --ink-3: #5c5c5c; --neon: #e5ff3d; --neon-ink: #0a0a0a; --bad: #ff4d4d; --hot: #ff6a3d;
  --sans: system-ui, -apple-system, "PingFang SC", "Noto Sans CJK SC", "Microsoft YaHei", sans-serif;
  /* 一颗气泡:左上一点高光、中间几乎透明、边缘一圈淡青的亮边(背景、打一下、爆了的气泡共用) */
  --bubble: radial-gradient(circle at 34% 30%, rgba(255, 255, 255, .95) 0 11%, rgba(205, 245, 255, .32) 26%, rgba(140, 225, 255, .1) 60%, rgba(160, 235, 255, .6) 90%, rgba(160, 235, 255, 0) 100%);
  /* 打一下、爆了的气泡再加一圈亮边和一点光晕:在河豚黄身体和花背景上也认得出来(背景慢慢飘的那几颗不加,要淡) */
  --bubble-rim: inset 0 0 0 1.5px rgba(210, 248, 255, .85), 0 0 8px rgba(150, 230, 255, .4);
  background: var(--bg); color: var(--ink); font-family: var(--sans);
  -webkit-user-select: none; user-select: none; -webkit-tap-highlight-color: transparent;
}
.puffer[hidden] { display: none; }
.puffer :where(p, h2, h3) { margin: 0; }
/* 用 :where 把重置的权重压到 0:后面单个 class 的按钮样式才盖得过它(button 的 background:none 会吃掉底色) */
.puffer :where(button) { appearance: none; border: 0; margin: 0; padding: 0; font: inherit; color: inherit; background: none; cursor: pointer; }
.puffer button:focus-visible { outline: 2px solid var(--neon); outline-offset: 2px; }
.puffer img { -webkit-user-drag: none; pointer-events: none; }
/* 按钮里面的字、金额一律点不到:按下、松手、click 都落在按钮本身(收钱上的金额每打一下就换) */
.puffer button > *, .puffer button * { pointer-events: none; }

/* ---------- 顶栏:返回、标题、余额 ----------
 * 照扫雷:余额整个显示(不缩,最多占 44%),标题吃剩下的地方 —— 标题会被翻成外文(马来「Pam Ikan Buntal」、菲律宾「Bomba ng Butete」
 * 20px 粗体要 156–167px),以前标题是不缩的那一列,360–430 宽余额被挤成「RM 1,2…」。现在长的标题照屏宽缩字、折成两行 */
.pf-top { flex: none; display: flex; align-items: center; gap: 10px; min-height: 56px; padding: 8px 16px; }
.pf-back { flex: none; display: grid; place-items: center; width: 40px; height: 40px; border-radius: 12px; background: var(--surface); box-shadow: inset 0 0 0 1px var(--line); }
.pf-back svg { width: 20px; height: 20px; }
.pf-title { flex: 1; min-width: 0; text-align: center; font-size: clamp(16px, 5vw, 20px); font-weight: 800; line-height: 1.12; letter-spacing: .02em; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden; }
.pf-bal { flex: none; max-width: 44%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; padding: 7px 12px; border-radius: 12px; background: var(--surface); box-shadow: inset 0 0 0 1px var(--line); font-size: 14px; font-weight: 700; font-variant-numeric: tabular-nums; }
/* 音符按钮(有背景音乐才有,照扫雷):跟返回键一样大的圆角方块,开着柠檬色、关了灰;多了一颗,顶栏间距收一点留给标题 */
.pf-music { flex: none; display: grid; place-items: center; width: 40px; height: 40px; border-radius: 12px; background: var(--surface); box-shadow: inset 0 0 0 1px var(--line); color: var(--neon); }
.pf-music[aria-pressed="false"] { color: var(--ink-2); }
.pf-music svg { width: 20px; height: 20px; }
.pf-top.has-music { gap: 6px; }
/* 返回键、音符按钮画的是 40px,点得到的范围往外各扩 2px 到 44px(按钮 ≥44px;画面不变,320 宽标题也不少那 4px)。
 * 伪元素不吃上面 button * 的 pointer-events: none,点在扩出来那一圈落的还是按钮本身 */
.pf-back, .pf-music { position: relative; }
.pf-back::before, .pf-music::before { content: ''; position: absolute; inset: -2px; }
@media (max-width: 359px) { .pf-top { gap: 6px; padding-left: 12px; padding-right: 12px; } .pf-bal { padding: 6px 9px; font-size: 13px; } }

/* ---------- 主画面:珊瑚礁打底(不动),河豚叠在上面居中、越按越大,大倍数压在上面,结果卡贴底 ----------
 * 背景图 4:3(1112×834),框吃掉面板以外的高度,一般比图窄:cover 裁两边,锚在 70% 让右边那个压力表露出一截(稿里也是切在右缘)。
 * 河豚那一层外面包一层 .pf-stage(跟主画面一样大的 container):河豚的框照主画面的宽 / 高算(cqw / cqh)。
 * container 只包河豚:包在整个场景上的话,打一下的气泡、彩纸、星星会被关进场景自己的叠放层,压到红色脉冲底下 */
.pf-hero { position: relative; flex: 1 1 auto; min-height: 220px; overflow: hidden; background: #03141c; }
.pf-scene { position: absolute; inset: 0; }
.pf-stage { position: absolute; inset: 0; container-type: size; pointer-events: none; }
.pf-bg { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; object-position: 70% 50%; }
/* 下缘融进黑底,接面板 */
.pf-hero::after { content: ''; position: absolute; left: 0; right: 0; bottom: 0; height: 14%; background: linear-gradient(to bottom, rgba(10, 10, 10, 0), var(--bg)); z-index: 1; pointer-events: none; }
/* 河豚(老板 10-26「应该要每按一次涨一下不是吗?只有画面在动鱼没有在动」):去背的影片 / 整张图叠在不动的背景上。
 * 框是正方形,画的是最大的那个大小 —— 河豚的大小(宽高的几何平均)= 主画面宽的 80%,主画面矮的时候照高度缩(不撑出画面、不顶到倍数字);
 * 照 --s(puffer.js 的 fishScale,写在 .pf-scene 上)缩:开局 0.375(画面宽 30%)→ 每按一下涨一点 → 快爆了 1。
 * 中心在主画面 58% 高的地方(倍数字底下)。认不得 cqw / cqh 的旧 WebView(iOS 15:iPhone 6s / 7 / SE 一代)cq 那一句整句作废,
 * 退回 --fw:puffer.js 量主画面、照同一条算式(宽 80%、高 68% 取小的)写在 .pf-stage 上;还没量到才照屏宽(复查 10-26:只照屏宽会撑出主画面、顶到倍数字) */
.pf-fishwrap { position: absolute; left: 0; right: 0; top: 16%; bottom: 0; margin: auto; width: var(--fw, min(72vw, 330px)); height: var(--fw, min(72vw, 330px)); width: min(80cqw, 68cqh); height: min(80cqw, 68cqh); transform: scale(var(--s, 1)); }
/* 结果画面(爆了 / 收钱):整只提到主画面上面那段(中心在 30% 高),脸露在贴底的结果卡上面;局结束了大倍数让给结果卡,上面那段是空的。
 * 从原本的位置(低 28% 主画面高)0.4 秒升上去,不是一下跳过去;大小的变化在里面那层(pf-popin / pf-hop)演。认不得 cqh 的旧 WebView 直接在上面,框一样是 --fw */
.pf-fishwrap.is-fish-pop, .pf-fishwrap.is-fish-win { top: 0; bottom: 40%; animation: pf-lift .4s cubic-bezier(.3, .7, .4, 1) both; }
@keyframes pf-lift { 0% { transform: translate3d(0, 28cqh, 0) scale(var(--s, 1)); } 100% { transform: translate3d(0, 0, 0) scale(var(--s, 1)); } }
.pf-bob, .pf-fishfx, .pf-body, .pf-jit { width: 100%; height: 100%; }
/* 演一次的那一层(影片、整张图共用):压扁、弹大从河豚中间偏下往外鼓 */
.pf-fishfx { position: relative; transform-origin: 50% 56%; }
/* 去背的影片六支叠在一起(puffer.js 只亮现在该播的那支,挂 is-on);一支都没亮(还没准备好 / 坏了 / 等点一下)就是底下的整张图。
 * 每支照 puffer.js 写的 --k / --tx / --ty / --ox / --oy 摆(FISH_SIZE):以河豚中心为原点放大到「河豚大小 = 框宽」、挪到框的正中 ——
 * 每支的河豚一样大、在同一个位置,换表情那一下不会忽大忽小。isolation:两支的 z-index 只在这一层里面比 */
.pf-clips { position: absolute; inset: 0; isolation: isolate; }
.pf-clip { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: contain; opacity: 0; pointer-events: none; transform-origin: var(--ox, 50%) var(--oy, 50%); transform: translate(var(--tx, 0), var(--ty, 0)) scale(var(--k, 1)); }
.pf-clip.is-on { opacity: 1; z-index: 1; }
/* 换支那一下(换段换表情、局结束换结果、再来一局):新的那支已经在下面亮着在播,刚换下来的那支留在上面 0.15 秒淡掉
 * (跟 puffer.js 的 CLIP_FADE_MS 一样长),看起来是同一只在换表情 */
.pf-clip.is-out { z-index: 2; animation: pf-clip-out .15s linear forwards; }
@keyframes pf-clip-out { 0% { opacity: 1; } 100% { opacity: 0; } }
/* 用影片的时候:整张图那一叠(连同果冻鼓缩、发抖、头晕)收起来,影片自己会动;浮和演一次的照旧(在外面两层) */
.pf-hero.is-clip .pf-body { display: none; }
/* 整张图(后备):跟影片同一套摆法(IMG_SIZE),所以退回整张图那一下河豚一样大、在同一个位置,一样越按越大 */
.pf-fish { position: absolute; inset: 0; display: block; width: 100%; height: 100%; object-fit: contain; transform-origin: var(--ox, 50%) var(--oy, 50%); transform: translate(var(--tx, 0), var(--ty, 0)) scale(var(--k, 1)); filter: drop-shadow(0 14px 22px rgba(0, 0, 0, .55)); }

/* 一直在动的(照这一局的样子挂,中途进页直接是对的) */
/* 整只慢慢上下浮,2.4 秒一圈(影片、整张图都浮) */
.pf-bob { animation: pf-bob 2.4s ease-in-out infinite; }
@keyframes pf-bob { 0%, 100% { transform: translate3d(0, 0, 0); } 50% { transform: translate3d(0, -7px, 0); } }
/* 整张图的呼吸:瘦高 ↔ 矮胖交替一点,像果冻;越鼓的那段鼓缩越明显、越快(跟浮的节拍错开,不会一起停在同一点)。影片自己会呼吸,不叠 */
.pf-body { transform-origin: 50% 62%; animation: pf-breathe-1 2.1s ease-in-out infinite; }
.pf-fishwrap.is-fish-2 .pf-body { animation: pf-breathe-2 1.9s ease-in-out infinite; }
.pf-fishwrap.is-fish-3 .pf-body { animation: pf-breathe-3 1.7s ease-in-out infinite; }
.pf-fishwrap.is-fish-4 .pf-body { animation: pf-breathe-4 1.25s ease-in-out infinite; }
@keyframes pf-breathe-1 { 0%, 100% { transform: scale(.99, 1.01); } 50% { transform: scale(1.02, .98); } }
@keyframes pf-breathe-2 { 0%, 100% { transform: scale(.985, 1.015); } 50% { transform: scale(1.03, .97); } }
@keyframes pf-breathe-3 { 0%, 100% { transform: scale(.98, 1.02); } 50% { transform: scale(1.04, .96); } }
@keyframes pf-breathe-4 { 0%, 100% { transform: scale(.975, 1.025); } 50% { transform: scale(1.05, .95); } }
/* 快爆了那张:在浮和鼓缩之外再快速小幅发抖(位移 + 一点点旋转,一来一回 0.24 秒 = 0.12 秒抖一下;影片 a4 自己会抖) */
.pf-fishwrap.is-fish-4 .pf-jit { animation: pf-shiver .24s linear infinite; }
@keyframes pf-shiver {
  0%, 100% { transform: translate3d(0, 0, 0) rotate(0deg); }
  25% { transform: translate3d(-1.6px, .6px, 0) rotate(-1.2deg); }
  50% { transform: translate3d(.4px, -.5px, 0) rotate(.2deg); }
  75% { transform: translate3d(1.6px, -.4px, 0) rotate(1deg); }
}
/* 爆了:往下沉一点停住(静止的位置写在 transform 上,动画只是沉下去的过程);整张图那张再头晕似的慢慢左右晃 ±6°(影片 apop 自己晕) */
.pf-fishwrap.is-fish-pop .pf-bob { transform: translate3d(0, 4%, 0); animation: pf-sink .9s cubic-bezier(.3, .7, .4, 1) both; }
@keyframes pf-sink { 0% { transform: translate3d(0, 0, 0); } 100% { transform: translate3d(0, 4%, 0); } }
.pf-fishwrap.is-fish-pop .pf-body { transform-origin: 50% 72%; animation: pf-dizzy 2.6s ease-in-out infinite; }
@keyframes pf-dizzy { 0%, 100% { transform: rotate(-6deg); } 50% { transform: rotate(6deg); } }
/* 快爆了:背景压一层很淡的红色脉冲(四周红、中间透明),在河豚和珊瑚礁上面、倍数和结果卡下面 */
.pf-danger { position: absolute; inset: 0; z-index: 1; pointer-events: none; opacity: 0; background: radial-gradient(ellipse 78% 72% at 50% 58%, rgba(255, 50, 40, 0) 42%, rgba(255, 50, 40, .26) 100%); }
.pf-hero.is-danger .pf-danger { animation: pf-danger 1.25s ease-in-out infinite; }
@keyframes pf-danger { 0%, 100% { opacity: .15; } 50% { opacity: 1; } }
/* 背景:几颗气泡慢慢往上飘(大小、速度、起点都不一样,进页时已经在半路)。每颗是一条跟主画面一样高的细柱,圆画在柱子底下,
 * 整条往上移一个自己的高度 = 从底下飘到顶上;不挡操作 */
.pf-ambient { position: absolute; inset: 0; overflow: hidden; pointer-events: none; }
.pf-amb { position: absolute; left: var(--x, 50%); bottom: 0; width: var(--z, 8px); height: 100%; opacity: 0; animation: pf-amb var(--t, 12s) linear var(--d, 0s) infinite; }
.pf-amb::before { content: ''; position: absolute; left: 0; bottom: calc(var(--z, 8px) * -1); width: var(--z, 8px); aspect-ratio: 1; border-radius: 50%; background: var(--bubble); }
@keyframes pf-amb {
  0% { opacity: 0; transform: translate3d(0, 0, 0); }
  8% { opacity: .75; }
  50% { transform: translate3d(7px, -52%, 0); }
  86% { opacity: .55; }
  100% { opacity: 0; transform: translate3d(-3px, -104%, 0); }
}

/* 演一次的(puffer.js 刚发生才挂在河豚那一层 .pf-fishfx;--g0 = 上一个大小 / 现在的大小,动画从上一个大小起演;
 * --qx / --qy = 换之前这一层演到哪,按下去挤着憋着的、上一下还没弹完的,新的那个从那里接上,不先跳回原样) */
/* 「打气」按下去那一刻(回包还没到,老板 10-26「点了要等一下才有反应」):在现在的大小先压扁(宽、矮,0.1 秒),
 * 回包没到就一直憋在那附近微微抖(循环,慢网络等一秒也不会像卡住);不先变大 —— 涨多少等回包 */
.pf-fishfx.is-squeeze { animation: pf-squeeze .1s cubic-bezier(.3, .7, .4, 1) both, pf-squeeze-hold .32s ease-in-out .1s infinite alternate; }
@keyframes pf-squeeze { 0% { transform: scale(var(--qx, 1), var(--qy, 1)); } 100% { transform: scale(1.1, .86); } }
@keyframes pf-squeeze-hold { 0% { transform: scale(1.1, .86); } 100% { transform: scale(1.07, .89); } }
/* 憋着的那一下没有结果要演(退回、出错、改按收钱):松回原样 */
.pf-fishfx.is-relax { animation: pf-relax .14s ease-out; }
@keyframes pf-relax { 0% { transform: scale(var(--qx, 1), var(--qy, 1)); } 100% { transform: scale(1, 1); } }
/* 打一下(回包没爆):从憋着的样子(上一个大小)弹到新的大小、高过一点、定住(0.2 秒);换段那一下弹得更大(换表情那支同时交叉淡化) */
.pf-fishfx.is-puff { animation: pf-puff .2s cubic-bezier(.3, .7, .4, 1); }
@keyframes pf-puff { 0% { transform: scale(calc(var(--g0, 1) * var(--qx, 1)), calc(var(--g0, 1) * var(--qy, 1))); } 45% { transform: scale(1.05, 1.09); } 72% { transform: scale(.985, 1.005); } 100% { transform: scale(1, 1); } }
.pf-fishfx.is-puff-big { animation: pf-puff-big .28s cubic-bezier(.3, .7, .4, 1); }
@keyframes pf-puff-big { 0% { transform: scale(calc(var(--g0, 1) * var(--qx, 1)), calc(var(--g0, 1) * var(--qy, 1))); } 42% { transform: scale(1.09, 1.16); } 70% { transform: scale(.97, 1.01); } 100% { transform: scale(1, 1); } }
/* 打一下身边冒的小气泡(位置、大小、飘多高、晚多久由 puffer.js 写,横向贴着河豚现在的身体两边):从河豚中心那一点往外放 */
.pf-fizz { position: absolute; left: 50%; top: 58%; width: 0; height: 0; z-index: 2; pointer-events: none; }
.pf-fizzset { position: absolute; left: 0; top: 0; }
.pf-bub { position: absolute; left: var(--x, 0); top: var(--y, 0); width: var(--z, 10px); aspect-ratio: 1; margin: calc(var(--z, 10px) / -2); border-radius: 50%; background: var(--bubble); box-shadow: var(--bubble-rim); opacity: 0; animation: pf-bub var(--t, .9s) var(--d, 0ms) cubic-bezier(.25, .55, .45, 1) both; }
/* 飘到三分之二才开始淡:珊瑚礁背景很花,太早淡掉就看不到 */
@keyframes pf-bub { 0% { opacity: 0; transform: translate3d(0, 0, 0) scale(.4); } 15% { opacity: 1; } 65% { opacity: 1; } 100% { opacity: 0; transform: translate3d(var(--w, 0), var(--h, -90px), 0) scale(1.1); } }
/* 爆之前:河豚那一层从憋着的样子猛地鼓一下(0.08 秒,跟 puffer.js 的 BULGE_MS 一样),停在鼓起来那里等换瘪掉那支 */
.pf-fishfx.is-bulge { animation: pf-bulge .08s ease-in forwards; }
@keyframes pf-bulge { 0% { transform: scale(var(--qx, 1), var(--qy, 1)); } 100% { transform: scale(1.17, 1.13); } }
/* 换上瘪掉那支:从炸开的大小(爆之前的大小再鼓一点)缩成瘪掉的大小,弹一下 */
.pf-fishfx.is-popin { animation: pf-popin .5s cubic-bezier(.2, .9, .3, 1.25); }
@keyframes pf-popin { 0% { transform: scale(calc(var(--g0, 1) * 1.15)); opacity: .7; } 45% { transform: scale(.93); opacity: 1; } 100% { transform: scale(1); opacity: 1; } }
/* 爆了:白闪一下(盖住河豚,不盖结果卡) */
.pf-flash { position: absolute; inset: 0; z-index: 3; pointer-events: none; opacity: 0; background: radial-gradient(circle at 50% 45%, #fff 0, rgba(255, 255, 255, .85) 30%, rgba(255, 255, 255, .35) 100%); }
.pf-flash.is-flash { animation: pf-flash .4s ease-out; }
@keyframes pf-flash { 0% { opacity: .85; } 100% { opacity: 0; } }
/* 爆了:从河豚中间(结果画面河豚提上去了,中心在 30% 高)炸出柠檬彩纸 + 气泡往四周飞、淡掉(方向、远近、转多少由 puffer.js 写) */
.pf-burst { position: absolute; left: 50%; top: 30%; width: 0; height: 0; z-index: 2; pointer-events: none; }
.pf-bit { position: absolute; left: 0; top: 0; opacity: 0; animation: pf-bit .95s var(--d, 0ms) cubic-bezier(.12, .7, .3, 1) both; }
.pf-bit.is-paper { width: 13px; height: 6px; margin: -3px -6px; border-radius: 2px; background: var(--c, var(--neon)); box-shadow: 0 0 6px rgba(229, 255, 61, .45); }
.pf-bit.is-bubble { width: var(--z, 12px); aspect-ratio: 1; margin: calc(var(--z, 12px) / -2); border-radius: 50%; background: var(--bubble); box-shadow: var(--bubble-rim); }
@keyframes pf-bit { 0% { opacity: 1; transform: translate3d(0, 0, 0) rotate(0deg) scale(.4); } 70% { opacity: 1; } 100% { opacity: 0; transform: translate3d(var(--dx, 0), var(--dy, 0), 0) rotate(var(--r, 0deg)) scale(1); } }
/* 收钱:换收钱那支,从上一个大小弹到收钱的大小、弹跳两下(上下 + 一点压扁) */
.pf-fishfx.is-hop { animation: pf-hop .9s ease-out; }
@keyframes pf-hop {
  0% { transform: translate3d(0, 0, 0) scale(calc(var(--g0, 1) * var(--qx, 1)), calc(var(--g0, 1) * var(--qy, 1))); }
  14% { transform: translate3d(0, -9%, 0) scale(.95, 1.06); }
  30% { transform: translate3d(0, 0, 0) scale(1.09, .9); }
  46% { transform: translate3d(0, -5%, 0) scale(.97, 1.04); }
  62% { transform: translate3d(0, 0, 0) scale(1.05, .95); }
  78%, 100% { transform: translate3d(0, 0, 0) scale(1, 1); }
}
/* 收钱:身边几颗柠檬色小星星闪一下(四角星用 clip-path 裁,光晕是底下一层径向渐层;位置、晚多久由 puffer.js 写) */
.pf-stars { position: absolute; inset: 0; z-index: 2; pointer-events: none; }
.pf-star { position: absolute; left: var(--x, 50%); top: var(--y, 50%); width: var(--z, 24px); height: var(--z, 24px); margin: calc(var(--z, 24px) / -2); opacity: 0; animation: pf-star .9s var(--d, 0ms) cubic-bezier(.3, .7, .4, 1) both; }
.pf-star::before { content: ''; position: absolute; inset: -70%; border-radius: 50%; background: radial-gradient(circle, rgba(229, 255, 61, .5), rgba(229, 255, 61, 0) 62%); }
.pf-star::after { content: ''; position: absolute; inset: 0; background: var(--neon); clip-path: polygon(50% 0, 61% 39%, 100% 50%, 61% 61%, 50% 100%, 39% 61%, 0 50%, 39% 39%); }
@keyframes pf-star { 0% { opacity: 0; transform: scale(.2) rotate(0deg); } 35% { opacity: 1; transform: scale(1.15) rotate(25deg); } 70% { opacity: 1; transform: scale(.9) rotate(35deg); } 100% { opacity: 0; transform: scale(.5) rotate(45deg); } }
/* 爆了:整个场景抖一下(结果卡不跟着抖,看得清) */
.pf-scene.is-shake { animation: pf-shake .42s cubic-bezier(.36, .07, .19, .97); }
@keyframes pf-shake {
  0%, 100% { transform: translate3d(0, 0, 0); }
  15% { transform: translate3d(-9px, 3px, 0); }
  30% { transform: translate3d(8px, -4px, 0); }
  45% { transform: translate3d(-6px, 2px, 0); }
  60% { transform: translate3d(5px, -2px, 0); }
  80% { transform: translate3d(-2px, 1px, 0); }
}

/* 大倍数:黑底药丸 + 柠檬描边发光,两边一对往上的双箭头(稿里的 ⌃⌃ 2.48x ⌃⌃) */
.pf-multbox { position: absolute; top: 12px; left: 0; right: 0; z-index: 3; display: flex; align-items: center; gap: 8px; width: max-content; max-width: calc(100% - 32px); margin: 0 auto; padding: 6px 16px; border-radius: 999px; background: rgba(6, 12, 4, .72); box-shadow: 0 0 0 2px var(--neon), 0 0 22px rgba(229, 255, 61, .5), inset 0 0 16px rgba(229, 255, 61, .22); }
.pf-multbox[hidden] { display: none; }
.pf-chev { flex: none; display: block; width: 26px; height: 26px; color: var(--neon); filter: drop-shadow(0 0 6px rgba(229, 255, 61, .7)); }
.pf-chev svg { display: block; width: 100%; height: 100%; }
.pf-multtext { position: relative; display: grid; }
.pf-mult, .pf-mult-hot { grid-area: 1 / 1; font-size: clamp(34px, 12vw, 54px); font-weight: 900; line-height: 1; letter-spacing: .01em; font-variant-numeric: tabular-nums; white-space: nowrap; }
/* 倍数长了(「1234.56x」以上)缩一档、再长再缩一档(puffer.js 按字数挂 is-long / is-xlong) */
.pf-multbox.is-long .pf-mult, .pf-multbox.is-long .pf-mult-hot { font-size: clamp(28px, 9.5vw, 44px); }
.pf-multbox.is-xlong .pf-mult, .pf-multbox.is-xlong .pf-mult-hot { font-size: clamp(22px, 7vw, 34px); }
.pf-mult { color: var(--neon); text-shadow: 0 0 12px rgba(229, 255, 61, .75), 0 0 28px rgba(229, 255, 61, .4); }
/* 快爆了(下一下危险到了门槛,puffer.js 的 isHot):红字和红框叠在上面,透明度一明一暗 = 红柠檬交替闪(稿 S2)。只是视觉,不写任何数字 */
.pf-mult-hot { color: var(--hot); text-shadow: 0 0 12px rgba(255, 80, 40, .85), 0 0 26px rgba(255, 40, 20, .5); opacity: 0; }
.pf-multbox::after { content: ''; position: absolute; inset: 0; border-radius: inherit; box-shadow: 0 0 0 2px var(--hot), 0 0 24px rgba(255, 70, 40, .6), inset 0 0 18px rgba(255, 70, 40, .3); opacity: 0; pointer-events: none; }
.pf-multbox.is-hot .pf-mult-hot, .pf-multbox.is-hot::after { animation: pf-hot .36s ease-in-out infinite alternate; }
@keyframes pf-hot { from { opacity: 0; } to { opacity: 1; } }
/* 打了一下没爆:倍数字跳一下 */
.pf-multbox.is-bump .pf-multtext { animation: pf-bump .2s ease-out; }
@keyframes pf-bump { 0% { transform: scale(1); } 50% { transform: scale(1.08); } 100% { transform: scale(1); } }

/* 收钱:金币从上面掉下来(每颗的横向位置 --x、晚多久 --d、转多少 --r 由 puffer.js 写)。纯 CSS 画的金币:径向渐层 + 一圈内环 */
.pf-coins { position: absolute; inset: 0; z-index: 2; overflow: hidden; pointer-events: none; container-type: size; }
.pf-coin { position: absolute; top: -12%; left: var(--x, 50%); width: 10%; max-width: 46px; aspect-ratio: 1; border-radius: 50%; background: radial-gradient(circle at 35% 30%, #fff7b8 0 12%, #ffd84a 32%, #e2a400 64%, #9b6a00 100%); box-shadow: inset 0 0 0 2px rgba(255, 240, 160, .55), inset 0 -3px 0 rgba(120, 70, 0, .45), 0 3px 8px rgba(0, 0, 0, .5); opacity: 0; animation: pf-coin 1.5s var(--d, 0ms) cubic-bezier(.4, .1, .7, 1) both; }
.pf-coin::after { content: ''; position: absolute; inset: 24%; border-radius: 50%; box-shadow: inset 0 0 0 2px rgba(150, 90, 0, .55); }
/* 往下掉的距离:至少 13 个金币高(手机上原本的手感),主画面高的(iPad、电脑版拉高窗口)按画面高度掉出底边(.pf-coins 是 container);
 * 认不得 cqh 的旧 WebView 那一句整句作废,退回 1300%。最后一小段再淡掉:怎么样都不会停在画面中间 */
@keyframes pf-coin {
  0% { opacity: 0; transform: translate3d(0, 0, 0) rotate(0deg); }
  8% { opacity: 1; }
  88% { opacity: 1; }
  100% { opacity: 0; transform: translate3d(0, 1300%, 0) rotate(var(--r, 360deg)); transform: translate3d(0, max(1300%, calc(115cqh + 100%)), 0) rotate(var(--r, 360deg)); }
}

/* 结果卡:放在主画面下三分之一(贴底,下面让 3%),脸露在卡上面(稿 S3 / S4;河豚在结果画面整只提到上面那段,见 .pf-fishwrap.is-fish-pop)。
 * 上面那段留白吃掉剩下的高;主画面矮到放不下(360×513 只剩不到 180px)留白先缩掉,整张卡(含「再来一局」)一定在画面里。
 * 影片和整张图同一个位置(慢网络下结果那两支还没抓到、中途抓到换成影片,卡和河豚都不跳) */
.pf-cardwrap { position: absolute; inset: 0; z-index: 4; display: flex; flex-direction: column; align-items: center; padding: 8px 16px; pointer-events: none; }
.pf-cardwrap::before { content: ''; flex: 1 1 0; }
.pf-cardwrap::after { content: ''; flex: 0 1 3%; }
.pf-cardwrap:empty { display: none; }
.pf-card { flex: none; margin: 0; pointer-events: auto; display: grid; justify-items: center; gap: 8px; width: min(100%, 300px); padding: 18px 18px 16px; border-radius: 22px; background: rgba(10, 10, 10, .88); box-shadow: 0 0 0 2px rgba(229, 255, 61, .85), 0 0 26px rgba(229, 255, 61, .35); text-align: center; animation: pf-card-in .32s cubic-bezier(.2, .8, .3, 1.2) both; }
@keyframes pf-card-in { from { opacity: 0; transform: scale(.88); } to { opacity: 1; transform: scale(1); } }
/* 爆了那张卡晚一点点出来:先让白闪、炸开被看到(这一小段里卡里的「再来一局」按到也不开局,puffer.js 有 600ms 窗口) */
.pf-card.is-pop { animation-delay: .22s; }
/* 爆了那张矮一点(瘪掉的河豚飘得低):「爆了」和输掉的钱并成一行,底下一颗「再来一局」;外文长的折成两行 */
.pf-card.is-pop { gap: 6px; padding: 12px 18px 14px; }
.pf-card-row { display: flex; flex-wrap: wrap; align-items: baseline; justify-content: center; column-gap: 12px; }
.pf-card-big { font-size: 34px; font-weight: 900; line-height: 1.1; }
.pf-card-amt { font-style: normal; font-size: 30px; font-weight: 900; line-height: 1.1; font-variant-numeric: tabular-nums; }
/* 爆了那一行只有输掉的钱:红字,跟别的游戏的「−RM x」一样 */
.pf-card .pf-loss { color: var(--bad); }
.pf-again { width: 100%; min-height: 48px; margin-top: 4px; border-radius: 999px; background: var(--neon); color: var(--neon-ink); font-size: 17px; font-weight: 800; }
.pf-again:active { transform: scale(.98); }
/* 收钱成功(稿 S4):上面一颗柠檬描边的「收钱成功」,中间白色发光的大字,底下一颗「x倍数」 */
.pf-card.is-win { width: min(100%, 340px); gap: 6px; padding: 14px 18px; border-radius: 28px; box-shadow: 0 0 0 2.5px var(--neon), 0 0 34px rgba(229, 255, 61, .55), inset 0 0 22px rgba(229, 255, 61, .18); }
.pf-card-tag { padding: 5px 16px; border-radius: 999px; box-shadow: inset 0 0 0 2px var(--neon), 0 0 12px rgba(229, 255, 61, .45); color: var(--neon); font-size: 20px; font-weight: 900; letter-spacing: .04em; }
.pf-card.is-win .pf-card-amt { font-size: clamp(34px, 11vw, 48px); color: #fff; text-shadow: 0 0 14px rgba(255, 255, 255, .45); }
.pf-card-mult { padding: 3px 16px; border-radius: 999px; box-shadow: inset 0 0 0 2px var(--neon); color: var(--neon); font-size: 20px; font-weight: 900; font-variant-numeric: tabular-nums; }
.pf-card-note { font-size: 12px; line-height: 1.35; color: var(--ink-2); }
.pf-card.is-plain .pf-card-big { font-size: 24px; }

/* ---------- 控制面板 ---------- */
.pf-panel { flex: none; display: grid; gap: 10px; padding: 12px 16px 0; }
/* 等服务器的时候不收第二下 */
.pf-panel.pf-busy { pointer-events: none; }
/* 「收钱」例外:打气还在等回复的时候也按得到(按了 = 这一下回来没爆就收) */
.pf-panel.pf-busy [data-pf="cash"] { pointer-events: auto; }
/* 等回复超过 0.3 秒:面板暗下去。「收钱」这时还按得到(见上一条),不能跟着暗 */
.pf-panel.pf-busy.pf-wait > :not(.pf-acts),
.pf-panel.pf-busy.pf-wait .pf-acts > :not([data-pf="cash"]) { opacity: .55; }

/* 难度四颗胶囊:选中的柠檬底;开局后锁住(选着的那颗照样亮,其余暗) */
.pf-levels { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 4px; padding: 4px; border-radius: 22px; background: var(--surface); box-shadow: inset 0 0 0 1px var(--line); }
.pf-level { min-width: 0; min-height: 42px; padding: 0 2px; border-radius: 999px; background: var(--surface-2); box-shadow: inset 0 0 0 1px var(--line); font-size: 15px; font-weight: 700; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* 外文长的译名(马来 Sederhana、菲律宾 Katamtaman、越南 Trung bình / Chuyên gia…,puffer.js 按译出来的字数给整排挂 is-long):
 * 四颗一起照屏宽缩字,360 宽一颗只有约 73px,也要整个字看得到 —— 开局后胶囊锁住,看到「Katamt…」就不知道现在是哪一档 */
.pf-levels.is-long .pf-level { font-size: clamp(10px, 3.1vw, 13px); letter-spacing: -.01em; }
.pf-level.is-on { background: var(--neon); color: var(--neon-ink); box-shadow: 0 0 14px rgba(229, 255, 61, .35); }
.pf-level:disabled { opacity: .45; }
.pf-level.is-on:disabled { opacity: 1; }

/* 「下一下 x.xx」:小河豚头像 + 柠檬数字;右边小字写打了几下 / 最多几下 */
.pf-next { display: flex; align-items: center; gap: 12px; min-height: 66px; padding: 6px 16px 6px 10px; border-radius: 18px; background: var(--surface); box-shadow: inset 0 0 0 1px var(--line); }
.pf-next-fish { flex: none; width: 56px; height: 48px; object-fit: contain; }
.pf-next-l { display: grid; gap: 1px; min-width: 0; }
.pf-next-l small { font-size: 13px; font-weight: 700; }
.pf-next-l b { font-size: 26px; font-weight: 900; line-height: 1.05; color: var(--neon); font-variant-numeric: tabular-nums; }
.pf-next-n { margin-left: auto; font-size: 13px; color: var(--ink-2); font-variant-numeric: tabular-nums; white-space: nowrap; }

/* 下注金额:− 数字 +,底下五颗快捷金额 */
.pf-betbox { display: grid; gap: 10px; padding: 12px; border-radius: 18px; background: var(--surface); box-shadow: inset 0 0 0 1px var(--line); }
.pf-betrow { display: flex; align-items: center; gap: 10px; }
.pf-step { flex: none; display: grid; place-items: center; width: 50px; height: 42px; border-radius: 12px; background: var(--surface-2); box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .14); font-size: 24px; font-weight: 600; }
.pf-step:disabled { opacity: .35; }
.pf-bet { flex: 1; min-width: 0; text-align: center; font-size: 22px; font-weight: 800; font-variant-numeric: tabular-nums; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.pf-quick { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 8px; }
.pf-qbtn { min-width: 0; min-height: 40px; border-radius: 12px; background: var(--surface-2); box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .12); font-size: 15px; font-weight: 700; font-variant-numeric: tabular-nums; }
.pf-qbtn.is-on { background: var(--neon); color: var(--neon-ink); }
.pf-qbtn:disabled { opacity: .35; }
.pf-qbtn.is-on:disabled { opacity: .7; }

/* 两颗大键:深色「收钱 RM x」(还没打第一下是「取消」)+ 柠檬「打气」;没开局 / 局结束了是一整条柠檬「开始 / 再来一局」(稿 S4) */
.pf-acts { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; }
.pf-btn[hidden] { display: none; }
.pf-btn { min-width: 0; display: grid; justify-items: center; align-content: center; gap: 2px; min-height: 66px; padding: 0 10px; border-radius: 18px; font-weight: 800; }
.pf-btn.is-dark { background: var(--surface); box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .16); }
.pf-btn.is-dark span { font-size: 16px; }
.pf-btn.is-dark small { max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 22px; font-weight: 800; font-variant-numeric: tabular-nums; }
.pf-btn.is-neon { background: var(--neon); color: var(--neon-ink); font-size: 22px; box-shadow: 0 0 20px rgba(229, 255, 61, .3); }
.pf-btn.is-wide { grid-column: 1 / -1; }
.pf-btn:disabled { opacity: .4; }
.pf-btn:active:not(:disabled) { transform: scale(.975); }
/* 按下去了、回复还没到(打气在路上 / 收钱在路上 / 等这一下回来就收):一明一暗 */
.pf-btn.is-armed { animation: pf-armed .5s ease-in-out infinite alternate; }
@keyframes pf-armed { from { opacity: 1; } to { opacity: .62; } }

.pf-hint { min-height: 22px; font-size: 12px; color: var(--ink-3); text-align: center; }

/* 规则卡在 .puffer 外面(弹层):每个难度几个爆点、最多几下、第一下的倍数(打满那一下的倍数不列:被单局最高派彩封住,写出来是个到不了的数) */
.sim-table.pf-rules-table { width: 100%; table-layout: fixed; font-size: 13px; margin-top: 10px; }
.pf-rules-table th, .pf-rules-table td { overflow-wrap: anywhere; font-variant-numeric: tabular-nums; }
.pf-rules-table th { color: var(--ink-2, #8a8a8a); font-weight: 600; }

/* 矮屏幕(iPhone SE、Telegram 半屏):收间距和按钮高度,主画面留够地方 */
@media (max-height: 740px) {
  .pf-top { min-height: 48px; padding-top: 4px; padding-bottom: 4px; }
  .pf-panel { gap: 8px; padding-top: 8px; }
  .pf-level { min-height: 38px; }
  .pf-next { min-height: 56px; }
  .pf-next-fish { width: 48px; height: 40px; }
  .pf-next-l b { font-size: 22px; }
  .pf-betbox { gap: 8px; padding: 10px; }
  .pf-step { height: 38px; } .pf-qbtn { min-height: 36px; }
  .pf-btn { min-height: 58px; }
  .pf-mult, .pf-mult-hot { font-size: clamp(30px, 10vw, 46px); }
  /* 收钱那张在矮屏(主画面只剩 260px 上下)排成两行:「收钱成功」和「x倍数」并排在上、大字在下,河豚的脸才露得出来(标签不改,只换格子位置) */
  .pf-card.is-win { grid-template-columns: auto auto; grid-template-areas: "tag mult" "amt amt" "note note"; justify-content: center; column-gap: 8px; row-gap: 2px; padding: 10px 16px; }
  .pf-card.is-win .pf-card-tag { grid-area: tag; } .pf-card.is-win .pf-card-mult { grid-area: mult; align-self: center; }
  .pf-card.is-win .pf-card-amt { grid-area: amt; font-size: clamp(30px, 9.5vw, 40px); } .pf-card.is-win .pf-card-note { grid-area: note; }
  .pf-card-tag, .pf-card-mult { font-size: 17px; }
  /* 爆了那张也收矮一点(瘪掉的河豚飘得低):字小一档、「再来一局」44px(还是按得到的大小) */
  .pf-card.is-pop { padding: 10px 16px 12px; } .pf-card-big { font-size: 28px; } .pf-card-amt { font-size: 26px; } .pf-again { min-height: 44px; margin-top: 2px; }
}
/* 更矮的(360×513 这种):主画面让出地方,保证两颗大键整颗看得到 */
@media (max-height: 620px) {
  .pf-hero { min-height: 150px; }
  .pf-panel { gap: 6px; padding-top: 6px; }
  .pf-level { min-height: 34px; font-size: 14px; }
  .pf-next { min-height: 46px; padding-top: 2px; padding-bottom: 2px; }
  .pf-next-fish { width: 40px; height: 34px; }
  .pf-next-l small { font-size: 11px; }
  .pf-next-l b { font-size: 19px; }
  .pf-betbox { gap: 6px; padding: 8px; }
  .pf-step { height: 34px; } .pf-bet { font-size: 19px; } .pf-qbtn { min-height: 32px; }
  .pf-btn { min-height: 50px; } .pf-btn.is-dark small { font-size: 18px; } .pf-btn.is-neon { font-size: 19px; }
  .pf-hint { min-height: 18px; }
  .pf-multbox { top: 8px; padding: 4px 12px; } .pf-chev { width: 20px; height: 20px; }
  .pf-mult, .pf-mult-hot { font-size: clamp(26px, 9vw, 38px); }
  .pf-card { gap: 6px; padding: 12px 14px; } .pf-card-big { font-size: 26px; } .pf-card-amt { font-size: 24px; }
  /* 收钱那张原本那几条(.pf-card.is-win …)比上面重,要用同样重的选择器才压得下来:大字、留白一起缩(外文的说明会折两行) */
  .pf-card.is-win { gap: 4px; padding: 10px 14px; } .pf-card.is-win .pf-card-amt { font-size: 28px; }
  .pf-card-note { font-size: 11px; line-height: 1.25; }
  .pf-card-tag, .pf-card-mult { font-size: 16px; }
}

/* 手机开了「减少动态效果」:浮、鼓缩、发抖、红色脉冲、头晕、背景气泡、按下去挤一下憋着(puffer.js 本来就不挂;打气声、震动照样有)、压扁弹大、猛鼓、白闪、炸开、弹跳、星星、抖、闪、跳、金币、卡片弹出、
 * 按钮一明一暗、影片换支淡化都是装饰,全关(结果照样看得清:哪个表情、多大 —— 大小直接变过去、爆了往下沉一点、快爆了那层红色停在淡淡的一层;
 * 影片那边 puffer.js 不播、停在第一格,跟整张图一样是一张静止的画面)。
 * 选择器要跟挂动画的那条一字不差(一样重),不然压不过 */
@media (prefers-reduced-motion: reduce) {
  .pf-fishwrap.is-fish-pop, .pf-fishwrap.is-fish-win, .pf-clip.is-out, .pf-bob, .pf-body, .pf-fishwrap.is-fish-2 .pf-body, .pf-fishwrap.is-fish-3 .pf-body, .pf-fishwrap.is-fish-4 .pf-body, .pf-fishwrap.is-fish-4 .pf-jit, .pf-fishwrap.is-fish-pop .pf-bob, .pf-fishwrap.is-fish-pop .pf-body, .pf-hero.is-danger .pf-danger, .pf-amb, .pf-fishfx.is-squeeze, .pf-fishfx.is-relax, .pf-fishfx.is-puff, .pf-fishfx.is-puff-big, .pf-bub, .pf-fishfx.is-bulge, .pf-fishfx.is-popin, .pf-flash.is-flash, .pf-bit, .pf-fishfx.is-hop, .pf-star, .pf-scene.is-shake, .pf-multbox.is-hot .pf-mult-hot, .pf-multbox.is-hot::after, .pf-multbox.is-bump .pf-multtext, .pf-coin, .pf-card, .pf-btn.is-armed { animation: none; }
  .pf-coin, .pf-ambient, .pf-fizz, .pf-burst, .pf-stars { display: none; }
  .pf-multbox.is-hot .pf-mult-hot, .pf-multbox.is-hot::after { opacity: 1; }
  .pf-hero.is-danger .pf-danger { opacity: .55; }
}
