diff --git a/TUICallKit-Vue3/pages.json b/TUICallKit-Vue3/pages.json index 0ab33978..759b8eee 100644 --- a/TUICallKit-Vue3/pages.json +++ b/TUICallKit-Vue3/pages.json @@ -223,6 +223,15 @@ "backgroundColor": "#c7d2fe", "disableScroll": true } + }, + { + "path": "endless-game/index", + "style": { + "navigationStyle": "custom", + "navigationBarTitleText": "控糖消消乐", + "backgroundColor": "#eefbf4", + "disableScroll": false + } } ] } diff --git a/TUICallKit-Vue3/tongji/endless-game/README.md b/TUICallKit-Vue3/tongji/endless-game/README.md new file mode 100644 index 00000000..2afa8a5a --- /dev/null +++ b/TUICallKit-Vue3/tongji/endless-game/README.md @@ -0,0 +1,56 @@ +# 控糖消消乐独立功能包 + +此目录包含“控糖消消乐”无尽三消版的全部运行文件,可以整体复制到另一个 uni-app Vue 3 项目的 `tongji/endless-game/` 下。 + +## 目录内容 + +- `index.vue`:页面、棋盘算法、关卡主题、任务、道具、三/四/五连奖励和适老化交互。 +- `game-endless.scss`:完整页面与动画样式。 +- `components/TongjiIcon.vue`:本功能包使用的图标组件。 +- `composables/useGameSfx.js`:滑动、掉落、消除、连击和大奖音效。 +- `utils/svgDataUrl.js`:图标编码工具。 +- `assets/food/`:本游戏使用的全部食品与驼乳粉图片。 +- `viteAssetOutput.js`:把食品图输出到本功能分包,避免增加微信小程序主包体积。 + +## 迁移步骤 + +1. 整体复制 `endless-game` 文件夹到目标项目的 `tongji/` 目录。 +2. 在目标项目的 `vite.config` 中引入素材输出规则,并放到 `build.rollupOptions.output`: + +```js +import { endlessGameAssetFileNames } from './tongji/endless-game/viteAssetOutput.js' + +// defineConfig 内 +build: { + rollupOptions: { + output: { assetFileNames: endlessGameAssetFileNames } + } +} +``` + +3. 在目标项目 `pages.json` 的 `tongji` 分包 `pages` 数组中加入: + +```json +{ + "path": "endless-game/index", + "style": { + "navigationStyle": "custom", + "navigationBarTitleText": "控糖消消乐", + "backgroundColor": "#eefbf4", + "disableScroll": false + } +} +``` + +4. 使用 `/tongji/endless-game/index` 打开游戏。 +5. 微信小程序后台需要允许音频域名:`https://gz-1349751149.cos.ap-guangzhou.myqcloud.com`。 + +## 说明 + +- 旧地址 `/tongji/pages/game` 和 `/tongji/pages/game-endless` 只是当前项目的兼容跳转,迁移时不需要复制。 +- 当前项目原来的“糖分突袭”源码保存在 `tongji/legacy-game/game.vue`,没有注册为页面,不参与打包。 +- 连消小目标按“连消×2”累计 2 次;连续 6 次普通消除没有连消时,下一次掉落会提供连消机会。“连消×3”保留为额外积分、驼乳粉与撒花惊喜,不阻挡主线任务。 +- 横向 3 个与竖向 3 个相交形成 L/T 形五消时,会在交点生成带高对比 L 标记的范围爆破棋子;该棋子再次被消除时清除周围九格。 +- 进入游戏时展示“本周 7 人同行榜”界面原型,包含前后名次、差距提示和可切换的同行/亲友鼓励;顶部“本周同行”可再次打开。当前榜单明确标注为预览数据,正式用户关系和周榜成绩需要后端接口接入。 +- 本功能包依赖 uni-app Vue 3 和 `@dcloudio/uni-app`,不依赖当前项目其他业务组件。 +- 食物风险等级和科普文案集中在 `index.vue` 的 `FOODS` 配置中,正式上线前仍需由医院医生审核。 diff --git a/TUICallKit-Vue3/tongji/endless-game/assets/food/apple.jpg b/TUICallKit-Vue3/tongji/endless-game/assets/food/apple.jpg new file mode 100644 index 00000000..1be0c448 Binary files /dev/null and b/TUICallKit-Vue3/tongji/endless-game/assets/food/apple.jpg differ diff --git a/TUICallKit-Vue3/tongji/endless-game/assets/food/banana.jpg b/TUICallKit-Vue3/tongji/endless-game/assets/food/banana.jpg new file mode 100644 index 00000000..cd4ae5eb Binary files /dev/null and b/TUICallKit-Vue3/tongji/endless-game/assets/food/banana.jpg differ diff --git a/TUICallKit-Vue3/tongji/endless-game/assets/food/blackCoffee.jpg b/TUICallKit-Vue3/tongji/endless-game/assets/food/blackCoffee.jpg new file mode 100644 index 00000000..a6288bce Binary files /dev/null and b/TUICallKit-Vue3/tongji/endless-game/assets/food/blackCoffee.jpg differ diff --git a/TUICallKit-Vue3/tongji/endless-game/assets/food/broccoli.jpg b/TUICallKit-Vue3/tongji/endless-game/assets/food/broccoli.jpg new file mode 100644 index 00000000..c3bb36e7 Binary files /dev/null and b/TUICallKit-Vue3/tongji/endless-game/assets/food/broccoli.jpg differ diff --git a/TUICallKit-Vue3/tongji/endless-game/assets/food/brownRice.jpg b/TUICallKit-Vue3/tongji/endless-game/assets/food/brownRice.jpg new file mode 100644 index 00000000..ba73bae0 Binary files /dev/null and b/TUICallKit-Vue3/tongji/endless-game/assets/food/brownRice.jpg differ diff --git a/TUICallKit-Vue3/tongji/endless-game/assets/food/camel.jpg b/TUICallKit-Vue3/tongji/endless-game/assets/food/camel.jpg new file mode 100644 index 00000000..f4c4f518 Binary files /dev/null and b/TUICallKit-Vue3/tongji/endless-game/assets/food/camel.jpg differ diff --git a/TUICallKit-Vue3/tongji/endless-game/assets/food/celery.jpg b/TUICallKit-Vue3/tongji/endless-game/assets/food/celery.jpg new file mode 100644 index 00000000..c248bee4 Binary files /dev/null and b/TUICallKit-Vue3/tongji/endless-game/assets/food/celery.jpg differ diff --git a/TUICallKit-Vue3/tongji/endless-game/assets/food/centuryCongee.jpg b/TUICallKit-Vue3/tongji/endless-game/assets/food/centuryCongee.jpg new file mode 100644 index 00000000..643ef6c6 Binary files /dev/null and b/TUICallKit-Vue3/tongji/endless-game/assets/food/centuryCongee.jpg differ diff --git a/TUICallKit-Vue3/tongji/endless-game/assets/food/chicken.jpg b/TUICallKit-Vue3/tongji/endless-game/assets/food/chicken.jpg new file mode 100644 index 00000000..1c155d22 Binary files /dev/null and b/TUICallKit-Vue3/tongji/endless-game/assets/food/chicken.jpg differ diff --git a/TUICallKit-Vue3/tongji/endless-game/assets/food/coffee.jpg b/TUICallKit-Vue3/tongji/endless-game/assets/food/coffee.jpg new file mode 100644 index 00000000..df6f57be Binary files /dev/null and b/TUICallKit-Vue3/tongji/endless-game/assets/food/coffee.jpg differ diff --git a/TUICallKit-Vue3/tongji/endless-game/assets/food/congee.jpg b/TUICallKit-Vue3/tongji/endless-game/assets/food/congee.jpg new file mode 100644 index 00000000..da4234d8 Binary files /dev/null and b/TUICallKit-Vue3/tongji/endless-game/assets/food/congee.jpg differ diff --git a/TUICallKit-Vue3/tongji/endless-game/assets/food/corn.jpg b/TUICallKit-Vue3/tongji/endless-game/assets/food/corn.jpg new file mode 100644 index 00000000..191a0376 Binary files /dev/null and b/TUICallKit-Vue3/tongji/endless-game/assets/food/corn.jpg differ diff --git a/TUICallKit-Vue3/tongji/endless-game/assets/food/cucumber.jpg b/TUICallKit-Vue3/tongji/endless-game/assets/food/cucumber.jpg new file mode 100644 index 00000000..d1f53649 Binary files /dev/null and b/TUICallKit-Vue3/tongji/endless-game/assets/food/cucumber.jpg differ diff --git a/TUICallKit-Vue3/tongji/endless-game/assets/food/egg.jpg b/TUICallKit-Vue3/tongji/endless-game/assets/food/egg.jpg new file mode 100644 index 00000000..5efadddd Binary files /dev/null and b/TUICallKit-Vue3/tongji/endless-game/assets/food/egg.jpg differ diff --git a/TUICallKit-Vue3/tongji/endless-game/assets/food/eightCongee.jpg b/TUICallKit-Vue3/tongji/endless-game/assets/food/eightCongee.jpg new file mode 100644 index 00000000..91837533 Binary files /dev/null and b/TUICallKit-Vue3/tongji/endless-game/assets/food/eightCongee.jpg differ diff --git a/TUICallKit-Vue3/tongji/endless-game/assets/food/grainMantou.jpg b/TUICallKit-Vue3/tongji/endless-game/assets/food/grainMantou.jpg new file mode 100644 index 00000000..ea42db37 Binary files /dev/null and b/TUICallKit-Vue3/tongji/endless-game/assets/food/grainMantou.jpg differ diff --git a/TUICallKit-Vue3/tongji/endless-game/assets/food/greenBean.jpg b/TUICallKit-Vue3/tongji/endless-game/assets/food/greenBean.jpg new file mode 100644 index 00000000..c34739c6 Binary files /dev/null and b/TUICallKit-Vue3/tongji/endless-game/assets/food/greenBean.jpg differ diff --git a/TUICallKit-Vue3/tongji/endless-game/assets/food/lettuce.jpg b/TUICallKit-Vue3/tongji/endless-game/assets/food/lettuce.jpg new file mode 100644 index 00000000..b6420370 Binary files /dev/null and b/TUICallKit-Vue3/tongji/endless-game/assets/food/lettuce.jpg differ diff --git a/TUICallKit-Vue3/tongji/endless-game/assets/food/mango.jpg b/TUICallKit-Vue3/tongji/endless-game/assets/food/mango.jpg new file mode 100644 index 00000000..a0ad188b Binary files /dev/null and b/TUICallKit-Vue3/tongji/endless-game/assets/food/mango.jpg differ diff --git a/TUICallKit-Vue3/tongji/endless-game/assets/food/milk.jpg b/TUICallKit-Vue3/tongji/endless-game/assets/food/milk.jpg new file mode 100644 index 00000000..e1b1fc52 Binary files /dev/null and b/TUICallKit-Vue3/tongji/endless-game/assets/food/milk.jpg differ diff --git a/TUICallKit-Vue3/tongji/endless-game/assets/food/milkTea.jpg b/TUICallKit-Vue3/tongji/endless-game/assets/food/milkTea.jpg new file mode 100644 index 00000000..ca7ae135 Binary files /dev/null and b/TUICallKit-Vue3/tongji/endless-game/assets/food/milkTea.jpg differ diff --git a/TUICallKit-Vue3/tongji/endless-game/assets/food/milletCongee.jpg b/TUICallKit-Vue3/tongji/endless-game/assets/food/milletCongee.jpg new file mode 100644 index 00000000..6e904a28 Binary files /dev/null and b/TUICallKit-Vue3/tongji/endless-game/assets/food/milletCongee.jpg differ diff --git a/TUICallKit-Vue3/tongji/endless-game/assets/food/mushroom.jpg b/TUICallKit-Vue3/tongji/endless-game/assets/food/mushroom.jpg new file mode 100644 index 00000000..91c67712 Binary files /dev/null and b/TUICallKit-Vue3/tongji/endless-game/assets/food/mushroom.jpg differ diff --git a/TUICallKit-Vue3/tongji/endless-game/assets/food/noodleSoup.jpg b/TUICallKit-Vue3/tongji/endless-game/assets/food/noodleSoup.jpg new file mode 100644 index 00000000..ca10d534 Binary files /dev/null and b/TUICallKit-Vue3/tongji/endless-game/assets/food/noodleSoup.jpg differ diff --git a/TUICallKit-Vue3/tongji/endless-game/assets/food/onion.jpg b/TUICallKit-Vue3/tongji/endless-game/assets/food/onion.jpg new file mode 100644 index 00000000..a9863a2a Binary files /dev/null and b/TUICallKit-Vue3/tongji/endless-game/assets/food/onion.jpg differ diff --git a/TUICallKit-Vue3/tongji/endless-game/assets/food/orangeJuice.jpg b/TUICallKit-Vue3/tongji/endless-game/assets/food/orangeJuice.jpg new file mode 100644 index 00000000..c5841a2e Binary files /dev/null and b/TUICallKit-Vue3/tongji/endless-game/assets/food/orangeJuice.jpg differ diff --git a/TUICallKit-Vue3/tongji/endless-game/assets/food/pepper.jpg b/TUICallKit-Vue3/tongji/endless-game/assets/food/pepper.jpg new file mode 100644 index 00000000..dc624a3c Binary files /dev/null and b/TUICallKit-Vue3/tongji/endless-game/assets/food/pepper.jpg differ diff --git a/TUICallKit-Vue3/tongji/endless-game/assets/food/pineapple.jpg b/TUICallKit-Vue3/tongji/endless-game/assets/food/pineapple.jpg new file mode 100644 index 00000000..b50b07ce Binary files /dev/null and b/TUICallKit-Vue3/tongji/endless-game/assets/food/pineapple.jpg differ diff --git a/TUICallKit-Vue3/tongji/endless-game/assets/food/potato.jpg b/TUICallKit-Vue3/tongji/endless-game/assets/food/potato.jpg new file mode 100644 index 00000000..597ecbc2 Binary files /dev/null and b/TUICallKit-Vue3/tongji/endless-game/assets/food/potato.jpg differ diff --git a/TUICallKit-Vue3/tongji/endless-game/assets/food/riceNoodleSoup.jpg b/TUICallKit-Vue3/tongji/endless-game/assets/food/riceNoodleSoup.jpg new file mode 100644 index 00000000..b782873b Binary files /dev/null and b/TUICallKit-Vue3/tongji/endless-game/assets/food/riceNoodleSoup.jpg differ diff --git a/TUICallKit-Vue3/tongji/endless-game/assets/food/shrimp.jpg b/TUICallKit-Vue3/tongji/endless-game/assets/food/shrimp.jpg new file mode 100644 index 00000000..2784bd16 Binary files /dev/null and b/TUICallKit-Vue3/tongji/endless-game/assets/food/shrimp.jpg differ diff --git a/TUICallKit-Vue3/tongji/endless-game/assets/food/soda.jpg b/TUICallKit-Vue3/tongji/endless-game/assets/food/soda.jpg new file mode 100644 index 00000000..bc108a84 Binary files /dev/null and b/TUICallKit-Vue3/tongji/endless-game/assets/food/soda.jpg differ diff --git a/TUICallKit-Vue3/tongji/endless-game/assets/food/strawberry.jpg b/TUICallKit-Vue3/tongji/endless-game/assets/food/strawberry.jpg new file mode 100644 index 00000000..11b341fc Binary files /dev/null and b/TUICallKit-Vue3/tongji/endless-game/assets/food/strawberry.jpg differ diff --git a/TUICallKit-Vue3/tongji/endless-game/assets/food/taro.jpg b/TUICallKit-Vue3/tongji/endless-game/assets/food/taro.jpg new file mode 100644 index 00000000..b016510b Binary files /dev/null and b/TUICallKit-Vue3/tongji/endless-game/assets/food/taro.jpg differ diff --git a/TUICallKit-Vue3/tongji/endless-game/assets/food/tea.jpg b/TUICallKit-Vue3/tongji/endless-game/assets/food/tea.jpg new file mode 100644 index 00000000..2e7c5c8c Binary files /dev/null and b/TUICallKit-Vue3/tongji/endless-game/assets/food/tea.jpg differ diff --git a/TUICallKit-Vue3/tongji/endless-game/assets/food/tomato.jpg b/TUICallKit-Vue3/tongji/endless-game/assets/food/tomato.jpg new file mode 100644 index 00000000..0ceb6f4f Binary files /dev/null and b/TUICallKit-Vue3/tongji/endless-game/assets/food/tomato.jpg differ diff --git a/TUICallKit-Vue3/tongji/endless-game/assets/food/udon.jpg b/TUICallKit-Vue3/tongji/endless-game/assets/food/udon.jpg new file mode 100644 index 00000000..ebd35d51 Binary files /dev/null and b/TUICallKit-Vue3/tongji/endless-game/assets/food/udon.jpg differ diff --git a/TUICallKit-Vue3/tongji/endless-game/assets/food/wonton.jpg b/TUICallKit-Vue3/tongji/endless-game/assets/food/wonton.jpg new file mode 100644 index 00000000..b5876166 Binary files /dev/null and b/TUICallKit-Vue3/tongji/endless-game/assets/food/wonton.jpg differ diff --git a/TUICallKit-Vue3/tongji/endless-game/components/TongjiIcon.vue b/TUICallKit-Vue3/tongji/endless-game/components/TongjiIcon.vue new file mode 100644 index 00000000..056aae92 --- /dev/null +++ b/TUICallKit-Vue3/tongji/endless-game/components/TongjiIcon.vue @@ -0,0 +1,190 @@ + + + + + diff --git a/TUICallKit-Vue3/tongji/endless-game/composables/useGameSfx.js b/TUICallKit-Vue3/tongji/endless-game/composables/useGameSfx.js new file mode 100644 index 00000000..2e49f363 --- /dev/null +++ b/TUICallKit-Vue3/tongji/endless-game/composables/useGameSfx.js @@ -0,0 +1,343 @@ +/** + * 糖分突袭 · 游戏音效(InnerAudioContext 池化,H5/小程序通用) + * 每种操作独立音轨 + playbackRate 变调,避免“全是同一个声” + */ +import { ref, onUnmounted } from 'vue' + +const STORAGE_KEY = 'tongji_game_sfx_enabled' +const COS = 'https://gz-1349751149.cos.ap-guangzhou.myqcloud.com/uploads/file' + +/** 9 条基础素材(已上传 COS,小程序域名白名单内) */ +const SRC = { + tap: `${COS}/20260526/20260526105128557ef8669.mp3`, + swoosh: `${COS}/20260527/202605271539371ebe13672.mp3`, + pop: `${COS}/20260526/202605261051282a0a94508.mp3`, + buzz: `${COS}/20260527/202605271539376ff628472.mp3`, + chime: `${COS}/20260528/202605280937077bad76716.mp3`, + fanfare: `${COS}/20260528/20260528093707b021f5794.mp3`, + blast: `${COS}/20260528/20260528093707ebd4d5733.mp3`, + sparkle: `${COS}/20260528/20260528093707016f07427.mp3`, + stinger: `${COS}/20260528/20260528093709c669f4659.mp3` +} + +/** + * 音效表:src / volume / rate(playbackRate) / pool + * rate 不同可在同一素材上听出明显差异 + */ +const SFX = { + // —— 交互 —— + select: { src: SRC.tap, volume: 0.32, rate: 1.05, pool: 3 }, + deselect: { src: SRC.tap, volume: 0.22, rate: 0.82, pool: 2 }, + hint: { src: SRC.tap, volume: 0.26, rate: 1.28, pool: 2 }, + + swap: { src: SRC.swoosh, volume: 0.38, rate: 1.0, pool: 3 }, + swapFail: { src: SRC.buzz, volume: 0.36, rate: 1.18, pool: 2 }, + + drop: { src: SRC.pop, volume: 0.3, rate: 0.88, pool: 4 }, + dropLight: { src: SRC.pop, volume: 0.22, rate: 1.22, pool: 2 }, + + // —— 消除(按连数)—— + match3: { src: SRC.chime, volume: 0.48, rate: 1.0, pool: 4 }, + match4: { src: SRC.fanfare, volume: 0.54, rate: 1.06, pool: 3 }, + match5: { src: SRC.blast, volume: 0.62, rate: 1.0, pool: 3 }, + + // —— 消除(按 GI 主色)—— + matchLowGi: { src: SRC.chime, volume: 0.42, rate: 0.86, pool: 2 }, + matchMidGi: { src: SRC.chime, volume: 0.46, rate: 1.02, pool: 2 }, + matchHighGi: { src: SRC.buzz, volume: 0.44, rate: 0.92, pool: 2 }, + + // —— 连消 —— + combo2: { src: SRC.fanfare, volume: 0.56, rate: 1.12, pool: 2 }, + combo3: { src: SRC.blast, volume: 0.64, rate: 1.08, pool: 2 }, + comboMega: { src: SRC.blast, volume: 0.72, rate: 1.22, pool: 2 }, + + // —— 道具 —— + insulin: { src: SRC.sparkle, volume: 0.5, rate: 1.15, pool: 2 }, + fiber: { src: SRC.swoosh, volume: 0.48, rate: 1.32, pool: 2 }, + meal: { src: SRC.stinger, volume: 0.42, rate: 1.35, pool: 1 }, + reshuffle: { src: SRC.swoosh, volume: 0.46, rate: 0.72, pool: 2 }, + + // —— 反馈 —— + score: { src: SRC.tap, volume: 0.28, rate: 1.38, pool: 2 }, + goalTick: { src: SRC.chime, volume: 0.36, rate: 1.45, pool: 2 }, + meterWarn: { src: SRC.buzz, volume: 0.5, rate: 1.0, pool: 2 }, + meterDanger: { src: SRC.buzz, volume: 0.58, rate: 0.78, pool: 2 }, + meterStable: { src: SRC.pop, volume: 0.24, rate: 1.05, pool: 1 }, + + win: { src: SRC.fanfare, volume: 0.66, rate: 1.0, pool: 1 }, + winStinger: { src: SRC.stinger, volume: 0.38, rate: 1.5, pool: 1 }, + lose: { src: SRC.buzz, volume: 0.52, rate: 0.68, pool: 1 }, + + // 兼容旧名 + match: { src: SRC.chime, volume: 0.48, rate: 1.0, pool: 4 }, + combo: { src: SRC.fanfare, volume: 0.56, rate: 1.1, pool: 3 }, + heal: { src: SRC.pop, volume: 0.44, rate: 1.18, pool: 2 }, + boost: { src: SRC.sparkle, volume: 0.52, rate: 1.0, pool: 2 }, + invalid: { src: SRC.buzz, volume: 0.4, rate: 1.15, pool: 2 }, + danger: { src: SRC.blast, volume: 0.58, rate: 0.95, pool: 2 } +} + +const WARMUP_SFX = [ + 'select', 'swap', 'swapFail', 'drop', 'dropLight', + 'match3', 'match4', 'match5', 'combo2', 'combo3', 'comboMega', + 'insulin', 'sparkle', 'reshuffle', 'lose' +] + +function createAudioContext() { + if (typeof Audio !== 'undefined') { + const audio = new Audio() + audio.preload = 'auto' + return { + get src() { return audio.src }, + set src(value) { audio.src = value }, + get volume() { return audio.volume }, + set volume(value) { audio.volume = value }, + get playbackRate() { return audio.playbackRate }, + set playbackRate(value) { audio.playbackRate = value }, + play() { + const promise = audio.play() + if (promise?.catch) promise.catch(() => {}) + }, + stop() { + audio.pause() + try { audio.currentTime = 0 } catch (_) {} + }, + seek(time) { + try { audio.currentTime = time } catch (_) {} + }, + destroy() { + audio.pause() + audio.removeAttribute('src') + } + } + } + return uni.createInnerAudioContext() +} + +class SfxPool { + constructor(src, size, volume, rate = 1) { + this.src = src + this.size = size + this.volume = volume + this.rate = rate + this.list = [] + this.cursor = 0 + this.warmedUp = false + } + + createOne() { + const ctx = createAudioContext() + ctx.src = this.src + ctx.obeyMuteSwitch = false + ctx.autoplay = false + ctx.volume = this.volume + try { + ctx.playbackRate = this.rate + } catch (_) {} + this.list.push(ctx) + return ctx + } + + create() { + while (this.list.length < this.size) this.createOne() + } + + warmUp() { + if (this.warmedUp) return + const ctx = this.list[0] || this.createOne() + try { + ctx.volume = 0 + ctx.play() + setTimeout(() => { + try { + ctx.stop() + ctx.volume = this.volume + } catch (_) {} + }, 60) + } catch (_) {} + if (this.size > 1) this.cursor = 1 + this.warmedUp = true + } + + play(scale = 1, rateMul = 1) { + if (this.list.length < this.size) this.create() + const ctx = this.list[this.cursor % this.list.length] + this.cursor += 1 + const vol = Math.min(1, this.volume * scale) + const rate = Math.min(2, Math.max(0.5, this.rate * rateMul)) + try { + ctx.volume = vol + try { + ctx.playbackRate = rate + } catch (_) {} + ctx.stop() + ctx.seek(0) + ctx.play() + } catch (_) { + try { + ctx.play() + } catch (e) {} + } + } + + destroy() { + this.list.forEach((ctx) => { + try { + ctx.stop() + ctx.destroy() + } catch (_) {} + }) + this.list = [] + this.warmedUp = false + } +} + +function dominantGi(tiles = []) { + const cnt = { low: 0, mid: 0, high: 0 } + tiles.forEach((t) => { + const g = t?.type?.gi + if (g && cnt[g] !== undefined) cnt[g] += 1 + }) + let best = 'mid' + let max = 0 + Object.entries(cnt).forEach(([k, v]) => { + if (v > max) { + max = v + best = k + } + }) + return best +} + +export function useGameSfx() { + const enabled = ref(true) + const pools = {} + + try { + const stored = uni.getStorageSync(STORAGE_KEY) + if (stored === false || stored === '0' || stored === 0) { + enabled.value = false + } + } catch (_) {} + + function ensureAudioOption() { + // #ifdef MP-WEIXIN + try { + uni.setInnerAudioOption({ + obeyMuteSwitch: false, + mixWithOther: true + }) + } catch (_) {} + // #endif + } + + function getPool(name) { + const cfg = SFX[name] + if (!cfg) return null + if (!pools[name]) { + pools[name] = new SfxPool(cfg.src, cfg.pool, cfg.volume, cfg.rate ?? 1) + } + return pools[name] + } + + function warmUp() { + if (!enabled.value) return + ensureAudioOption() + const names = typeof Audio !== 'undefined' ? ['select'] : WARMUP_SFX + names.forEach((name) => { + getPool(name)?.warmUp() + }) + } + + function play(name, scale = 1, rateMul = 1) { + if (!enabled.value) return + getPool(name)?.play(scale, rateMul) + } + + function playLayer(primary, secondary, delayMs = 70, secScale = 0.6) { + play(primary) + if (secondary) { + setTimeout(() => play(secondary, secScale), delayMs) + } + } + + function playMatch({ matchCount = 3, chain = 0, tiles = [] } = {}) { + if (!enabled.value) return + const giTrack = (() => { + const gi = dominantGi(tiles) + if (gi === 'low') return 'matchLowGi' + if (gi === 'high') return 'matchHighGi' + return 'matchMidGi' + })() + + if (chain >= 3) { + playLayer('comboMega', giTrack, 90, 0.55) + return + } + if (chain === 2) { + playLayer('combo3', giTrack, 80, 0.5) + return + } + if (chain === 1) { + playLayer('combo2', giTrack, 70, 0.45) + return + } + + if (matchCount >= 5) { + playLayer('match5', 'matchHighGi', 85, 0.5) + } else if (matchCount >= 4) { + playLayer('match4', giTrack, 65, 0.48) + } else { + play(giTrack, 1, 1) + setTimeout(() => play('match3', 0.85), 40) + } + } + + function playDrop(count = 1) { + if (!enabled.value || count <= 0) return + const name = count >= 4 ? 'drop' : 'dropLight' + play(name, Math.min(1.2, 0.85 + count * 0.04), count >= 6 ? 0.9 : 1) + } + + function playMeter(delta, level) { + if (!enabled.value) return + if (level > 70) play('meterDanger', 1 + (level - 70) * 0.02) + else if (level < 30) play('meterWarn', 0.9, 0.95) + else if (delta > 8) play('meterWarn', 0.75) + else if (delta < -5) play('meterStable', 1.1) + } + + function toggleEnabled() { + enabled.value = !enabled.value + try { + uni.setStorageSync(STORAGE_KEY, enabled.value ? '1' : '0') + } catch (_) {} + if (enabled.value) { + warmUp() + play('select') + } + } + + function destroyAll() { + Object.keys(pools).forEach((key) => { + pools[key]?.destroy() + delete pools[key] + }) + } + + onUnmounted(() => { + destroyAll() + }) + + return { + enabled, + play, + playLayer, + playMatch, + playDrop, + playMeter, + warmUp, + toggleEnabled, + destroyAll + } +} diff --git a/TUICallKit-Vue3/tongji/endless-game/game-endless.scss b/TUICallKit-Vue3/tongji/endless-game/game-endless.scss new file mode 100644 index 00000000..069de3fc --- /dev/null +++ b/TUICallKit-Vue3/tongji/endless-game/game-endless.scss @@ -0,0 +1,559 @@ +.eg-page { + min-height: 100vh; + color: #173b32; + background: linear-gradient(180deg, #eefbf4 0%, #f9f3dd 56%, #fffaf1 100%); + font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Microsoft YaHei', sans-serif; +} + +.eg-confetti-layer { position: fixed; z-index: 80; inset: 0; overflow: hidden; pointer-events: none; } +.eg-confetti-piece { + position: absolute; + top: -70rpx; + display: block; + border-radius: 3rpx; + box-shadow: 0 2rpx 4rpx rgba(0,0,0,.12); + animation-name: egConfettiFall; + animation-timing-function: cubic-bezier(.18,.72,.35,1); + animation-fill-mode: both; +} +.eg-confetti-piece.is-emoji { width: auto !important; height: auto !important; background: transparent !important; box-shadow: none; font-size: 42rpx; } + +.eg-nav { + position: relative; + display: flex; + align-items: center; + justify-content: space-between; + padding-right: 28rpx; + padding-bottom: 16rpx; + padding-left: 28rpx; +} + +.eg-nav-btn { + display: flex; + width: 72rpx; + height: 72rpx; + align-items: center; + justify-content: center; + border: 2rpx solid rgba(21, 94, 75, .12); + border-radius: 24rpx; + background: rgba(255,255,255,.82); +} + +.eg-nav-actions { display: flex; width: 156rpx; justify-content: flex-end; gap: 10rpx; } +.eg-nav-btn--small { width: 68rpx; height: 68rpx; border-radius: 22rpx; } + +.eg-title-wrap { position: absolute; left: 50%; display: flex; flex-direction: column; align-items: center; transform: translateX(-50%); } +.eg-title { color: #155e4b; font-size: 40rpx; font-weight: 800; } +.eg-subtitle { margin-top: 2rpx; color: #648278; font-size: 24rpx; } +.eg-content { padding: 0 16rpx 48rpx; } + +.eg-score-card { + display: flex; + align-items: center; + margin-bottom: 16rpx; + padding: 18rpx 24rpx; + border: 2rpx solid rgba(21, 94, 75, .1); + border-radius: 28rpx; + background: rgba(255,255,255,.9); + box-shadow: 0 10rpx 28rpx rgba(32, 78, 43, .08); +} + +.eg-stat { display: flex; flex: 1; flex-direction: column; align-items: center; } +.eg-stat--main { border-right: 2rpx solid #e2eee8; border-left: 2rpx solid #e2eee8; } +.eg-stat-label { color: #71847d; font-size: 24rpx; } +.eg-stat-value { margin-top: 4rpx; color: #204e2b; font-size: 38rpx; font-weight: 800; } +.eg-stat-score { margin-top: 2rpx; color: #e16f24; font-size: 46rpx; font-weight: 900; } +.eg-stat--rank { position: relative; cursor: pointer; } +.eg-stat--rank.is-locked { opacity: .52; } +.eg-stat-rank-value { margin-top: 1rpx; color: #176b52; font-size: 35rpx; font-weight: 1000; line-height: 1.08; } +.eg-stat-rank-hint { margin-top: 2rpx; color: #df7427; font-size: 18rpx; font-weight: 800; } + +.eg-task-card { + margin-bottom: 16rpx; + padding: 18rpx 22rpx; + border-radius: 24rpx; + background: rgba(255, 249, 223, .94); + box-shadow: 0 8rpx 22rpx rgba(118, 89, 25, .08); +} +.eg-task-card { position: relative; overflow: visible; } + +.eg-task-reward-flight { + position: absolute; + z-index: 72; + top: 4rpx; + left: 42%; + display: flex; + width: 112rpx; + height: 92rpx; + align-items: center; + justify-content: center; + border: 4rpx solid rgba(255,255,255,.96); + border-radius: 24rpx; + background: #fff7e8; + box-shadow: 0 10rpx 24rpx rgba(159,94,23,.28); + animation: egTaskRewardFlight 1.12s cubic-bezier(.18,.78,.22,1) both; + pointer-events: none; +} +.eg-task-reward-flight > image { position: relative; z-index: 2; width: 78rpx; height: 66rpx; } +.eg-task-reward-flight > text { position: absolute; z-index: 3; top: -14rpx; right: -16rpx; display: flex; width: 48rpx; height: 48rpx; align-items: center; justify-content: center; border: 4rpx solid #fff; border-radius: 50%; color: #fff; background: #ed7625; box-shadow: 0 5rpx 12rpx rgba(191,75,19,.3); font-size: 25rpx; font-weight: 1000; } +.eg-task-reward-glow { position: absolute; z-index: 1; inset: -15rpx; border-radius: 32rpx; background: radial-gradient(circle, rgba(255,210,67,.52), rgba(255,210,67,0) 68%); animation: egRewardGlow .42s ease-in-out infinite alternate; } + +.eg-task-head { display: flex; align-items: center; justify-content: space-between; color: #7c5b1b; font-size: 27rpx; font-weight: 700; } +.eg-task-name { display: flex; align-items: center; gap: 10rpx; } +.eg-task-count { color: #9a6718; font-size: 29rpx; } +.eg-progress { height: 14rpx; margin-top: 12rpx; overflow: hidden; border-radius: 999rpx; background: #eadfbd; } +.eg-progress-fill { height: 100%; border-radius: inherit; background: linear-gradient(90deg, #f0a128, #f4c34f); transition: width .25s; } + +.eg-board-shell { + position: relative; + padding: 14rpx; + border: 2rpx solid rgba(34, 103, 79, .16); + border-radius: 34rpx; + background: rgba(255,255,255,.92); + box-shadow: 0 18rpx 44rpx rgba(29, 78, 59, .12); +} + +.eg-board-top { display: flex; align-items: center; justify-content: space-between; margin: 0 4rpx 14rpx; } +.eg-board-bonuses { display: flex; align-items: center; gap: 8rpx; } +.eg-combo { padding: 8rpx 16rpx; border-radius: 999rpx; color: #45685d; background: #e8f3ee; font-size: 25rpx; font-weight: 700; } +.eg-combo.is-hot { color: #fff; background: linear-gradient(135deg, #f59e0b, #ea580c); } +.eg-double-state { padding: 8rpx 14rpx; border: 2rpx solid rgba(255,255,255,.9); border-radius: 999rpx; color: #fff; background: linear-gradient(135deg, #7c3aed, #d946ef); box-shadow: 0 4rpx 12rpx rgba(124,58,237,.24); font-size: 23rpx; font-weight: 900; white-space: nowrap; } +.eg-risk { display: flex; align-items: center; gap: 10rpx; color: #8d512c; font-size: 24rpx; } +.eg-risk-dots { display: flex; gap: 5rpx; } +.eg-risk-dot { width: 12rpx; height: 12rpx; border-radius: 50%; background: #ead8c9; } +.eg-risk-dot.on { background: #e96b3b; box-shadow: 0 0 0 3rpx rgba(233,107,59,.12); } +.eg-board { display: flex; flex-direction: column; gap: 8rpx; opacity: 1; transition: opacity .15s; } +.eg-board.is-busy { opacity: .82; } +.eg-row { display: flex; gap: 8rpx; } + +.eg-cell { + position: relative; + display: flex; + height: 166rpx; + flex: 1; + flex-direction: column; + align-items: center; + justify-content: center; + overflow: hidden; + border: 5rpx solid transparent; + border-radius: 26rpx; + box-shadow: inset 0 -5rpx 0 rgba(0,0,0,.05), 0 5rpx 12rpx rgba(31, 66, 53, .08); + transition: transform .16s cubic-bezier(.2,.8,.2,1), border-color .15s, opacity .16s, filter .16s; + will-change: transform; +} + +.eg-cell.is-dragging { transition: transform .055s linear, border-color .15s; } +.eg-cell.is-selected { z-index: 2; border-color: #167d61; transform: scale(1.06); box-shadow: 0 0 0 6rpx rgba(22,125,97,.14); } +.eg-cell.is-camel-target { border-color: #f59e0b; animation: egPulse 1s infinite; } +.eg-replace-target { position: absolute; z-index: 7; right: 7rpx; bottom: 6rpx; padding: 4rpx 10rpx; border: 2rpx solid #fff; border-radius: 999rpx; color: #fff; background: #1570a6; box-shadow: 0 3rpx 9rpx rgba(20,91,135,.28); font-size: 20rpx; font-weight: 900; } +.eg-cell.is-clearing { + z-index: 8; + border-color: #fff; + animation: egHit .22s cubic-bezier(.2,.9,.3,1) forwards; + filter: brightness(1.28) saturate(1.25); +} +.eg-cell.is-yellow, +.eg-cell.is-cream, +.eg-cell.is-red, +.eg-cell.is-purple { background: #fffaf0; } +.eg-cell.is-level-low { background: #f4faf6; } +.eg-cell.is-level-mid { background: #fff9eb; } +.eg-cell.is-orange, +.eg-cell.is-level-high { background: #fff0e4; } +.eg-cell.is-milk { background: #edf7ff; } +.eg-cell.is-high { border-color: #ef7b45; } +.eg-food-image { width: 132rpx; height: 92rpx; margin-top: 7rpx; border-radius: 17rpx; filter: saturate(1.06) contrast(1.03); } +.eg-food-name { margin-top: 8rpx; color: #203d34; font-size: 29rpx; font-weight: 900; letter-spacing: 1rpx; line-height: 1.1; } +.eg-sugar-tag { position: absolute; top: 5rpx; right: 5rpx; min-width: 52rpx; padding: 4rpx 10rpx; border: 2rpx solid #fff; border-radius: 999rpx; color: #fff; box-shadow: 0 3rpx 8rpx rgba(25,55,45,.22); font-size: 23rpx; font-weight: 900; line-height: 1.25; text-align: center; } +.eg-sugar-tag.is-low { color: #316a54; border-color: rgba(255,255,255,.82); background: #dcefe6; box-shadow: 0 2rpx 5rpx rgba(32,101,75,.1); } +.eg-sugar-tag.is-mid { color: #735a15; border-color: rgba(255,255,255,.9); background: #f7e7a8; box-shadow: 0 2rpx 6rpx rgba(137,91,0,.15); } +.eg-sugar-tag.is-high { background: #d9431f; box-shadow: 0 4rpx 10rpx rgba(163,45,21,.32); } +.eg-sugar-tag.is-prop { background: #2563a8; } +.eg-special-mark { position: absolute; z-index: 12; bottom: 5rpx; left: 6rpx; display: flex; width: 40rpx; height: 40rpx; align-items: center; justify-content: center; border: 3rpx solid rgba(255,255,255,.96); border-radius: 50%; color: #fff; background: #6d28d9; box-shadow: 0 4rpx 11rpx rgba(73,31,130,.3); font-size: 23rpx; font-weight: 900; } +.eg-special-mark.is-row { width: 58rpx; height: 40rpx; border-radius: 999rpx; background: linear-gradient(135deg, #ffd64d, #ff8a18 48%, #e83b22); box-shadow: 0 0 0 4rpx rgba(255,172,30,.2), 0 4rpx 15rpx rgba(203,59,26,.45); animation: egFlameReady .7s ease-in-out infinite alternate; } +.eg-special-mark.is-col { width: 40rpx; height: 58rpx; border-radius: 999rpx; background: linear-gradient(180deg, #ffd64d, #ff8a18 48%, #e83b22); box-shadow: 0 0 0 4rpx rgba(255,172,30,.2), 0 4rpx 15rpx rgba(203,59,26,.45); animation: egFlameReady .7s ease-in-out infinite alternate; } +.eg-special-mark.is-burst { width: 48rpx; height: 48rpx; border-color: #fff3a8; border-radius: 15rpx 50% 50%; background: linear-gradient(145deg, #7c3aed 5%, #dd3b70 48%, #ff8a18 100%); box-shadow: 0 4rpx 13rpx rgba(108,35,155,.38), 0 0 12rpx rgba(255,179,38,.3); animation: egLReady 1.25s ease-in-out infinite alternate; } +.eg-mini-flame { position: relative; width: 25rpx; height: 30rpx; border-radius: 70% 32% 68% 40%; background: linear-gradient(135deg, #fff8a8 8%, #ffc21f 42%, #f04420 84%); box-shadow: 0 0 14rpx rgba(255,230,92,.95); transform: rotate(43deg); } +.eg-mini-flame-core { position: absolute; right: 4rpx; bottom: 3rpx; width: 10rpx; height: 15rpx; border-radius: 70% 35% 70% 42%; background: #fffbd1; box-shadow: 0 0 7rpx rgba(255,255,210,.95); } +.eg-l-special { position: relative; width: 29rpx; height: 29rpx; filter: drop-shadow(0 1rpx 2rpx rgba(73,22,105,.3)); } +.eg-l-arm { position: absolute; left: 3rpx; bottom: 3rpx; border-radius: 999rpx; background: #fff9b8; box-shadow: 0 0 6rpx rgba(255,249,184,.92); } +.eg-l-arm.is-vertical { width: 8rpx; height: 25rpx; } +.eg-l-arm.is-horizontal { width: 25rpx; height: 8rpx; } +.eg-l-core { position: absolute; left: 1rpx; bottom: 1rpx; width: 12rpx; height: 12rpx; border: 2rpx solid #fff; border-radius: 50%; background: #ff5b25; } +.eg-cell.is-fire-clearing { border-color: #ff7a1a; box-shadow: inset 0 0 25rpx rgba(255,102,18,.36), 0 0 18rpx rgba(255,105,20,.48); filter: brightness(1.35) saturate(1.42); } +.eg-cell.is-clearing.is-fire-clearing { animation: egFireCellHit .36s cubic-bezier(.2,.78,.22,1) forwards; } +.eg-fire-clear { position: absolute; z-index: 14; inset: 2rpx; overflow: hidden; border: 3rpx solid rgba(255,210,71,.92); border-radius: 22rpx; opacity: .96; background: linear-gradient(180deg, rgba(255,230,86,.12), rgba(239,63,27,.22)); pointer-events: none; } +.eg-fire-glow { position: absolute; inset: 6%; border-radius: 18rpx; background: radial-gradient(circle, rgba(255,246,177,.74), rgba(255,131,22,.32) 47%, rgba(222,47,23,0) 75%); animation: egFireGlow .34s ease-out both; } +.eg-fire-sweep { position: absolute; top: 23%; left: -48%; width: 168%; height: 55%; border-radius: 60% 45% 55% 42%; background: linear-gradient(90deg, rgba(255,184,38,0), rgba(255,238,124,.94) 28%, rgba(255,133,22,.92) 53%, rgba(226,54,25,.82) 72%, rgba(255,184,38,0)); box-shadow: 0 0 27rpx rgba(255,91,17,.72); animation: egFireSweep .34s cubic-bezier(.2,.78,.22,1) both; } +.eg-fire-flame { position: absolute; bottom: 13%; width: 25rpx; height: 39rpx; border-radius: 72% 35% 68% 42%; background: linear-gradient(145deg, #fff7a6 4%, #ffc329 39%, #ff7419 66%, #dc321f 100%); box-shadow: 0 0 15rpx rgba(255,124,21,.88); transform: rotate(42deg); animation: egFireFlame .34s ease-out both; } +.eg-fire-flame > view { position: absolute; right: 5rpx; bottom: 4rpx; width: 10rpx; height: 18rpx; border-radius: 70% 35% 70% 42%; background: #fffbd6; } +.eg-fire-flame.is-left { left: 16%; animation-delay: .01s; } +.eg-fire-flame.is-center { left: 43%; bottom: 19%; transform: rotate(42deg) scale(1.18); animation-delay: .035s; } +.eg-fire-flame.is-right { right: 15%; animation-delay: .065s; } +.eg-fire-spark { position: absolute; width: 10rpx; height: 17rpx; border-radius: 70% 35% 70% 42%; background: #ffe45e; box-shadow: 0 0 11rpx rgba(255,139,28,.9); animation: egFireSpark .34s ease-out both; } +.eg-fire-spark.is-one { top: 46%; left: 25%; } +.eg-fire-spark.is-two { top: 34%; left: 62%; animation-delay: .035s; } +.eg-fire-spark.is-three { top: 52%; left: 78%; animation-delay: .07s; } +.eg-board-tip { display: flex; align-items: center; justify-content: center; gap: 8rpx; min-height: 58rpx; margin-top: 12rpx; color: #684817; font-size: 26rpx; font-weight: 700; text-align: center; } +.eg-impact-text { + position: absolute; + z-index: 20; + top: 46%; + left: 50%; + padding: 10rpx 24rpx; + border: 4rpx solid rgba(255,255,255,.9); + border-radius: 999rpx; + color: #fff; + background: linear-gradient(135deg, #f59e0b, #e4572e); + box-shadow: 0 10rpx 28rpx rgba(190, 70, 25, .35); + font-size: 34rpx; + font-weight: 900; + transform: translate(-50%, -50%); + animation: egImpact .52s cubic-bezier(.16,.82,.3,1) forwards; + pointer-events: none; +} +.eg-impact-text.is-level-2 { font-size: 40rpx; background: linear-gradient(135deg, #8b5cf6, #ec4899); } +.eg-impact-text.is-level-3 { font-size: 46rpx; background: linear-gradient(135deg, #ef4444, #f59e0b); box-shadow: 0 12rpx 36rpx rgba(239,68,68,.42); } +.eg-jackpot { + position: absolute; + z-index: 30; + top: 48%; + left: 50%; + width: 540rpx; + padding: 24rpx 22rpx 20rpx; + overflow: hidden; + border: 7rpx solid #ffd83d; + border-radius: 36rpx; + color: #fff; + background: radial-gradient(circle at 50% 0%, #ef4444, #9f1239 65%, #64102a); + box-shadow: 0 0 0 7rpx #fff2a8, 0 22rpx 52rpx rgba(100,16,42,.5); + text-align: center; + transform: translate(-50%, -50%); + animation: egJackpotIn 1.35s cubic-bezier(.16,.85,.25,1) forwards; + pointer-events: none; +} +.eg-jackpot.is-match-4 { background: radial-gradient(circle at 50% 0%, #8b5cf6, #5b21b6 68%, #35106d); } +.eg-jackpot.is-match-5 { border-color: #fff36a; background: radial-gradient(circle at 50% 0%, #ff6b24, #d7193f 58%, #760c2a); animation-duration: 1.9s; } +.eg-jackpot.is-match-3 { + width: 414rpx; + padding: 20rpx 22rpx 18rpx; + border-width: 4rpx; + border-color: #f1c663; + color: #31574b; + background: linear-gradient(155deg, #f7fff9, #fff5d9); + box-shadow: 0 0 0 4rpx rgba(255,255,255,.84), 0 14rpx 34rpx rgba(98,82,39,.2); + animation: egTripleIn 1.12s cubic-bezier(.2,.82,.28,1) forwards; +} +.eg-jackpot.is-match-3 .eg-jackpot-lights { display: none; } +.eg-jackpot.is-match-3 .eg-jackpot-kicker { color: #5b8c73; font-size: 21rpx; letter-spacing: 3rpx; text-shadow: none; } +.eg-jackpot.is-match-3 .eg-jackpot-title { color: #365b4e; font-size: 31rpx; text-shadow: none; } +.eg-jackpot.is-match-3 .eg-jackpot-reward { color: #ad7021; font-size: 25rpx; } +.eg-jackpot.is-match-3 .eg-jackpot-note { color: #829087; } +.eg-triple-cans { position: relative; display: flex; justify-content: center; gap: 12rpx; margin: 12rpx 0 10rpx; } +.eg-triple-can { display: flex; width: 78rpx; height: 76rpx; align-items: center; justify-content: center; border: 3rpx solid #f4d996; border-radius: 19rpx; background: rgba(255,255,255,.9); box-shadow: 0 5rpx 12rpx rgba(128,92,30,.12); animation: egTripleCan .42s cubic-bezier(.2,.86,.26,1) both; } +.eg-triple-can:nth-child(2) { animation-delay: .07s; } +.eg-triple-can:nth-child(3) { animation-delay: .14s; } +.eg-triple-can > image { width: 62rpx; height: 58rpx; } + +.eg-jackpot.is-match-4 { + width: 548rpx; + padding: 25rpx 22rpx 22rpx; + border-color: #ffd95b; + background: radial-gradient(circle at 50% -10%, #a97cff, #6031b5 58%, #341066); + box-shadow: 0 0 0 6rpx #fff0a9, 0 0 36rpx rgba(255,210,76,.5), 0 24rpx 58rpx rgba(53,16,109,.48); + animation: egFourIn 1.88s cubic-bezier(.16,.84,.24,1) forwards; +} +.eg-jackpot.is-match-4 .eg-jackpot-kicker { color: #ffef8d; font-size: 24rpx; letter-spacing: 3rpx; } +.eg-jackpot.is-match-4 .eg-jackpot-title { margin-top: 9rpx; color: #fff; font-size: 39rpx; } +.eg-jackpot.is-match-4 .eg-jackpot-reward { color: #fff2a8; } +.eg-four-teaser { position: relative; margin-top: 13rpx; } +.eg-four-cans { display: flex; align-items: center; justify-content: center; gap: 8rpx; } +.eg-four-can { display: flex; width: 76rpx; height: 92rpx; align-items: center; justify-content: center; border: 4rpx solid #ffe078; border-radius: 18rpx; background: linear-gradient(180deg, #fffef7, #ffe9a5); box-shadow: inset 0 -5rpx 0 rgba(171,106,17,.12), 0 7rpx 13rpx rgba(34,12,67,.28); animation: egFourCan .48s cubic-bezier(.18,.9,.25,1.2) both; } +.eg-four-can:nth-child(2) { animation-delay: .07s; } +.eg-four-can:nth-child(3) { animation-delay: .14s; } +.eg-four-can:nth-child(4) { animation-delay: .21s; } +.eg-four-can > image { width: 64rpx; height: 70rpx; } +.eg-four-can.is-locked { border-style: dashed; border-color: rgba(255,241,161,.86); color: #fff6ae; background: rgba(48,18,91,.54); box-shadow: inset 0 0 16rpx rgba(255,225,91,.18), 0 0 18rpx rgba(255,222,91,.36); font-size: 48rpx; font-weight: 1000; animation: egLockedCan .68s .34s ease-in-out infinite alternate; } +.eg-four-promise { display: inline-flex; margin-top: 15rpx; padding: 8rpx 18rpx; border: 2rpx solid rgba(255,247,184,.72); border-radius: 999rpx; color: #3d176f; background: linear-gradient(90deg, #fff3a3, #ffd85b); box-shadow: 0 5rpx 15rpx rgba(28,7,62,.22); font-size: 25rpx; font-weight: 1000; animation: egPromisePulse .72s ease-in-out infinite alternate; } +.eg-five-celebration { position: relative; margin: 15rpx 0 10rpx; } +.eg-five-cans { display: flex; align-items: center; justify-content: center; gap: 8rpx; } +.eg-five-can { display: flex; width: 78rpx; height: 93rpx; align-items: center; justify-content: center; border: 4rpx solid #fff17b; border-radius: 18rpx; background: linear-gradient(180deg, #fff, #fff2ac); box-shadow: inset 0 -5rpx 0 rgba(184,91,12,.13), 0 7rpx 0 #bd5b13, 0 0 16rpx rgba(255,240,101,.5); animation: egFiveCan .56s cubic-bezier(.16,.92,.24,1.2) both; } +.eg-five-can:nth-child(2) { animation-delay: .07s; } +.eg-five-can:nth-child(3) { animation-delay: .14s; } +.eg-five-can:nth-child(4) { animation-delay: .21s; } +.eg-five-can:nth-child(5) { animation-delay: .28s; } +.eg-five-can > image { width: 65rpx; height: 72rpx; } +.eg-five-sevens { display: flex; justify-content: center; gap: 8rpx; margin-top: 13rpx; } +.eg-five-sevens > text { display: flex; width: 58rpx; height: 55rpx; align-items: center; justify-content: center; border: 3rpx solid #fff5a7; border-radius: 13rpx; color: #fff36a; background: linear-gradient(155deg, #d7193f, #9f1239); box-shadow: 0 5rpx 0 #70102b, 0 0 13rpx rgba(255,243,106,.45); font-size: 43rpx; font-weight: 1000; line-height: 1; text-shadow: 0 3rpx 0 #8f1733; } +.eg-jackpot-lights { position: absolute; inset: 8rpx; border: 4rpx dotted rgba(255,255,255,.88); border-radius: 25rpx; animation: egLights .24s steps(2) infinite; } +.eg-jackpot-kicker { position: relative; display: block; color: #fff36a; font-size: 28rpx; font-weight: 900; letter-spacing: 6rpx; text-shadow: 0 3rpx 0 rgba(83,19,25,.45); } +.eg-jackpot-reels { position: relative; display: flex; justify-content: center; gap: 12rpx; margin: 13rpx 0; } +.eg-jackpot-reel { display: flex; width: 112rpx; height: 126rpx; flex-direction: column; align-items: center; justify-content: center; border: 6rpx solid #ffcf31; border-radius: 20rpx; background: linear-gradient(180deg, #fff 0%, #fff7cf 48%, #ffd55e 50%, #fff 53%, #fff8dc 100%); box-shadow: inset 0 0 18rpx rgba(137,78,10,.25), 0 7rpx 0 #a85a0a; animation: egReelStop .52s cubic-bezier(.2,.9,.25,1) both; } +.eg-jackpot-reel:nth-child(2) { animation-delay: .09s; } +.eg-jackpot-reel:nth-child(3) { animation-delay: .18s; } +.eg-jackpot-seven { color: #e11d48; font-size: 61rpx; font-weight: 1000; line-height: .85; text-shadow: 0 3rpx 0 #ffd1d8; } +.eg-jackpot-milk { width: 54rpx; height: 36rpx; margin-top: 3rpx; border-radius: 7rpx; } +.eg-jackpot-title { position: relative; display: block; font-size: 37rpx; font-weight: 900; text-shadow: 0 4rpx 0 rgba(73,10,30,.5); } +.eg-jackpot-points { position: relative; display: block; margin-top: 2rpx; color: #fff36a; font-size: 66rpx; font-weight: 1000; line-height: 1.05; letter-spacing: 2rpx; text-shadow: 0 5rpx 0 #9e2813, 0 0 18rpx rgba(255,243,106,.85); animation: egPointsPop .62s .42s cubic-bezier(.16,.9,.25,1.25) both; } +.eg-jackpot-reward { position: relative; display: block; margin-top: 7rpx; color: #fff5a5; font-size: 27rpx; font-weight: 800; line-height: 1.35; } +.eg-jackpot-note { position: relative; display: block; margin-top: 8rpx; color: rgba(255,255,255,.8); font-size: 22rpx; } +.eg-tools { margin-top: 16rpx; } +.eg-tool { display: flex; align-items: center; padding: 16rpx 18rpx; border: 3rpx solid #c9e0d6; border-radius: 26rpx; background: rgba(255,255,255,.92); box-shadow: 0 7rpx 18rpx rgba(24, 84, 63, .07); } +.eg-tool.active { border-color: #f59e0b; background: #fffbeb; } +.eg-tool.disabled { opacity: .45; } +.eg-tool-icon { position: relative; display: flex; width: 88rpx; height: 88rpx; align-items: center; justify-content: center; overflow: visible; border-radius: 24rpx; font-size: 46rpx; background: linear-gradient(145deg, #e8f7ff, #d4ecff); box-shadow: inset 0 -4rpx 0 rgba(33,104,151,.08); } +.eg-tool-food-image { width: 76rpx; height: 58rpx; border-radius: 14rpx; } +.eg-replace-badge { position: absolute; right: -12rpx; bottom: -8rpx; display: flex; min-width: 78rpx; height: 38rpx; align-items: center; justify-content: center; gap: 2rpx; padding: 0 8rpx; border: 3rpx solid #fff; border-radius: 999rpx; color: #fff; background: linear-gradient(135deg, #2389b9, #145d91); box-shadow: 0 5rpx 12rpx rgba(20,93,145,.3); } +.eg-replace-badge .tj-icon-wrap { width: 22rpx; height: 22rpx; } +.eg-replace-badge > text { font-size: 20rpx; font-weight: 900; line-height: 1; } +.eg-tool-copy { display: flex; flex: 1; flex-direction: column; margin-left: 16rpx; } +.eg-tool-name { color: #244a3d; font-size: 29rpx; font-weight: 800; } +.eg-tool-desc { margin-top: 5rpx; color: #6c8179; font-size: 25rpx; } +.eg-tool-count { color: #155e4b; font-size: 32rpx; font-weight: 900; } +.eg-legend { display: flex; justify-content: center; gap: 18rpx; margin-top: 18rpx; color: #53685f; font-size: 25rpx; font-weight: 700; } +.eg-dot--safe { color: #4cae7b; } +.eg-dot--mid { color: #e2aa19; } +.eg-dot--high { color: #e4572e; } + +.eg-weekly-overlay { position: fixed; z-index: 90; inset: 0; display: flex; align-items: center; justify-content: center; padding: 28rpx; background: rgba(16, 55, 43, .66); backdrop-filter: blur(5px); } +.eg-weekly-card { width: 100%; max-width: 664rpx; max-height: calc(100vh - 56rpx); overflow-y: auto; padding: 28rpx 26rpx 22rpx; border: 5rpx solid rgba(255,255,255,.96); border-radius: 38rpx; background: linear-gradient(160deg, #f5fff9 0%, #fffdf3 56%, #fff4db 100%); box-shadow: 0 30rpx 80rpx rgba(5,45,32,.38); animation: egWeeklyIn .36s cubic-bezier(.18,.88,.27,1.08) both; } +.eg-weekly-head { display: flex; align-items: center; justify-content: space-between; } +.eg-weekly-title-line { display: flex; align-items: center; gap: 10rpx; } +.eg-weekly-title { color: #155e4b; font-size: 37rpx; font-weight: 1000; } +.eg-weekly-preview { padding: 5rpx 10rpx; border-radius: 999rpx; color: #9a5b14; background: #fff0bd; font-size: 19rpx; font-weight: 900; } +.eg-weekly-sub { display: block; margin-top: 5rpx; color: #6b837a; font-size: 23rpx; font-weight: 700; } +.eg-weekly-medal { display: flex; width: 76rpx; height: 76rpx; align-items: center; justify-content: center; border: 5rpx solid #fff3b2; border-radius: 50%; color: #fff; background: linear-gradient(145deg, #f5ad24, #e26925); box-shadow: 0 8rpx 0 #b85421, 0 12rpx 24rpx rgba(181,84,33,.24); font-size: 39rpx; font-weight: 1000; } +.eg-weekly-progress-card { display: flex; align-items: center; justify-content: space-between; margin-top: 20rpx; padding: 16rpx 18rpx; border: 3rpx solid #d7ebe2; border-radius: 24rpx; background: rgba(255,255,255,.9); } +.eg-weekly-progress-label { display: block; color: #657b73; font-size: 22rpx; font-weight: 700; } +.eg-weekly-progress-value { display: block; margin-top: -2rpx; color: #e06b24; font-size: 43rpx; font-weight: 1000; } +.eg-weekly-progress-copy { display: flex; flex-direction: column; align-items: flex-end; color: #235b49; font-size: 24rpx; font-weight: 900; line-height: 1.55; } +.eg-weekly-progress-copy text:last-child { color: #b86820; } +.eg-weekly-list { display: flex; flex-direction: column; gap: 7rpx; margin-top: 15rpx; } +.eg-weekly-row { display: flex; min-height: 68rpx; align-items: center; padding: 7rpx 14rpx; border: 2rpx solid transparent; border-radius: 19rpx; color: #345c50; background: rgba(255,255,255,.72); } +.eg-weekly-row.is-me { border-color: #f2bd48; color: #174f3e; background: linear-gradient(90deg, #fff2bb, #fff9e3); box-shadow: 0 5rpx 14rpx rgba(153,102,17,.12); transform: scale(1.015); } +.eg-weekly-place { width: 43rpx; color: #6b7d76; font-size: 27rpx; font-weight: 1000; text-align: center; } +.eg-weekly-row:nth-child(1) .eg-weekly-place { color: #dd7b17; font-size: 31rpx; } +.eg-weekly-avatar { display: flex; width: 52rpx; height: 52rpx; flex: 0 0 52rpx; align-items: center; justify-content: center; margin-left: 6rpx; border: 3rpx solid rgba(255,255,255,.9); border-radius: 50%; color: #fff; background: #5d9b84; box-shadow: 0 3rpx 9rpx rgba(30,78,61,.16); font-size: 24rpx; font-weight: 1000; } +.eg-weekly-avatar.is-tone-2 { background: #e39932; } +.eg-weekly-avatar.is-tone-3 { background: #6c83c8; } +.eg-weekly-avatar.is-tone-4 { background: #e5683e; } +.eg-weekly-avatar.is-tone-5 { background: #a673bd; } +.eg-weekly-avatar.is-tone-6 { background: #448e9c; } +.eg-weekly-avatar.is-tone-7 { background: #c27b49; } +.eg-weekly-name { flex: 1; margin-left: 13rpx; overflow: hidden; font-size: 27rpx; font-weight: 850; text-overflow: ellipsis; white-space: nowrap; } +.eg-weekly-me { margin-right: 8rpx; padding: 3rpx 9rpx; border-radius: 999rpx; color: #fff; background: #dd6b27; font-size: 18rpx; font-weight: 900; } +.eg-weekly-count { min-width: 70rpx; color: #315b4d; font-size: 29rpx; font-weight: 1000; text-align: right; } +.eg-cheer-card { margin-top: 16rpx; padding: 15rpx 17rpx 14rpx; border: 3rpx solid #f0d38d; border-radius: 23rpx; background: linear-gradient(135deg, #fff9d9, #fff1c2); } +.eg-cheer-card.is-family { border-color: #d9c8ef; background: linear-gradient(135deg, #f8f2ff, #efe7ff); } +.eg-cheer-card.is-bright { border-color: #f4c77b; background: linear-gradient(135deg, #fff8dc, #ffeec7); } +.eg-cheer-top { display: flex; align-items: center; justify-content: space-between; } +.eg-cheer-source { color: #8e5c18; font-size: 21rpx; font-weight: 900; } +.eg-cheer-card.is-family .eg-cheer-source { color: #77539c; } +.eg-cheer-change { padding: 5rpx 12rpx; border-radius: 999rpx; color: #176b52; background: rgba(255,255,255,.75); font-size: 19rpx; font-weight: 900; } +.eg-cheer-copy { display: block; margin-top: 7rpx; color: #55462d; font-size: 28rpx; font-weight: 900; line-height: 1.42; } +.eg-cheer-note { display: block; margin-top: 5rpx; color: #8d7c77; font-size: 18rpx; } +.eg-weekly-start { display: flex; height: 84rpx; align-items: center; justify-content: center; margin-top: 18rpx; border-radius: 24rpx; color: #fff; background: linear-gradient(135deg, #24906c, #12654a); box-shadow: 0 9rpx 20rpx rgba(18,101,74,.24); font-size: 30rpx; font-weight: 1000; } +.eg-weekly-footnote { display: block; margin-top: 10rpx; color: #8b8b7d; font-size: 18rpx; text-align: center; } + +.eg-stage-overlay { position: fixed; z-index: 58; inset: 0; display: flex; align-items: center; justify-content: center; padding: 40rpx; background: rgba(22,62,50,.42); pointer-events: none; } +.eg-stage-card { width: 100%; max-width: 620rpx; padding: 34rpx 28rpx 30rpx; border: 6rpx solid #fff; border-radius: 38rpx; background: linear-gradient(155deg, #f5fff9, #fff8da); box-shadow: 0 26rpx 70rpx rgba(10,56,41,.3); text-align: center; animation: egStageIn 1.8s cubic-bezier(.16,.86,.28,1) both; } +.eg-stage-kicker { display: block; color: #16815f; font-size: 23rpx; font-weight: 900; letter-spacing: 3rpx; } +.eg-stage-number { display: block; margin-top: 5rpx; color: #e47722; font-size: 48rpx; font-weight: 1000; line-height: 1.1; } +.eg-stage-title { display: block; margin-top: 7rpx; color: #194e3d; font-size: 38rpx; font-weight: 900; } +.eg-stage-foods { display: flex; justify-content: center; gap: 9rpx; margin-top: 22rpx; } +.eg-stage-food { display: flex; width: 92rpx; min-height: 110rpx; flex-direction: column; align-items: center; justify-content: center; border: 3rpx solid #dbece4; border-radius: 20rpx; color: #315b4e; background: #fff; font-size: 20rpx; font-weight: 800; } +.eg-stage-food > image { width: 72rpx; height: 52rpx; border-radius: 12rpx; } +.eg-stage-food > text { margin-top: 8rpx; } +.eg-stage-food.is-danger { border-color: #ef8b52; color: #a43c20; background: #fff1e6; box-shadow: 0 0 0 4rpx rgba(239,139,82,.12); } +.eg-stage-tip { display: block; margin-top: 20rpx; color: #6e776d; font-size: 21rpx; font-weight: 700; } +.eg-reshuffle-overlay { position: fixed; z-index: 65; inset: 0; display: flex; align-items: center; justify-content: center; padding: 46rpx; background: rgba(19,55,45,.5); } +.eg-reshuffle-card { width: 100%; max-width: 540rpx; padding: 38rpx 30rpx; border: 5rpx solid rgba(255,255,255,.95); border-radius: 36rpx; background: linear-gradient(155deg, #f4fff9, #e6f6ef); box-shadow: 0 24rpx 64rpx rgba(10,48,37,.3); text-align: center; } +.eg-reshuffle-icon { display: flex; width: 94rpx; height: 94rpx; align-items: center; justify-content: center; margin: 0 auto 18rpx; border-radius: 50%; background: linear-gradient(135deg, #2aa87b, #126a50); box-shadow: 0 10rpx 24rpx rgba(18,106,80,.28); animation: egReshuffleSpin .85s linear infinite; } +.eg-reshuffle-title { display: block; color: #174e3d; font-size: 36rpx; font-weight: 900; } +.eg-reshuffle-desc { display: block; margin-top: 10rpx; color: #5b746b; font-size: 24rpx; font-weight: 700; } + +.eg-overlay { position: fixed; z-index: 50; top: 0; right: 0; bottom: 0; left: 0; display: flex; align-items: center; justify-content: center; padding: 36rpx; background: rgba(20, 48, 40, .62); } +.eg-modal { width: 100%; max-width: 650rpx; padding: 34rpx 28rpx 28rpx; border-radius: 38rpx; background: #fffdf8; box-shadow: 0 28rpx 70rpx rgba(0,0,0,.22); } +.eg-modal-icon { display: block; font-size: 68rpx; text-align: center; } +.eg-modal-title { display: block; margin-top: 6rpx; color: #204e3d; font-size: 38rpx; font-weight: 900; text-align: center; } +.eg-modal-sub { display: block; margin: 8rpx 0 22rpx; color: #718078; font-size: 24rpx; text-align: center; } +.eg-result-warning { display: flex; width: 92rpx; height: 92rpx; align-items: center; justify-content: center; margin: 0 auto; border-radius: 50%; color: #fff; background: #e4572e; font-size: 60rpx; font-weight: 900; } +.eg-result-food { display: block; margin: 12rpx 0 18rpx; color: #c34b27; font-size: 28rpx; font-weight: 800; text-align: center; } +.eg-knowledge-card { padding: 22rpx; border-radius: 24rpx; background: #fff3e7; } +.eg-knowledge-title { display: block; color: #8d4427; font-size: 26rpx; font-weight: 800; } +.eg-knowledge-copy { display: block; margin-top: 9rpx; color: #5f514a; font-size: 23rpx; line-height: 1.65; } +.eg-knowledge-review { display: block; margin-top: 12rpx; color: #9b7768; font-size: 19rpx; } +.eg-result-score { display: flex; align-items: center; justify-content: space-between; margin: 20rpx 6rpx; color: #466258; font-size: 25rpx; } +.eg-result-score text:last-child { color: #e16f24; font-size: 36rpx; font-weight: 900; } +.eg-result-best { display: flex; align-items: center; justify-content: space-between; margin: -12rpx 6rpx 20rpx; color: #75867f; font-size: 22rpx; } +.eg-result-best text:last-child { color: #315d4e; font-size: 27rpx; font-weight: 900; } +.eg-primary-btn, .eg-secondary-btn { display: flex; height: 88rpx; align-items: center; justify-content: center; border-radius: 24rpx; font-size: 28rpx; font-weight: 800; } +.eg-primary-btn { color: #fff; background: linear-gradient(135deg, #238b68, #126649); box-shadow: 0 10rpx 22rpx rgba(18,102,73,.23); } +.eg-secondary-btn { margin-top: 12rpx; color: #45665b; background: #edf4f1; } +@keyframes egPulse { + 0%, 100% { box-shadow: 0 0 0 0 rgba(245,158,11,.2); } + 50% { box-shadow: 0 0 0 8rpx rgba(245,158,11,.12); } +} + +@keyframes egWeeklyIn { + 0% { opacity: 0; transform: translateY(34rpx) scale(.91); } + 100% { opacity: 1; transform: translateY(0) scale(1); } +} + +@keyframes egHit { + 0% { opacity: 1; transform: scale(1); } + 42% { opacity: 1; transform: scale(1.18) rotate(-3deg); } + 100% { opacity: 0; transform: scale(.32) rotate(7deg); } +} + +@keyframes egTaskRewardFlight { + 0% { opacity: 0; transform: translate3d(-20rpx, 0, 0) scale(.62) rotate(-8deg); } + 16% { opacity: 1; transform: translate3d(0, -24rpx, 0) scale(1.1) rotate(3deg); } + 72% { opacity: 1; transform: translate3d(270rpx, 900rpx, 0) scale(.68) rotate(8deg); } + 100% { opacity: 0; transform: translate3d(350rpx, 1205rpx, 0) scale(.3) rotate(2deg); } +} + +@keyframes egRewardGlow { + from { opacity: .4; transform: scale(.86); } + to { opacity: .9; transform: scale(1.12); } +} + +@keyframes egFireSweep { + 0% { opacity: 0; transform: translate3d(-38%, 20rpx, 0) scaleX(.6); } + 28% { opacity: 1; } + 72% { opacity: .96; } + 100% { opacity: 0; transform: translate3d(58%, -15rpx, 0) scaleX(1.08); } +} + +@keyframes egFireSpark { + 0% { opacity: 0; transform: translateY(14rpx) scale(.5) rotate(35deg); } + 38% { opacity: 1; } + 100% { opacity: 0; transform: translateY(-38rpx) scale(1.05) rotate(58deg); } +} + +@keyframes egFireGlow { + 0% { opacity: 0; transform: scale(.72); } + 38% { opacity: 1; transform: scale(1.08); } + 100% { opacity: 0; transform: scale(1.28); } +} + +@keyframes egFireCellHit { + 0% { opacity: 1; transform: scale(1); } + 24% { opacity: 1; transform: scale(1.09); } + 72% { opacity: 1; transform: scale(1.02); } + 100% { opacity: 0; transform: scale(.48); } +} + +@keyframes egFireFlame { + 0% { opacity: 0; transform: translateY(20rpx) rotate(42deg) scale(.55); } + 38% { opacity: 1; transform: translateY(0) rotate(42deg) scale(1.05); } + 100% { opacity: 0; transform: translateY(-31rpx) rotate(48deg) scale(.86); } +} + +@keyframes egFlameReady { + from { transform: scale(.94); filter: brightness(.96); } + to { transform: scale(1.08); filter: brightness(1.18); } +} + +@keyframes egLReady { + from { transform: scale(.94) rotate(-2deg); filter: brightness(.96); } + to { transform: scale(1.06) rotate(2deg); filter: brightness(1.12); } +} + +@keyframes egImpact { + 0% { opacity: 0; transform: translate(-50%, -42%) scale(.55); } + 35% { opacity: 1; transform: translate(-50%, -50%) scale(1.16); } + 72% { opacity: 1; transform: translate(-50%, -54%) scale(1); } + 100% { opacity: 0; transform: translate(-50%, -76%) scale(.92); } +} + +@keyframes egJackpotIn { + 0% { opacity: 0; transform: translate(-50%, -45%) scale(.48) rotate(-4deg); } + 20% { opacity: 1; transform: translate(-50%, -50%) scale(1.09) rotate(2deg); } + 31% { transform: translate(-50%, -50%) scale(.98) rotate(0); } + 78% { opacity: 1; transform: translate(-50%, -53%) scale(1); } + 100% { opacity: 0; transform: translate(-50%, -68%) scale(.92); } +} + +@keyframes egTripleIn { + 0% { opacity: 0; transform: translate(-50%, -44%) scale(.82); } + 20% { opacity: 1; transform: translate(-50%, -50%) scale(1.03); } + 74% { opacity: 1; transform: translate(-50%, -52%) scale(1); } + 100% { opacity: 0; transform: translate(-50%, -60%) scale(.96); } +} + +@keyframes egTripleCan { + 0% { opacity: 0; transform: translateY(18rpx) scale(.72); } + 100% { opacity: 1; transform: translateY(0) scale(1); } +} + +@keyframes egFourIn { + 0% { opacity: 0; transform: translate(-50%, -44%) scale(.62) rotate(-3deg); } + 18% { opacity: 1; transform: translate(-50%, -50%) scale(1.08) rotate(1deg); } + 29% { transform: translate(-50%, -50%) scale(.98) rotate(0); } + 78% { opacity: 1; transform: translate(-50%, -52%) scale(1); } + 100% { opacity: 0; transform: translate(-50%, -65%) scale(.94); } +} + +@keyframes egFourCan { + 0% { opacity: 0; transform: translateY(-22rpx) rotate(-7deg) scale(.65); } + 75% { opacity: 1; transform: translateY(5rpx) rotate(2deg) scale(1.05); } + 100% { opacity: 1; transform: translateY(0) rotate(0) scale(1); } +} + +@keyframes egFiveCan { + 0% { opacity: 0; transform: translateY(-40rpx) scale(.52) rotate(-8deg); filter: brightness(1.5); } + 70% { opacity: 1; transform: translateY(7rpx) scale(1.08) rotate(2deg); } + 100% { opacity: 1; transform: translateY(0) scale(1) rotate(0); filter: brightness(1); } +} + +@keyframes egLockedCan { + from { opacity: .62; transform: scale(.92); filter: brightness(.9); } + to { opacity: 1; transform: scale(1.06); filter: brightness(1.2); } +} + +@keyframes egPromisePulse { + from { transform: scale(.97); box-shadow: 0 5rpx 15rpx rgba(28,7,62,.22); } + to { transform: scale(1.03); box-shadow: 0 7rpx 21rpx rgba(255,221,89,.38); } +} + +@keyframes egReelStop { + 0% { opacity: .2; transform: translateY(-48rpx) scaleY(1.4); filter: blur(5rpx); } + 70% { opacity: 1; transform: translateY(7rpx) scaleY(.94); filter: blur(0); } + 100% { transform: translateY(0) scaleY(1); } +} + +@keyframes egLights { + 0% { opacity: .35; } + 100% { opacity: 1; } +} + +@keyframes egConfettiFall { + 0% { opacity: 0; transform: translate3d(0, -8vh, 0) rotate(0deg) scale(.7); } + 9% { opacity: 1; } + 48% { transform: translate3d(42rpx, 48vh, 0) rotate(420deg) scale(1); } + 100% { opacity: .92; transform: translate3d(-28rpx, 112vh, 0) rotate(920deg) scale(.86); } +} + +@keyframes egPointsPop { + 0% { opacity: 0; transform: scale(.35) rotate(-7deg); } + 55% { opacity: 1; transform: scale(1.28) rotate(3deg); } + 78% { transform: scale(.92) rotate(0); } + 100% { opacity: 1; transform: scale(1); } +} + +@keyframes egStageIn { + 0% { opacity: 0; transform: scale(.7) translateY(34rpx); } + 18% { opacity: 1; transform: scale(1.06) translateY(0); } + 30%, 76% { opacity: 1; transform: scale(1); } + 100% { opacity: 0; transform: scale(.96) translateY(-22rpx); } +} + +@keyframes egReshuffleSpin { + from { transform: rotate(0); } + to { transform: rotate(360deg); } +} + +@media screen and (max-height: 760px) { + .eg-task-card { padding-top: 13rpx; padding-bottom: 13rpx; } +} diff --git a/TUICallKit-Vue3/tongji/endless-game/index.vue b/TUICallKit-Vue3/tongji/endless-game/index.vue new file mode 100644 index 00000000..2684ea8e --- /dev/null +++ b/TUICallKit-Vue3/tongji/endless-game/index.vue @@ -0,0 +1,1459 @@ + + + + + diff --git a/TUICallKit-Vue3/tongji/endless-game/utils/svgDataUrl.js b/TUICallKit-Vue3/tongji/endless-game/utils/svgDataUrl.js new file mode 100644 index 00000000..045b6282 --- /dev/null +++ b/TUICallKit-Vue3/tongji/endless-game/utils/svgDataUrl.js @@ -0,0 +1,49 @@ +/** + * 将 SVG 字符串转为小程序可用的 data URL(base64 在真机上更稳定) + */ + +function utf8ToBytes(str) { + const encoded = encodeURIComponent(str) + const bytes = [] + for (let i = 0; i < encoded.length; i++) { + if (encoded.charCodeAt(i) === 37) { + bytes.push(parseInt(encoded.substring(i + 1, i + 3), 16)) + i += 2 + } else { + bytes.push(encoded.charCodeAt(i)) + } + } + return new Uint8Array(bytes) +} + +function bytesToBase64(bytes) { + if (typeof uni !== 'undefined' && typeof uni.arrayBufferToBase64 === 'function') { + return uni.arrayBufferToBase64(bytes.buffer) + } + if (typeof btoa !== 'undefined') { + let binary = '' + for (let i = 0; i < bytes.length; i++) { + binary += String.fromCharCode(bytes[i]) + } + return btoa(binary) + } + const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' + let result = '' + for (let i = 0; i < bytes.length; i += 3) { + const a = bytes[i] + const b = i + 1 < bytes.length ? bytes[i + 1] : 0 + const c = i + 2 < bytes.length ? bytes[i + 2] : 0 + result += chars[a >> 2] + result += chars[((a & 3) << 4) | (b >> 4)] + result += i + 1 < bytes.length ? chars[((b & 15) << 2) | (c >> 6)] : '=' + result += i + 2 < bytes.length ? chars[c & 63] : '=' + } + return result +} + +export function svgToDataUrl(svg) { + const normalized = String(svg || '').trim() + if (!normalized) return '' + const base64 = bytesToBase64(utf8ToBytes(normalized)) + return `data:image/svg+xml;base64,${base64}` +} diff --git a/TUICallKit-Vue3/tongji/endless-game/viteAssetOutput.js b/TUICallKit-Vue3/tongji/endless-game/viteAssetOutput.js new file mode 100644 index 00000000..b80d3e00 --- /dev/null +++ b/TUICallKit-Vue3/tongji/endless-game/viteAssetOutput.js @@ -0,0 +1,33 @@ +const FOOD_ASSET_NAMES = new Set([ + 'apple.jpg', 'banana.jpg', 'blackCoffee.jpg', 'broccoli.jpg', 'brownRice.jpg', + 'camel.jpg', 'celery.jpg', 'centuryCongee.jpg', 'chicken.jpg', 'coffee.jpg', + 'congee.jpg', 'corn.jpg', 'cucumber.jpg', 'egg.jpg', 'eightCongee.jpg', + 'grainMantou.jpg', 'greenBean.jpg', 'lettuce.jpg', 'mango.jpg', 'milk.jpg', + 'milkTea.jpg', 'milletCongee.jpg', 'mushroom.jpg', 'noodleSoup.jpg', 'onion.jpg', + 'orangeJuice.jpg', 'pepper.jpg', 'pineapple.jpg', 'potato.jpg', 'riceNoodleSoup.jpg', + 'shrimp.jpg', 'soda.jpg', 'strawberry.jpg', 'taro.jpg', 'tea.jpg', 'tomato.jpg', + 'udon.jpg', 'wonton.jpg' +]) + +/** + * 让游戏食品图进入 tongji 分包,避免增加微信小程序主包体积。 + * 迁移到其他 uni-app 项目时,在 vite.config 中复用此函数即可。 + */ +export function endlessGameAssetFileNames(assetInfo = {}) { + const candidates = [ + assetInfo.name, + ...(assetInfo.names || []), + assetInfo.originalFileName, + ...(assetInfo.originalFileNames || []) + ].filter(Boolean) + + const isEndlessFood = candidates.some((value) => { + const normalized = String(value).replace(/\\/g, '/') + const basename = normalized.split('/').pop() + return normalized.includes('tongji/endless-game/assets/food/') || FOOD_ASSET_NAMES.has(basename) + }) + + return isEndlessFood + ? 'tongji/endless-game/assets/food/[name].[hash][extname]' + : 'assets/[name].[hash][extname]' +} diff --git a/TUICallKit-Vue3/tongji/pages/weekly.vue b/TUICallKit-Vue3/tongji/pages/weekly.vue index 353204ab..dd348e07 100644 --- a/TUICallKit-Vue3/tongji/pages/weekly.vue +++ b/TUICallKit-Vue3/tongji/pages/weekly.vue @@ -1514,7 +1514,7 @@ async function goMorePage() { function goGamePage() { uni.navigateTo({ - url: '/tongji/pages/game', + url: '/tongji/endless-game/index', fail() { uni.showToast({ title: '暂时无法打开游戏', icon: 'none' }) } diff --git a/TUICallKit-Vue3/vite.config.ts b/TUICallKit-Vue3/vite.config.ts index cb8a4bc7..99a36031 100644 --- a/TUICallKit-Vue3/vite.config.ts +++ b/TUICallKit-Vue3/vite.config.ts @@ -1,10 +1,18 @@ import { defineConfig } from 'vite' import uni from '@dcloudio/vite-plugin-uni' +import { endlessGameAssetFileNames } from './tongji/endless-game/viteAssetOutput.js' // uni-app 工程根目录就是源码目录(HBuilderX 兼容) // 编译产物默认输出到 ./dist// export default defineConfig({ plugins: [uni()], + build: { + rollupOptions: { + output: { + assetFileNames: endlessGameAssetFileNames, + }, + }, + }, css: { preprocessorOptions: { scss: {